diff --git a/apps/acls/api/login_acl.py b/apps/acls/api/login_acl.py index c42e61a2c..85e902143 100644 --- a/apps/acls/api/login_acl.py +++ b/apps/acls/api/login_acl.py @@ -1,20 +1,14 @@ -from common.permissions import IsOrgAdmin, HasQueryParamsUserAndIsCurrentOrgMember from common.drf.api import JMSBulkModelViewSet from ..models import LoginACL from .. import serializers from ..filters import LoginAclFilter -__all__ = ['LoginACLViewSet', ] +__all__ = ['LoginACLViewSet'] class LoginACLViewSet(JMSBulkModelViewSet): queryset = LoginACL.objects.all() filterset_class = LoginAclFilter search_fields = ('name',) - permission_classes = (IsOrgAdmin,) serializer_class = serializers.LoginACLSerializer - def get_permissions(self): - if self.action in ["retrieve", "list"]: - self.permission_classes = (IsOrgAdmin, HasQueryParamsUserAndIsCurrentOrgMember) - return super().get_permissions() diff --git a/apps/acls/api/login_asset_acl.py b/apps/acls/api/login_asset_acl.py index fa03851b9..1447c16d5 100644 --- a/apps/acls/api/login_asset_acl.py +++ b/apps/acls/api/login_asset_acl.py @@ -1,5 +1,4 @@ from orgs.mixins.api import OrgBulkModelViewSet -from common.permissions import IsOrgAdmin from .. import models, serializers @@ -10,5 +9,4 @@ class LoginAssetACLViewSet(OrgBulkModelViewSet): model = models.LoginAssetACL filterset_fields = ('name', ) search_fields = filterset_fields - permission_classes = (IsOrgAdmin, ) serializer_class = serializers.LoginAssetACLSerializer diff --git a/apps/acls/api/login_asset_check.py b/apps/acls/api/login_asset_check.py index f45f4f2ff..e27eefc07 100644 --- a/apps/acls/api/login_asset_check.py +++ b/apps/acls/api/login_asset_check.py @@ -1,7 +1,6 @@ from rest_framework.response import Response from rest_framework.generics import CreateAPIView -from common.permissions import IsAppUser from common.utils import reverse, lazyproperty from orgs.utils import tmp_to_org from tickets.api import GenericTicketStatusRetrieveCloseAPI @@ -12,8 +11,8 @@ __all__ = ['LoginAssetCheckAPI', 'LoginAssetConfirmStatusAPI'] class LoginAssetCheckAPI(CreateAPIView): - permission_classes = (IsAppUser,) serializer_class = serializers.LoginAssetCheckSerializer + model = LoginAssetACL def create(self, request, *args, **kwargs): is_need_confirm, response_data = self.check_if_need_confirm() diff --git a/apps/acls/apps.py b/apps/acls/apps.py index 2456a1b4f..0a93fe495 100644 --- a/apps/acls/apps.py +++ b/apps/acls/apps.py @@ -1,5 +1,7 @@ from django.apps import AppConfig +from django.utils.translation import ugettext_lazy as _ class AclsConfig(AppConfig): name = 'acls' + verbose_name = _('Acls') diff --git a/apps/acls/migrations/0002_auto_20211130_1037.py b/apps/acls/migrations/0002_auto_20211130_1037.py new file mode 100644 index 000000000..903a9685e --- /dev/null +++ b/apps/acls/migrations/0002_auto_20211130_1037.py @@ -0,0 +1,21 @@ +# Generated by Django 3.1.13 on 2021-11-30 02:37 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('acls', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='loginacl', + options={'ordering': ('priority', '-date_updated', 'name'), 'verbose_name': 'Login acl'}, + ), + migrations.AlterModelOptions( + name='loginassetacl', + options={'ordering': ('priority', '-date_updated', 'name'), 'verbose_name': 'Login asset acl'}, + ), + ] diff --git a/apps/applications/api/account.py b/apps/applications/api/account.py index 4fc06807e..def6292c1 100644 --- a/apps/applications/api/account.py +++ b/apps/applications/api/account.py @@ -6,8 +6,9 @@ from django.db.models import F, Q from common.drf.filters import BaseFilterSet from common.drf.api import JMSBulkModelViewSet +from rbac.permissions import RBACPermission from ..models import Account -from ..hands import IsOrgAdminOrAppUser, IsOrgAdmin, NeedMFAVerify +from ..hands import NeedMFAVerify from .. import serializers @@ -31,7 +32,8 @@ class AccountFilterSet(BaseFilterSet): username = self.get_query_param('username') if not username: return qs - qs = qs.filter(Q(username=username) | Q(systemuser__username=username)).distinct() + q = Q(username=username) | Q(systemuser__username=username) + qs = qs.filter(q).distinct() return qs @@ -41,7 +43,6 @@ class ApplicationAccountViewSet(JMSBulkModelViewSet): filterset_class = AccountFilterSet filterset_fields = ['username', 'app_display', 'type', 'category', 'app'] serializer_class = serializers.AppAccountSerializer - permission_classes = (IsOrgAdmin,) def get_queryset(self): queryset = Account.get_queryset() @@ -50,5 +51,9 @@ class ApplicationAccountViewSet(JMSBulkModelViewSet): class ApplicationAccountSecretViewSet(ApplicationAccountViewSet): serializer_class = serializers.AppAccountSecretSerializer - permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify] + permission_classes = [RBACPermission, NeedMFAVerify] http_method_names = ['get', 'options'] + rbac_perms = { + 'retrieve': 'view_applicationaccountsecret', + 'list': 'view_applicationaccountsecret', + } diff --git a/apps/applications/api/application.py b/apps/applications/api/application.py index ef00fe8c1..1d79e67d1 100644 --- a/apps/applications/api/application.py +++ b/apps/applications/api/application.py @@ -6,8 +6,7 @@ from rest_framework.decorators import action from rest_framework.response import Response from common.tree import TreeNodeSerializer -from common.mixins.api import SuggestionMixin -from ..hands import IsOrgAdminOrAppUser +from common.mixins.views import SuggestionMixin from .. import serializers from ..models import Application @@ -22,12 +21,14 @@ class ApplicationViewSet(SuggestionMixin, OrgBulkModelViewSet): 'type': ['exact', 'in'], } search_fields = ('name', 'type', 'category') - permission_classes = (IsOrgAdminOrAppUser,) serializer_classes = { 'default': serializers.AppSerializer, 'get_tree': TreeNodeSerializer, 'suggestion': serializers.MiniAppSerializer } + rbac_perms = { + 'get_tree': 'applications.view_application' + } @action(methods=['GET'], detail=False, url_path='tree') def get_tree(self, request, *args, **kwargs): diff --git a/apps/applications/api/remote_app.py b/apps/applications/api/remote_app.py index c68679bab..4aea7d93b 100644 --- a/apps/applications/api/remote_app.py +++ b/apps/applications/api/remote_app.py @@ -2,10 +2,8 @@ # from orgs.mixins import generics -from ..hands import IsAppUser from .. import models from ..serializers import RemoteAppConnectionInfoSerializer -from ..permissions import IsRemoteApp __all__ = [ @@ -15,5 +13,4 @@ __all__ = [ class RemoteAppConnectionInfoApi(generics.RetrieveAPIView): model = models.Application - permission_classes = (IsAppUser, IsRemoteApp) serializer_class = RemoteAppConnectionInfoSerializer diff --git a/apps/applications/apps.py b/apps/applications/apps.py index 3c22ddedc..1662edcf0 100644 --- a/apps/applications/apps.py +++ b/apps/applications/apps.py @@ -1,7 +1,9 @@ from __future__ import unicode_literals +from django.utils.translation import ugettext_lazy as _ from django.apps import AppConfig class ApplicationsConfig(AppConfig): name = 'applications' + verbose_name = _('Applications') diff --git a/apps/applications/hands.py b/apps/applications/hands.py index 4987d5948..74d6bf9cb 100644 --- a/apps/applications/hands.py +++ b/apps/applications/hands.py @@ -11,5 +11,5 @@ """ -from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser, NeedMFAVerify +from common.permissions import NeedMFAVerify from users.models import User, UserGroup diff --git a/apps/applications/migrations/0012_auto_20211130_1037.py b/apps/applications/migrations/0012_auto_20211130_1037.py new file mode 100644 index 000000000..f6fd8b925 --- /dev/null +++ b/apps/applications/migrations/0012_auto_20211130_1037.py @@ -0,0 +1,29 @@ +# Generated by Django 3.1.13 on 2021-11-30 02:37 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('applications', '0011_auto_20210826_1759'), + ] + + operations = [ + migrations.AlterModelOptions( + name='account', + options={'verbose_name': 'Application account'}, + ), + migrations.AlterModelOptions( + name='application', + options={'ordering': ('name',), 'verbose_name': 'Application'}, + ), + migrations.AlterModelOptions( + name='applicationuser', + options={'verbose_name': 'Application user'}, + ), + migrations.AlterModelOptions( + name='historicalaccount', + options={'get_latest_by': 'history_date', 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical Application account'}, + ), + ] diff --git a/apps/applications/migrations/0013_auto_20220211_1401.py b/apps/applications/migrations/0013_auto_20220211_1401.py new file mode 100644 index 000000000..bbbc3a677 --- /dev/null +++ b/apps/applications/migrations/0013_auto_20220211_1401.py @@ -0,0 +1,17 @@ +# Generated by Django 3.1.12 on 2022-02-11 06:01 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('applications', '0012_auto_20211130_1037'), + ] + + operations = [ + migrations.AlterModelOptions( + name='account', + options={'permissions': [('view_applicationaccountsecret', 'Can view application account secret'), ('change_appplicationaccountsecret', 'Can view application account secret')], 'verbose_name': 'Application account'}, + ), + ] diff --git a/apps/applications/models/account.py b/apps/applications/models/account.py index 1b0da7979..eac7b99ef 100644 --- a/apps/applications/models/account.py +++ b/apps/applications/models/account.py @@ -20,8 +20,12 @@ class Account(BaseUser): auth_attrs = ['username', 'password', 'private_key', 'public_key'] class Meta: - verbose_name = _('Account') + verbose_name = _('Application account') unique_together = [('username', 'app', 'systemuser')] + permissions = [ + ('view_applicationaccountsecret', _('Can view application account secret')), + ('change_appplicationaccountsecret', _('Can view application account secret')), + ] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/apps/applications/models/application.py b/apps/applications/models/application.py index 725736aeb..17f17c13a 100644 --- a/apps/applications/models/application.py +++ b/apps/applications/models/application.py @@ -219,6 +219,7 @@ class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin): verbose_name = _('Application') unique_together = [('org_id', 'name')] ordering = ('name',) + verbose_name = _("Application") def __str__(self): category_display = self.get_category_display() @@ -265,3 +266,4 @@ class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin): class ApplicationUser(SystemUser): class Meta: proxy = True + verbose_name = _('Application user') diff --git a/apps/assets/api/accounts.py b/apps/assets/api/accounts.py index d4b08b380..1db1bb090 100644 --- a/apps/assets/api/accounts.py +++ b/apps/assets/api/accounts.py @@ -6,7 +6,6 @@ from django.shortcuts import get_object_or_404 from rest_framework.generics import CreateAPIView from orgs.mixins.api import OrgBulkModelViewSet -from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser, NeedMFAVerify from common.drf.filters import BaseFilterSet from ..tasks.account_connectivity import test_accounts_connectivity_manual from ..models import AuthBook, Node @@ -62,7 +61,6 @@ class AccountViewSet(OrgBulkModelViewSet): 'default': serializers.AccountSerializer, 'verify_account': serializers.AssetTaskSerializer } - permission_classes = (IsOrgAdmin,) def get_queryset(self): queryset = AuthBook.get_queryset() @@ -82,17 +80,22 @@ class AccountSecretsViewSet(AccountViewSet): serializer_classes = { 'default': serializers.AccountSecretSerializer } - permission_classes = (IsOrgAdmin, NeedMFAVerify) http_method_names = ['get'] + rbac_perms = { + 'list': 'assets.view_assetsecret', + 'retrieve': 'assets.view_assetsecret', + } class AccountTaskCreateAPI(CreateAPIView): - permission_classes = (IsOrgAdminOrAppUser,) serializer_class = serializers.AccountTaskSerializer filterset_fields = AccountViewSet.filterset_fields search_fields = AccountViewSet.search_fields filterset_class = AccountViewSet.filterset_class + def check_permissions(self, request): + return request.user.has_perm('assets.test_assetconnectivity') + def get_accounts(self): queryset = AuthBook.objects.all() queryset = self.filter_queryset(queryset) @@ -109,5 +112,4 @@ class AccountTaskCreateAPI(CreateAPIView): def get_exception_handler(self): def handler(e, context): return Response({"error": str(e)}, status=400) - return handler diff --git a/apps/assets/api/admin_user.py b/apps/assets/api/admin_user.py index e7f1598cd..1192599b9 100644 --- a/apps/assets/api/admin_user.py +++ b/apps/assets/api/admin_user.py @@ -2,9 +2,9 @@ from django.db.models import Count from orgs.mixins.api import OrgBulkModelViewSet from common.utils import get_logger -from ..hands import IsOrgAdmin from ..models import SystemUser from .. import serializers +from rbac.permissions import RBACPermission logger = get_logger(__file__) @@ -20,7 +20,7 @@ class AdminUserViewSet(OrgBulkModelViewSet): filterset_fields = ("name", "username") search_fields = filterset_fields serializer_class = serializers.AdminUserSerializer - permission_classes = (IsOrgAdmin,) + permission_classes = (RBACPermission,) ordering_fields = ('name',) ordering = ('name', ) diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index 5b4ef7bb3..40bbef952 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -1,13 +1,11 @@ # -*- coding: utf-8 -*- # -from assets.api import FilterAssetByNodeMixin from rest_framework.viewsets import ModelViewSet from rest_framework.generics import RetrieveAPIView, ListAPIView from django.shortcuts import get_object_or_404 from django.db.models import Q from common.utils import get_logger, get_object_or_none -from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser, IsSuperUser from common.mixins.api import SuggestionMixin from users.models import User, UserGroup from users.serializers import UserSerializer, UserGroupSerializer @@ -17,6 +15,7 @@ from perms.serializers import AssetPermissionSerializer from perms.filters import AssetPermissionFilter from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins import generics +from assets.api import FilterAssetByNodeMixin from ..models import Asset, Node, Platform from .. import serializers from ..tasks import ( @@ -55,7 +54,6 @@ class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet) 'default': serializers.AssetSerializer, 'suggestion': serializers.MiniAssetSerializer } - permission_classes = (IsOrgAdminOrAppUser,) extra_filter_backends = [FilterAssetByNodeFilterBackend, LabelFilterBackend, IpInFilterBackend] def set_assets_node(self, assets): @@ -76,8 +74,10 @@ class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet) class AssetPlatformRetrieveApi(RetrieveAPIView): queryset = Platform.objects.all() - permission_classes = (IsOrgAdminOrAppUser,) serializer_class = serializers.PlatformSerializer + rbac_perms = { + 'retrieve': 'assets.view_gateway' + } def get_object(self): asset_pk = self.kwargs.get('pk') @@ -87,16 +87,10 @@ class AssetPlatformRetrieveApi(RetrieveAPIView): class AssetPlatformViewSet(ModelViewSet): queryset = Platform.objects.all() - permission_classes = (IsSuperUser,) serializer_class = serializers.PlatformSerializer filterset_fields = ['name', 'base'] search_fields = ['name'] - def get_permissions(self): - if self.request.method.lower() in ['get', 'options']: - self.permission_classes = (IsOrgAdmin,) - return super().get_permissions() - def check_object_permissions(self, request, obj): if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal: self.permission_denied( @@ -131,7 +125,6 @@ class AssetsTaskMixin: class AssetTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView): model = Asset serializer_class = serializers.AssetTaskSerializer - permission_classes = (IsOrgAdmin,) def create(self, request, *args, **kwargs): pk = self.kwargs.get('pk') @@ -139,11 +132,24 @@ class AssetTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView): request.data['assets'] = [pk] return super().create(request, *args, **kwargs) + def check_permissions(self, request): + action = request.data.get('action') + action_perm_require = { + 'push_system_user': 'assets.push_assetsystemuser', + 'test_system_user': 'assets.test_assetconnectivity' + } + perm_required = action_perm_require.get(action) + has = self.request.user.has_perm(perm_required) + + if not has: + self.permission_denied(request) + def perform_asset_task(self, serializer): data = serializer.validated_data action = data['action'] if action not in ['push_system_user', 'test_system_user']: return + asset = data['asset'] system_users = data.get('system_users') if not system_users: @@ -166,12 +172,13 @@ class AssetTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView): class AssetsTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView): model = Asset serializer_class = serializers.AssetsTaskSerializer - permission_classes = (IsOrgAdmin,) class AssetGatewayListApi(generics.ListAPIView): - permission_classes = (IsOrgAdminOrAppUser,) serializer_class = serializers.GatewayWithAuthSerializer + rbac_perms = { + 'list': 'assets.view_gateway' + } def get_queryset(self): asset_id = self.kwargs.get('pk') @@ -183,7 +190,6 @@ class AssetGatewayListApi(generics.ListAPIView): class BaseAssetPermUserOrUserGroupListApi(ListAPIView): - permission_classes = (IsOrgAdmin,) def get_object(self): asset_id = self.kwargs.get('pk') @@ -220,11 +226,13 @@ class AssetPermUserGroupListApi(BaseAssetPermUserOrUserGroupListApi): class BaseAssetPermUserOrUserGroupPermissionsListApiMixin(generics.ListAPIView): - permission_classes = (IsOrgAdmin,) model = AssetPermission serializer_class = AssetPermissionSerializer filterset_class = AssetPermissionFilter search_fields = ('name',) + rbac_perms = { + 'list': 'perms.view_assetpermission' + } def get_object(self): asset_id = self.kwargs.get('pk') diff --git a/apps/assets/api/backup.py b/apps/assets/api/backup.py index 4c0aaf18f..d507b4655 100644 --- a/apps/assets/api/backup.py +++ b/apps/assets/api/backup.py @@ -1,11 +1,9 @@ # -*- coding: utf-8 -*- # -from rest_framework import status, mixins, viewsets +from rest_framework import status, viewsets from rest_framework.response import Response -from common.permissions import IsOrgAdmin from orgs.mixins.api import OrgBulkModelViewSet - from .. import serializers from ..tasks import execute_account_backup_plan from ..models import ( @@ -24,17 +22,13 @@ class AccountBackupPlanViewSet(OrgBulkModelViewSet): ordering_fields = ('name',) ordering = ('name',) serializer_class = serializers.AccountBackupPlanSerializer - permission_classes = (IsOrgAdmin,) -class AccountBackupPlanExecutionViewSet( - mixins.CreateModelMixin, mixins.ListModelMixin, - mixins.RetrieveModelMixin, viewsets.GenericViewSet -): +class AccountBackupPlanExecutionViewSet(viewsets.ModelViewSet): serializer_class = serializers.AccountBackupPlanExecutionSerializer search_fields = ('trigger',) filterset_fields = ('trigger', 'plan_id') - permission_classes = (IsOrgAdmin,) + http_method_names = ['get', 'post'] def get_queryset(self): queryset = AccountBackupPlanExecution.objects.all() diff --git a/apps/assets/api/cmd_filter.py b/apps/assets/api/cmd_filter.py index 1c6f19cbb..7db425ab9 100644 --- a/apps/assets/api/cmd_filter.py +++ b/apps/assets/api/cmd_filter.py @@ -9,7 +9,6 @@ from common.utils import reverse from common.utils import lazyproperty from orgs.mixins.api import OrgBulkModelViewSet from tickets.api import GenericTicketStatusRetrieveCloseAPI -from ..hands import IsOrgAdmin, IsAppUser from ..models import CommandFilter, CommandFilterRule from .. import serializers @@ -23,7 +22,6 @@ class CommandFilterViewSet(OrgBulkModelViewSet): model = CommandFilter filterset_fields = ("name",) search_fields = filterset_fields - permission_classes = (IsOrgAdmin,) serializer_class = serializers.CommandFilterSerializer @@ -31,7 +29,6 @@ class CommandFilterRuleViewSet(OrgBulkModelViewSet): model = CommandFilterRule filterset_fields = ('content',) search_fields = filterset_fields - permission_classes = (IsOrgAdmin,) serializer_class = serializers.CommandFilterRuleSerializer def get_queryset(self): @@ -43,8 +40,10 @@ class CommandFilterRuleViewSet(OrgBulkModelViewSet): class CommandConfirmAPI(CreateAPIView): - permission_classes = (IsAppUser,) serializer_class = serializers.CommandConfirmSerializer + rbac_perms = { + 'create': 'tickets.add_ticket' + } def create(self, request, *args, **kwargs): ticket = self.create_command_confirm_ticket() diff --git a/apps/assets/api/domain.py b/apps/assets/api/domain.py index fe3255103..b37d895f2 100644 --- a/apps/assets/api/domain.py +++ b/apps/assets/api/domain.py @@ -6,7 +6,6 @@ from rest_framework.views import APIView, Response from rest_framework.serializers import ValidationError from common.utils import get_logger -from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser from orgs.mixins.api import OrgBulkModelViewSet from ..models import Domain, Gateway from .. import serializers @@ -20,7 +19,6 @@ class DomainViewSet(OrgBulkModelViewSet): model = Domain filterset_fields = ("name", ) search_fields = filterset_fields - permission_classes = (IsOrgAdminOrAppUser,) serializer_class = serializers.DomainSerializer ordering_fields = ('name',) ordering = ('name', ) @@ -35,13 +33,14 @@ class GatewayViewSet(OrgBulkModelViewSet): model = Gateway filterset_fields = ("domain__name", "name", "username", "ip", "domain") search_fields = ("domain__name", "name", "username", "ip") - permission_classes = (IsOrgAdminOrAppUser,) serializer_class = serializers.GatewaySerializer class GatewayTestConnectionApi(SingleObjectMixin, APIView): - permission_classes = (IsOrgAdmin,) object = None + rbac_perms = { + 'POST': 'assets.change_gateway' + } def post(self, request, *args, **kwargs): self.object = self.get_object(Gateway.objects.all()) diff --git a/apps/assets/api/gathered_user.py b/apps/assets/api/gathered_user.py index 959259799..22be7daf7 100644 --- a/apps/assets/api/gathered_user.py +++ b/apps/assets/api/gathered_user.py @@ -3,7 +3,6 @@ from orgs.mixins.api import OrgModelViewSet from assets.models import GatheredUser -from common.permissions import IsOrgAdmin from ..serializers import GatheredUserSerializer from ..filters import AssetRelatedByNodeFilterBackend @@ -15,7 +14,6 @@ __all__ = ['GatheredUserViewSet'] class GatheredUserViewSet(OrgModelViewSet): model = GatheredUser serializer_class = GatheredUserSerializer - permission_classes = [IsOrgAdmin] extra_filter_backends = [AssetRelatedByNodeFilterBackend] filterset_fields = ['asset', 'username', 'present', 'asset__ip', 'asset__hostname', 'asset_id'] diff --git a/apps/assets/api/label.py b/apps/assets/api/label.py index 06dff8b8a..cbb7b5bb3 100644 --- a/apps/assets/api/label.py +++ b/apps/assets/api/label.py @@ -17,7 +17,6 @@ from django.db.models import Count from common.utils import get_logger from orgs.mixins.api import OrgBulkModelViewSet -from ..hands import IsOrgAdmin from ..models import Label from .. import serializers @@ -30,7 +29,6 @@ class LabelViewSet(OrgBulkModelViewSet): model = Label filterset_fields = ("name", "value") search_fields = filterset_fields - permission_classes = (IsOrgAdmin,) serializer_class = serializers.LabelSerializer def list(self, request, *args, **kwargs): diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py index c7f32b306..f32c050f9 100644 --- a/apps/assets/api/node.py +++ b/apps/assets/api/node.py @@ -20,7 +20,6 @@ from common.tree import TreeNodeSerializer from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins import generics from orgs.utils import current_org -from ..hands import IsOrgAdmin from ..models import Node from ..tasks import ( update_node_assets_hardware_info_manual, @@ -46,7 +45,6 @@ class NodeViewSet(SuggestionMixin, OrgBulkModelViewSet): model = Node filterset_fields = ('value', 'key', 'id') search_fields = ('value', ) - permission_classes = (IsOrgAdmin,) serializer_class = serializers.NodeSerializer @action(methods=[POST], detail=False, url_path='check_assets_amount_task') @@ -85,7 +83,6 @@ class NodeListAsTreeApi(generics.ListAPIView): ] """ model = Node - permission_classes = (IsOrgAdmin,) serializer_class = TreeNodeSerializer @staticmethod @@ -100,7 +97,6 @@ class NodeListAsTreeApi(generics.ListAPIView): class NodeChildrenApi(generics.ListCreateAPIView): - permission_classes = (IsOrgAdmin,) serializer_class = serializers.NodeSerializer instance = None is_initial = False @@ -199,7 +195,6 @@ class NodeChildrenAsTreeApi(SerializeToTreeNodeMixin, NodeChildrenApi): class NodeAssetsApi(generics.ListAPIView): - permission_classes = (IsOrgAdmin,) serializer_class = serializers.AssetSerializer def get_queryset(self): @@ -214,7 +209,6 @@ class NodeAssetsApi(generics.ListAPIView): class NodeAddChildrenApi(generics.UpdateAPIView): model = Node - permission_classes = (IsOrgAdmin,) serializer_class = serializers.NodeAddChildrenSerializer instance = None @@ -231,7 +225,6 @@ class NodeAddChildrenApi(generics.UpdateAPIView): class NodeAddAssetsApi(generics.UpdateAPIView): model = Node serializer_class = serializers.NodeAssetsSerializer - permission_classes = (IsOrgAdmin,) instance = None def perform_update(self, serializer): @@ -243,7 +236,6 @@ class NodeAddAssetsApi(generics.UpdateAPIView): class NodeRemoveAssetsApi(generics.UpdateAPIView): model = Node serializer_class = serializers.NodeAssetsSerializer - permission_classes = (IsOrgAdmin,) instance = None def perform_update(self, serializer): @@ -262,7 +254,6 @@ class NodeRemoveAssetsApi(generics.UpdateAPIView): class MoveAssetsToNodeApi(generics.UpdateAPIView): model = Node serializer_class = serializers.NodeAssetsSerializer - permission_classes = (IsOrgAdmin,) instance = None def perform_update(self, serializer): @@ -305,8 +296,6 @@ class MoveAssetsToNodeApi(generics.UpdateAPIView): class NodeTaskCreateApi(generics.CreateAPIView): model = Node serializer_class = serializers.NodeTaskSerializer - permission_classes = (IsOrgAdmin,) - def get_object(self): node_id = self.kwargs.get('pk') node = get_object_or_none(self.model, id=node_id) diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py index 673a9f34d..b409b6cd0 100644 --- a/apps/assets/api/system_user.py +++ b/apps/assets/api/system_user.py @@ -1,16 +1,15 @@ # ~*~ coding: utf-8 ~*~ from django.shortcuts import get_object_or_404 -from django.middleware import csrf from rest_framework.response import Response +from rest_framework.decorators import action from common.utils import get_logger, get_object_or_none from common.utils.crypto import get_aes_crypto -from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser, IsValidUser +from common.permissions import IsValidUser +from common.mixins.api import SuggestionMixin from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins import generics -from common.mixins.api import SuggestionMixin from orgs.utils import tmp_to_root_org -from rest_framework.decorators import action from ..models import SystemUser, CommandFilterRule from .. import serializers from ..serializers import SystemUserWithAuthInfoSerializer, SystemUserTempAuthSerializer @@ -46,7 +45,6 @@ class SystemUserViewSet(SuggestionMixin, OrgBulkModelViewSet): } ordering_fields = ('name', 'protocol', 'login_mode') ordering = ('name', ) - permission_classes = (IsOrgAdminOrAppUser,) @action(methods=['get'], detail=False, url_path='su-from') def su_from(self, request, *args, **kwargs): @@ -80,8 +78,13 @@ class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView): Get system user auth info """ model = SystemUser - permission_classes = (IsOrgAdminOrAppUser,) serializer_class = SystemUserWithAuthInfoSerializer + rbac_perms = { + 'retrieve': 'assets.view_systemusersecret', + 'list': 'assets.view_systemusersecret', + 'change': 'assets.change_systemuser', + 'destroy': 'assets.change_systemuser', + } def destroy(self, request, *args, **kwargs): instance = self.get_object() @@ -123,7 +126,6 @@ class SystemUserAssetAuthInfoApi(generics.RetrieveAPIView): Get system user with asset auth info """ model = SystemUser - permission_classes = (IsOrgAdminOrAppUser,) serializer_class = SystemUserWithAuthInfoSerializer def get_object(self): @@ -140,8 +142,10 @@ class SystemUserAppAuthInfoApi(generics.RetrieveAPIView): Get system user with asset auth info """ model = SystemUser - permission_classes = (IsOrgAdminOrAppUser,) serializer_class = SystemUserWithAuthInfoSerializer + rbac_perms = { + 'retrieve': 'assets.view_systemusersecret', + } def get_object(self): instance = super().get_object() @@ -153,7 +157,6 @@ class SystemUserAppAuthInfoApi(generics.RetrieveAPIView): class SystemUserTaskApi(generics.CreateAPIView): - permission_classes = (IsOrgAdmin,) serializer_class = serializers.SystemUserTaskSerializer def do_push(self, system_user, asset_ids=None): @@ -175,6 +178,18 @@ class SystemUserTaskApi(generics.CreateAPIView): pk = self.kwargs.get('pk') return get_object_or_404(SystemUser, pk=pk) + def check_permissions(self, request): + action = request.data.get('action') + action_perm_require = { + 'push': 'assets.push_systemuser', + 'test': 'assets.test_connectivity' + } + perm_required = action_perm_require.get(action) + has = self.request.user.has_perm(perm_required) + + if not has: + self.permission_denied(request) + def perform_create(self, serializer): action = serializer.validated_data["action"] asset = serializer.validated_data.get('asset') @@ -198,7 +213,9 @@ class SystemUserTaskApi(generics.CreateAPIView): class SystemUserCommandFilterRuleListApi(generics.ListAPIView): - permission_classes = (IsOrgAdminOrAppUser,) + rbac_perms = { + 'list': 'assets.view_commandfilterule' + } def get_serializer_class(self): from ..serializers import CommandFilterRuleSerializer @@ -224,10 +241,12 @@ class SystemUserCommandFilterRuleListApi(generics.ListAPIView): class SystemUserAssetsListView(generics.ListAPIView): - permission_classes = (IsOrgAdmin,) serializer_class = serializers.AssetSimpleSerializer filterset_fields = ("hostname", "ip") search_fields = filterset_fields + rbac_perms = { + 'list': 'assets.view_asset' + } def get_object(self): pk = self.kwargs.get('pk') diff --git a/apps/assets/api/system_user_relation.py b/apps/assets/api/system_user_relation.py index 374d45cc2..056bd1e53 100644 --- a/apps/assets/api/system_user_relation.py +++ b/apps/assets/api/system_user_relation.py @@ -5,7 +5,6 @@ from django.db.models import F, Value, Model from django.db.models.signals import m2m_changed from django.db.models.functions import Concat -from common.permissions import IsOrgAdmin from common.utils import get_logger from orgs.mixins.api import OrgBulkModelViewSet from orgs.utils import current_org @@ -71,7 +70,6 @@ class BaseRelationViewSet(RelationMixin, OrgBulkModelViewSet): class SystemUserAssetRelationViewSet(BaseRelationViewSet): serializer_class = serializers.SystemUserAssetRelationSerializer model = models.SystemUser.assets.through - permission_classes = (IsOrgAdmin,) filterset_fields = [ 'id', 'asset', 'systemuser', ] @@ -97,7 +95,6 @@ class SystemUserAssetRelationViewSet(BaseRelationViewSet): class SystemUserNodeRelationViewSet(BaseRelationViewSet): serializer_class = serializers.SystemUserNodeRelationSerializer model = models.SystemUser.nodes.through - permission_classes = (IsOrgAdmin,) filterset_fields = [ 'id', 'node', 'systemuser', ] @@ -118,7 +115,6 @@ class SystemUserNodeRelationViewSet(BaseRelationViewSet): class SystemUserUserRelationViewSet(BaseRelationViewSet): serializer_class = serializers.SystemUserUserRelationSerializer model = models.SystemUser.users.through - permission_classes = (IsOrgAdmin,) filterset_fields = [ 'id', 'user', 'systemuser', ] diff --git a/apps/assets/apps.py b/apps/assets/apps.py index 04ed9fded..5b44e2e92 100644 --- a/apps/assets/apps.py +++ b/apps/assets/apps.py @@ -1,10 +1,15 @@ from __future__ import unicode_literals +from django.utils.translation import ugettext_lazy as _ from django.apps import AppConfig class AssetsConfig(AppConfig): name = 'assets' + verbose_name = _('Assets') + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) def ready(self): super().ready() diff --git a/apps/assets/hands.py b/apps/assets/hands.py index ee13e589e..c2bf4fd20 100644 --- a/apps/assets/hands.py +++ b/apps/assets/hands.py @@ -11,5 +11,4 @@ """ -from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser from users.models import User, UserGroup diff --git a/apps/assets/migrations/0077_auto_20211130_1037.py b/apps/assets/migrations/0077_auto_20211130_1037.py new file mode 100644 index 000000000..49cf2d2be --- /dev/null +++ b/apps/assets/migrations/0077_auto_20211130_1037.py @@ -0,0 +1,17 @@ +# Generated by Django 3.1.13 on 2021-11-30 02:37 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0076_delete_assetuser'), + ] + + operations = [ + migrations.AlterModelOptions( + name='label', + options={'verbose_name': 'Label'}, + ), + ] diff --git a/apps/assets/migrations/0078_auto_20220211_1401.py b/apps/assets/migrations/0078_auto_20220211_1401.py new file mode 100644 index 000000000..d1b234b2a --- /dev/null +++ b/apps/assets/migrations/0078_auto_20220211_1401.py @@ -0,0 +1,21 @@ +# Generated by Django 3.1.12 on 2022-02-11 06:01 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0077_auto_20211130_1037'), + ] + + operations = [ + migrations.AlterModelOptions( + name='asset', + options={'ordering': ['hostname'], 'permissions': [('test_assetconnectivity', 'Can test asset connectivity'), ('push_assetsystemuser', 'Can push system user to asset')], 'verbose_name': 'Asset'}, + ), + migrations.AlterModelOptions( + name='authbook', + options={'permissions': [('view_assetaccountsecret', 'Can view asset account secret'), ('change_assetaccountsecret', 'Can change asset account secret')], 'verbose_name': 'AuthBook'}, + ), + ] diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index aaabab1b7..e2de48aa6 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -355,3 +355,7 @@ class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin unique_together = [('org_id', 'hostname')] verbose_name = _("Asset") ordering = ["hostname", ] + permissions = [ + ('test_assetconnectivity', 'Can test asset connectivity'), + ('push_assetsystemuser', 'Can push system user to asset'), + ] diff --git a/apps/assets/models/authbook.py b/apps/assets/models/authbook.py index c489ab608..c3164faba 100644 --- a/apps/assets/models/authbook.py +++ b/apps/assets/models/authbook.py @@ -26,6 +26,10 @@ class AuthBook(BaseUser, AbsConnectivity): class Meta: verbose_name = _('AuthBook') unique_together = [('username', 'asset', 'systemuser')] + permissions = [ + ('view_assetaccountsecret', _('Can view asset account secret')), + ('change_assetaccountsecret', _('Can change asset account secret')) + ] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/apps/assets/models/label.py b/apps/assets/models/label.py index c81726425..f7820ccb1 100644 --- a/apps/assets/models/label.py +++ b/apps/assets/models/label.py @@ -37,3 +37,4 @@ class Label(OrgModelMixin): class Meta: db_table = "assets_label" unique_together = [('name', 'value', 'org_id')] + verbose_name = _('Label') diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index a888b404b..d22f4ea10 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -325,7 +325,7 @@ class SystemUser(ProtocolMixin, AuthMixin, BaseUser): verbose_name = _("System user") -# Todo: 准备废弃 +# Deprecated: 准备废弃 class AdminUser(BaseUser): """ A privileged user that ansible can use it to push system user and so on diff --git a/apps/audits/api.py b/apps/audits/api.py index 2130c8333..3da41d463 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -4,7 +4,6 @@ from rest_framework.mixins import ListModelMixin, CreateModelMixin from django.db.models import F, Value from django.db.models.functions import Concat -from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor, IsOrgAdmin from common.drf.filters import DatetimeRangeFilter from common.api import CommonGenericViewSet from orgs.mixins.api import OrgGenericViewSet, OrgBulkModelViewSet, OrgRelationMixin @@ -20,7 +19,6 @@ class FTPLogViewSet(CreateModelMixin, OrgGenericViewSet): model = FTPLog serializer_class = FTPLogSerializer - permission_classes = (IsOrgAdminOrAppUser | IsOrgAuditor,) extra_filter_backends = [DatetimeRangeFilter] date_range_filter_fields = [ ('date_start', ('date_from', 'date_to')) @@ -32,7 +30,6 @@ class FTPLogViewSet(CreateModelMixin, class UserLoginLogViewSet(ListModelMixin, CommonGenericViewSet): queryset = UserLoginLog.objects.all() - permission_classes = [IsOrgAdmin | IsOrgAuditor] serializer_class = UserLoginLogSerializer extra_filter_backends = [DatetimeRangeFilter] date_range_filter_fields = [ @@ -58,7 +55,6 @@ class UserLoginLogViewSet(ListModelMixin, CommonGenericViewSet): class OperateLogViewSet(ListModelMixin, OrgGenericViewSet): model = OperateLog serializer_class = OperateLogSerializer - permission_classes = [IsOrgAdmin | IsOrgAuditor] extra_filter_backends = [DatetimeRangeFilter] date_range_filter_fields = [ ('datetime', ('date_from', 'date_to')) @@ -70,7 +66,6 @@ class OperateLogViewSet(ListModelMixin, OrgGenericViewSet): class PasswordChangeLogViewSet(ListModelMixin, CommonGenericViewSet): queryset = PasswordChangeLog.objects.all() - permission_classes = [IsOrgAdmin | IsOrgAuditor] serializer_class = PasswordChangeLogSerializer extra_filter_backends = [DatetimeRangeFilter] date_range_filter_fields = [ @@ -91,7 +86,6 @@ class PasswordChangeLogViewSet(ListModelMixin, CommonGenericViewSet): class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet): model = CommandExecution serializer_class = CommandExecutionSerializer - permission_classes = [IsOrgAdmin | IsOrgAuditor] extra_filter_backends = [DatetimeRangeFilter] date_range_filter_fields = [ ('date_start', ('date_from', 'date_to')) @@ -117,7 +111,6 @@ class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet): class CommandExecutionHostRelationViewSet(OrgRelationMixin, OrgBulkModelViewSet): serializer_class = CommandExecutionHostsRelationSerializer m2m_field = CommandExecution.hosts.field - permission_classes = [IsOrgAdmin | IsOrgAuditor] filterset_fields = [ 'id', 'asset', 'commandexecution' ] diff --git a/apps/audits/apps.py b/apps/audits/apps.py index ba010cd6c..e66faa467 100644 --- a/apps/audits/apps.py +++ b/apps/audits/apps.py @@ -1,10 +1,12 @@ from django.apps import AppConfig from django.conf import settings +from django.utils.translation import ugettext_lazy as _ from django.db.models.signals import post_save class AuditsConfig(AppConfig): name = 'audits' + verbose_name = _('Audits') def ready(self): from . import signals_handler diff --git a/apps/audits/migrations/0013_auto_20211130_1037.py b/apps/audits/migrations/0013_auto_20211130_1037.py new file mode 100644 index 000000000..15fa2a382 --- /dev/null +++ b/apps/audits/migrations/0013_auto_20211130_1037.py @@ -0,0 +1,29 @@ +# Generated by Django 3.1.13 on 2021-11-30 02:37 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('audits', '0012_auto_20210414_1443'), + ] + + operations = [ + migrations.AlterModelOptions( + name='ftplog', + options={'verbose_name': 'File transfer log'}, + ), + migrations.AlterModelOptions( + name='operatelog', + options={'verbose_name': 'Operate log'}, + ), + migrations.AlterModelOptions( + name='passwordchangelog', + options={'verbose_name': 'Password change log'}, + ), + migrations.AlterModelOptions( + name='userloginlog', + options={'ordering': ['-datetime', 'username'], 'verbose_name': 'User login log'}, + ), + ] diff --git a/apps/audits/models.py b/apps/audits/models.py index f40dce16c..0cd17ede2 100644 --- a/apps/audits/models.py +++ b/apps/audits/models.py @@ -43,6 +43,9 @@ class FTPLog(OrgModelMixin): is_success = models.BooleanField(default=True, verbose_name=_("Success")) date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Date start')) + class Meta: + verbose_name = _("File transfer log") + class OperateLog(OrgModelMixin): ACTION_CREATE = 'create' @@ -73,6 +76,9 @@ class OperateLog(OrgModelMixin): self.org_id = Organization.ROOT_ID return super(OperateLog, self).save(*args, **kwargs) + class Meta: + verbose_name = _("Operate log") + class PasswordChangeLog(models.Model): id = models.UUIDField(default=uuid.uuid4, primary_key=True) @@ -84,6 +90,9 @@ class PasswordChangeLog(models.Model): def __str__(self): return "{} change {}'s password".format(self.change_by, self.user) + class Meta: + verbose_name = _('Password change log') + class UserLoginLog(models.Model): LOGIN_TYPE_CHOICE = ( @@ -155,3 +164,4 @@ class UserLoginLog(models.Model): class Meta: ordering = ['-datetime', 'username'] + verbose_name = _('User login log') diff --git a/apps/audits/signals_handler.py b/apps/audits/signals_handler.py index 0f9356066..daa3fa4bf 100644 --- a/apps/audits/signals_handler.py +++ b/apps/audits/signals_handler.py @@ -102,11 +102,6 @@ def create_operate_log(action, sender, resource): M2M_NEED_RECORD = { - 'OrganizationMember': ( - _('User and Organization'), - _('{User} JOINED {Organization}'), - _('{User} LEFT {Organization}') - ), User.groups.through._meta.object_name: ( _('User and Group'), _('{User} JOINED {UserGroup}'), diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 4bf980374..87bde8380 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -24,12 +24,10 @@ from applications.models import Application from authentication.signals import post_auth_failed from common.utils import get_logger, random_string from common.mixins.api import SerializerMixin -from common.permissions import IsSuperUserOrAppUser, IsValidUser, IsSuperUser from common.utils.common import get_file_by_arch from orgs.mixins.api import RootOrgViewMixin from common.http import is_true from perms.models.base import Action -from perms.utils.application.permission import validate_permission as app_validate_permission from perms.utils.application.permission import get_application_actions from perms.utils.asset.permission import get_asset_actions @@ -42,6 +40,14 @@ __all__ = ['UserConnectionTokenViewSet'] class ClientProtocolMixin: + """ + 下载客户端支持的连接文件,里面包含了 token,和 其他连接信息 + + - [x] RDP + - [ ] KoKo + + 本质上,这里还是暴露出 token 来,进行使用 + """ request: Request get_serializer: Callable create_token: Callable @@ -167,7 +173,7 @@ class ClientProtocolMixin: rst = rst.decode('ascii') return rst - @action(methods=['POST', 'GET'], detail=False, url_path='rdp/file', permission_classes=[IsValidUser]) + @action(methods=['POST', 'GET'], detail=False, url_path='rdp/file') def get_rdp_file(self, request, *args, **kwargs): if self.request.method == 'GET': data = self.request.query_params @@ -214,7 +220,7 @@ class ClientProtocolMixin: } return data - @action(methods=['POST', 'GET'], detail=False, url_path='client-url', permission_classes=[IsValidUser]) + @action(methods=['POST', 'GET'], detail=False, url_path='client-url') def get_client_protocol_url(self, request, *args, **kwargs): serializer = self.get_valid_serializer() try: @@ -271,8 +277,14 @@ class SecretDetailMixin: 'remote_app': None, } - @action(methods=['POST'], detail=False, permission_classes=[IsSuperUserOrAppUser], url_path='secret-info/detail') + @action(methods=['POST'], detail=False, url_path='secret-info/detail') def get_secret_detail(self, request, *args, **kwargs): + perm_required = 'authentication.view_connectiontokensecret' + + # 非常重要的 api,再逻辑层再判断一下,双重保险 + if not request.user.has_perm(perm_required): + raise PermissionDenied('Not allow to view secret') + token = request.data.get('token', '') try: value, user, system_user, asset, app, expired_at, actions = self.valid_token(token) @@ -307,12 +319,18 @@ class UserConnectionTokenViewSet( RootOrgViewMixin, SerializerMixin, ClientProtocolMixin, SecretDetailMixin, GenericViewSet ): - permission_classes = (IsSuperUserOrAppUser,) serializer_classes = { 'default': ConnectionTokenSerializer, 'get_secret_detail': ConnectionTokenSecretSerializer, } CACHE_KEY_PREFIX = 'CONNECTION_TOKEN_{}' + rbac_perms = { + 'GET': 'view_connectiontoken', + 'create': 'add_connectiontoken', + 'get_secret_detail': 'view_connectiontokensecret', + 'get_rdp_file': 'add_connectiontoken', + 'get_client_protocol_url': 'add_connectiontoken', + } @staticmethod def check_resource_permission(user, asset, application, system_user): @@ -403,14 +421,6 @@ class UserConnectionTokenViewSet( raise serializers.ValidationError('Permission expired or invalid') return value, user, system_user, asset, app, expired_at, actions - def get_permissions(self): - if self.action in ["create", "get_rdp_file"]: - if self.request.data.get('user', None): - self.permission_classes = (IsSuperUser,) - else: - self.permission_classes = (IsValidUser,) - return super().get_permissions() - def get(self, request): token = request.query_params.get('token') key = self.CACHE_KEY_PREFIX.format(token) diff --git a/apps/authentication/api/dingtalk.py b/apps/authentication/api/dingtalk.py index ce1732118..da03f015b 100644 --- a/apps/authentication/api/dingtalk.py +++ b/apps/authentication/api/dingtalk.py @@ -5,7 +5,6 @@ from rest_framework.response import Response from users.permissions import IsAuthPasswdTimeValid from users.models import User from common.utils import get_logger -from common.permissions import IsOrgAdmin from common.mixins.api import RoleUserMixin, RoleAdminMixin from authentication import errors @@ -32,4 +31,4 @@ class DingTalkQRUnBindForUserApi(RoleUserMixin, DingTalkQRUnBindBase): class DingTalkQRUnBindForAdminApi(RoleAdminMixin, DingTalkQRUnBindBase): user_id_url_kwarg = 'user_id' - permission_classes = (IsOrgAdmin,) + \ No newline at end of file diff --git a/apps/authentication/api/feishu.py b/apps/authentication/api/feishu.py index 1665d057a..aaed60db9 100644 --- a/apps/authentication/api/feishu.py +++ b/apps/authentication/api/feishu.py @@ -5,7 +5,6 @@ from rest_framework.response import Response from users.permissions import IsAuthPasswdTimeValid from users.models import User from common.utils import get_logger -from common.permissions import IsOrgAdmin from common.mixins.api import RoleUserMixin, RoleAdminMixin from authentication import errors @@ -32,7 +31,6 @@ class FeiShuQRUnBindForUserApi(RoleUserMixin, FeiShuQRUnBindBase): class FeiShuQRUnBindForAdminApi(RoleAdminMixin, FeiShuQRUnBindBase): user_id_url_kwarg = 'user_id' - permission_classes = (IsOrgAdmin,) class FeiShuEventSubscriptionCallback(APIView): diff --git a/apps/authentication/api/login_confirm.py b/apps/authentication/api/login_confirm.py index e3a1bc118..9adedb1e0 100644 --- a/apps/authentication/api/login_confirm.py +++ b/apps/authentication/api/login_confirm.py @@ -1,13 +1,10 @@ # -*- coding: utf-8 -*- # -from rest_framework.generics import UpdateAPIView from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.permissions import AllowAny -from django.shortcuts import get_object_or_404 from common.utils import get_logger -from common.permissions import IsOrgAdmin from .. import errors, mixins __all__ = ['TicketStatusApi'] diff --git a/apps/authentication/api/mfa.py b/apps/authentication/api/mfa.py index 950152def..90aeb7fc4 100644 --- a/apps/authentication/api/mfa.py +++ b/apps/authentication/api/mfa.py @@ -39,14 +39,6 @@ class MFASendCodeApi(AuthMixin, CreateAPIView): username = '' ip = '' - def get_user_from_db(self, username): - try: - user = get_object_or_404(User, username=username) - return user - except Exception as e: - self.incr_mfa_failed_time(username, self.ip) - raise e - def get_user_from_db(self, username): """避免暴力测试用户名""" ip = self.get_request_ip() diff --git a/apps/authentication/api/sso.py b/apps/authentication/api/sso.py index 11435edcb..11f76d633 100644 --- a/apps/authentication/api/sso.py +++ b/apps/authentication/api/sso.py @@ -13,7 +13,7 @@ from common.utils.timezone import utc_now from common.const.http import POST, GET from common.drf.api import JMSGenericViewSet from common.drf.serializers import EmptySerializer -from common.permissions import IsSuperUser +from common.permissions import OnlySuperUser from common.utils import reverse from users.models import User from ..serializers import SSOTokenSerializer @@ -32,9 +32,8 @@ class SSOViewSet(AuthMixin, JMSGenericViewSet): 'login_url': SSOTokenSerializer, 'login': EmptySerializer } - permission_classes = (IsSuperUser,) - @action(methods=[POST], detail=False, permission_classes=[IsSuperUser], url_path='login-url') + @action(methods=[POST], detail=False, permission_classes=[OnlySuperUser], url_path='login-url') def login_url(self, request, *args, **kwargs): if not settings.AUTH_SSO: raise SSOAuthClosed() diff --git a/apps/authentication/api/wecom.py b/apps/authentication/api/wecom.py index c66da5f79..486efde21 100644 --- a/apps/authentication/api/wecom.py +++ b/apps/authentication/api/wecom.py @@ -5,7 +5,6 @@ from rest_framework.response import Response from users.permissions import IsAuthPasswdTimeValid from users.models import User from common.utils import get_logger -from common.permissions import IsOrgAdmin from common.mixins.api import RoleUserMixin, RoleAdminMixin from authentication import errors @@ -32,4 +31,4 @@ class WeComQRUnBindForUserApi(RoleUserMixin, WeComQRUnBindBase): class WeComQRUnBindForAdminApi(RoleAdminMixin, WeComQRUnBindBase): user_id_url_kwarg = 'user_id' - permission_classes = (IsOrgAdmin,) + \ No newline at end of file diff --git a/apps/authentication/apps.py b/apps/authentication/apps.py index 5b63e5696..527c54871 100644 --- a/apps/authentication/apps.py +++ b/apps/authentication/apps.py @@ -1,8 +1,10 @@ from django.apps import AppConfig +from django.utils.translation import ugettext_lazy as _ class AuthenticationConfig(AppConfig): name = 'authentication' + verbose_name = _('Authentication') def ready(self): from . import signals_handlers diff --git a/apps/authentication/backends/api.py b/apps/authentication/backends/api.py index 79d420626..8ceecc4af 100644 --- a/apps/authentication/backends/api.py +++ b/apps/authentication/backends/api.py @@ -29,6 +29,9 @@ def get_request_date_header(request): class JMSModelBackend(ModelBackend): + def has_perm(self, user_obj, perm, obj=None): + return False + def user_can_authenticate(self, user): return True diff --git a/apps/authentication/migrations/0005_auto_20210929_1106.py b/apps/authentication/migrations/0005_auto_20210929_1106.py new file mode 100644 index 000000000..3197d5d43 --- /dev/null +++ b/apps/authentication/migrations/0005_auto_20210929_1106.py @@ -0,0 +1,17 @@ +# Generated by Django 3.1.12 on 2021-09-29 03:06 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0004_ssotoken'), + ] + + operations = [ + migrations.AlterModelOptions( + name='loginconfirmsetting', + options={'verbose_name': 'Login Confirm'}, + ), + ] diff --git a/apps/authentication/migrations/0006_auto_20211130_1037.py b/apps/authentication/migrations/0006_auto_20211130_1037.py new file mode 100644 index 000000000..c1023be9d --- /dev/null +++ b/apps/authentication/migrations/0006_auto_20211130_1037.py @@ -0,0 +1,21 @@ +# Generated by Django 3.1.13 on 2021-11-30 02:37 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0005_auto_20210929_1106'), + ] + + operations = [ + migrations.AlterModelOptions( + name='accesskey', + options={'verbose_name': 'Access key'}, + ), + migrations.AlterModelOptions( + name='ssotoken', + options={'verbose_name': 'SSO token'}, + ), + ] diff --git a/apps/authentication/migrations/0007_connectiontoken.py b/apps/authentication/migrations/0007_connectiontoken.py new file mode 100644 index 000000000..b5d404c97 --- /dev/null +++ b/apps/authentication/migrations/0007_connectiontoken.py @@ -0,0 +1,26 @@ +# Generated by Django 3.1.12 on 2022-02-11 06:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0006_auto_20211130_1037'), + ] + + operations = [ + migrations.CreateModel( + name='ConnectionToken', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated 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')), + ], + options={ + 'permissions': [('add_superconnectiontoken', 'Can add super connection token'), ('view_connectiontokensecret', 'Can view connect token secret')], + }, + ), + ] diff --git a/apps/authentication/models.py b/apps/authentication/models.py index 58c80ee68..f263973fa 100644 --- a/apps/authentication/models.py +++ b/apps/authentication/models.py @@ -29,6 +29,9 @@ class AccessKey(models.Model): def __str__(self): return str(self.id) + class Meta: + verbose_name = _("Access key") + class PrivateToken(Token): """Inherit from auth token, otherwise migration is boring""" @@ -45,3 +48,17 @@ class SSOToken(models.JMSBaseModel): authkey = models.UUIDField(primary_key=True, default=uuid.uuid4, verbose_name=_('Token')) expired = models.BooleanField(default=False, verbose_name=_('Expired')) user = models.ForeignKey('users.User', on_delete=models.CASCADE, verbose_name=_('User'), db_constraint=False) + + class Meta: + verbose_name = _('SSO token') + + +class ConnectionToken(models.JMSBaseModel): + # Todo: 未来可能放到这里,不记录到 redis 了,虽然方便,但是不易于审计 + # Todo: add connection token 可能要授权给 普通用户, 或者放开就行 + + class Meta: + permissions = [ + ('add_superconnectiontoken', _('Can add super connection token')), + ('view_connectiontokensecret', _('Can view connect token secret')) + ] diff --git a/apps/common/mixins/views.py b/apps/common/mixins/views.py index f167d2001..3d553b3d0 100644 --- a/apps/common/mixins/views.py +++ b/apps/common/mixins/views.py @@ -2,10 +2,13 @@ # from django.contrib.auth.mixins import UserPassesTestMixin from rest_framework import permissions +from rest_framework.decorators import action from rest_framework.request import Request +from rest_framework.response import Response +from common.permissions import IsValidUser -__all__ = ["PermissionsMixin"] +__all__ = ["PermissionsMixin", "SuggestionMixin"] class PermissionsMixin(UserPassesTestMixin): @@ -23,3 +26,17 @@ class PermissionsMixin(UserPassesTestMixin): return True +class SuggestionMixin: + suggestion_mini_count = 10 + + @action(methods=['get'], detail=False, permission_classes=(IsValidUser,)) + def suggestions(self, request, *args, **kwargs): + queryset = self.filter_queryset(self.get_queryset()) + queryset = queryset[:self.suggestion_mini_count] + page = self.paginate_queryset(queryset) + if page is not None: + serializer = self.get_serializer(page, many=True) + return self.get_paginated_response(serializer.data) + + serializer = self.get_serializer(queryset, many=True) + return Response(serializer.data) \ No newline at end of file diff --git a/apps/common/permissions.py b/apps/common/permissions.py index bd9a1e030..b52c0cc5c 100644 --- a/apps/common/permissions.py +++ b/apps/common/permissions.py @@ -5,9 +5,6 @@ from rest_framework import permissions from django.conf import settings from common.exceptions import MFAVerifyRequired -from orgs.utils import current_org -from common.utils import is_uuid - class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission): """Allows access to valid user, is active and not expired""" @@ -17,80 +14,12 @@ class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission): and request.user.is_valid -class IsAppUser(IsValidUser): - """Allows access only to app user """ - +class OnlySuperUser(IsValidUser): def has_permission(self, request, view): - return super(IsAppUser, self).has_permission(request, view) \ - and request.user.is_app - - -class IsSuperUser(IsValidUser): - def has_permission(self, request, view): - return super(IsSuperUser, self).has_permission(request, view) \ + return super().has_permission(request, view) \ and request.user.is_superuser -class IsSuperUserOrAppUser(IsSuperUser): - def has_permission(self, request, view): - if request.user.is_anonymous: - return False - return super(IsSuperUserOrAppUser, self).has_permission(request, view) \ - or request.user.is_app - - -class IsSuperAuditor(IsValidUser): - def has_permission(self, request, view): - return super(IsSuperAuditor, self).has_permission(request, view) \ - and request.user.is_super_auditor - - -class IsOrgAuditor(IsValidUser): - def has_permission(self, request, view): - if not current_org: - return False - return super(IsOrgAuditor, self).has_permission(request, view) \ - and current_org.can_audit_by(request.user) - - -class IsOrgAdmin(IsValidUser): - """Allows access only to superuser""" - - def has_permission(self, request, view): - if not current_org: - return False - return super(IsOrgAdmin, self).has_permission(request, view) \ - and current_org.can_admin_by(request.user) - - -class IsOrgAdminOrAppUser(IsValidUser): - """Allows access between superuser and app user""" - - def has_permission(self, request, view): - if not current_org: - return False - if request.user.is_anonymous: - return False - return super(IsOrgAdminOrAppUser, self).has_permission(request, view) \ - and (current_org.can_admin_by(request.user) or request.user.is_app) - - -class IsOrgAdminOrAppUserOrUserReadonly(IsOrgAdminOrAppUser): - def has_permission(self, request, view): - if IsValidUser.has_permission(self, request, view) \ - and request.method in permissions.SAFE_METHODS: - return True - else: - return IsOrgAdminOrAppUser.has_permission(self, request, view) - - -class IsCurrentUserOrReadOnly(permissions.BasePermission): - def has_object_permission(self, request, view, obj): - if request.method in permissions.SAFE_METHODS: - return True - return obj == request.user - - class WithBootstrapToken(permissions.BasePermission): def has_permission(self, request, view): authorization = request.META.get('HTTP_AUTHORIZATION', '') @@ -100,21 +29,6 @@ class WithBootstrapToken(permissions.BasePermission): return settings.BOOTSTRAP_TOKEN == request_bootstrap_token -class UserCanAnyPermCurrentOrg(permissions.BasePermission): - def has_permission(self, request, view): - return current_org.can_any_by(request.user) - - -class UserCanUpdatePassword(permissions.BasePermission): - def has_permission(self, request, view): - return request.user.can_update_password() - - -class UserCanUpdateSSHKey(permissions.BasePermission): - def has_permission(self, request, view): - return request.user.can_update_ssh_key() - - class NeedMFAVerify(permissions.BasePermission): def has_permission(self, request, view): if not settings.SECURITY_VIEW_AUTH_NEED_MFA: @@ -126,80 +40,7 @@ class NeedMFAVerify(permissions.BasePermission): raise MFAVerifyRequired() -class CanUpdateDeleteUser(permissions.BasePermission): - - @staticmethod - def has_delete_object_permission(request, view, obj): - if request.user.is_anonymous: - return False - if not request.user.can_admin_current_org: - return False - # 超级管理员 / 组织管理员 - if str(request.user.id) == str(obj.id): - return False - # 超级管理员 - if request.user.is_superuser: - if obj.is_superuser and obj.username in ['admin']: - return False - return True - # 组织管理员 - if obj.is_superuser: - return False - if obj.is_super_auditor: - return False - if obj.can_admin_current_org: - return False - return True - - @staticmethod - def has_update_object_permission(request, view, obj): - if request.user.is_anonymous: - return False - if not request.user.can_admin_current_org: - return False - # 超级管理员 / 组织管理员 - if str(request.user.id) == str(obj.id): - return True - # 超级管理员 - if request.user.is_superuser: - return True - # 组织管理员 - if obj.is_superuser: - return False - if obj.is_super_auditor: - return False - return True - - def has_object_permission(self, request, view, obj): - if request.user.is_anonymous: - return False - if not request.user.can_admin_current_org: - return False - if request.method in ['DELETE']: - return self.has_delete_object_permission(request, view, obj) - if request.method in ['PUT', 'PATCH']: - return self.has_update_object_permission(request, view, obj) - return True - - class IsObjectOwner(IsValidUser): def has_object_permission(self, request, view, obj): return (super().has_object_permission(request, view, obj) and request.user == getattr(obj, 'user', None)) - - -class HasQueryParamsUserAndIsCurrentOrgMember(permissions.BasePermission): - def has_permission(self, request, view): - query_user_id = request.query_params.get('user') - if not query_user_id or not is_uuid(query_user_id): - return False - query_user = current_org.get_members().filter(id=query_user_id).first() - return bool(query_user) - - -class OnlySuperUserCanList(IsValidUser): - def has_permission(self, request, view): - user = request.user - if view.action == 'list' and not user.is_superuser: - return False - return True diff --git a/apps/common/tree.py b/apps/common/tree.py index 8d5e40e5b..d4ffa47bf 100644 --- a/apps/common/tree.py +++ b/apps/common/tree.py @@ -15,6 +15,7 @@ class TreeNode: iconSkin = "" parentInfo = '' meta = {} + checked = False _tree = None @@ -101,4 +102,7 @@ class TreeNodeSerializer(serializers.Serializer): open = serializers.BooleanField(default=False) iconSkin = serializers.CharField(max_length=128, allow_blank=True) nocheck = serializers.BooleanField(default=False) + checked = serializers.BooleanField(default=False) + halfCheck = serializers.BooleanField(default=False) + chkDisabled = serializers.BooleanField(default=False) meta = serializers.JSONField() diff --git a/apps/jumpserver/api.py b/apps/jumpserver/api.py index 3e7c4f19c..8edc35e76 100644 --- a/apps/jumpserver/api.py +++ b/apps/jumpserver/api.py @@ -15,8 +15,7 @@ from assets.models import Asset from terminal.models import Session from terminal.utils import ComponentsPrometheusMetricsUtil from orgs.utils import current_org -from common.permissions import IsOrgAdmin, IsOrgAuditor -from common.utils import lazyproperty, get_request_ip +from common.utils import lazyproperty from orgs.caches import OrgResourceStatisticsCache @@ -213,8 +212,10 @@ class DatesLoginMetricMixin: class IndexApi(DatesLoginMetricMixin, APIView): - permission_classes = (IsOrgAdmin | IsOrgAuditor,) http_method_names = ['get'] + rbac_perms = { + 'GET': 'view_auditview' + } def get(self, request, *args, **kwargs): data = {} diff --git a/apps/jumpserver/settings/auth.py b/apps/jumpserver/settings/auth.py index 883f63d07..f5e6af75f 100644 --- a/apps/jumpserver/settings/auth.py +++ b/apps/jumpserver/settings/auth.py @@ -139,6 +139,7 @@ OTP_IN_RADIUS = CONFIG.OTP_IN_RADIUS AUTH_BACKEND_MODEL = 'authentication.backends.api.JMSModelBackend' +RBAC_BACKEND = 'rbac.backends.RBACBackend' AUTH_BACKEND_PUBKEY = 'authentication.backends.pubkey.PublicKeyAuthBackend' AUTH_BACKEND_LDAP = 'authentication.backends.ldap.LDAPAuthorizationBackend' AUTH_BACKEND_OIDC_PASSWORD = 'jms_oidc_rp.backends.OIDCAuthPasswordBackend' @@ -154,7 +155,7 @@ AUTH_BACKEND_SAML2 = 'authentication.backends.saml2.SAML2Backend' AUTHENTICATION_BACKENDS = [ - AUTH_BACKEND_MODEL, AUTH_BACKEND_PUBKEY, AUTH_BACKEND_WECOM, + AUTH_BACKEND_MODEL, RBAC_BACKEND, AUTH_BACKEND_PUBKEY, AUTH_BACKEND_WECOM, AUTH_BACKEND_DINGTALK, AUTH_BACKEND_FEISHU, AUTH_BACKEND_AUTH_TOKEN, AUTH_BACKEND_SSO, ] diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py index fd8feeaad..e0c1b5628 100644 --- a/apps/jumpserver/settings/base.py +++ b/apps/jumpserver/settings/base.py @@ -49,6 +49,7 @@ INSTALLED_APPS = [ 'tickets.apps.TicketsConfig', 'acls.apps.AclsConfig', 'notifications.apps.NotificationsConfig', + 'rbac.apps.RBACConfig', 'common.apps.CommonConfig', 'jms_oidc_rp', 'rest_framework', diff --git a/apps/jumpserver/settings/libs.py b/apps/jumpserver/settings/libs.py index 9c67a15e8..ae36c437f 100644 --- a/apps/jumpserver/settings/libs.py +++ b/apps/jumpserver/settings/libs.py @@ -7,7 +7,7 @@ REST_FRAMEWORK = { # Use Django's standard `django.contrib.auth` permissions, # or allow read-only access for unauthenticated users. 'DEFAULT_PERMISSION_CLASSES': ( - 'common.permissions.IsSuperUser', + 'rbac.permissions.RBACPermission', ), 'DEFAULT_RENDERER_CLASSES': ( 'rest_framework.renderers.JSONRenderer', diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index ea41cc7ee..80948f9f6 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -24,6 +24,7 @@ api_v1 = [ path('tickets/', include('tickets.urls.api_urls', namespace='api-tickets')), path('acls/', include('acls.urls.api_urls', namespace='api-acls')), path('notifications/', include('notifications.urls.api_urls', namespace='api-notifications')), + path('rbac/', include('rbac.urls.api_urls', namespace='api-rbac')), path('prometheus/metrics/', api.PrometheusMetricsApi.as_view()), ] diff --git a/apps/notifications/api/notifications.py b/apps/notifications/api/notifications.py index e1ad87a43..8271c2a10 100644 --- a/apps/notifications/api/notifications.py +++ b/apps/notifications/api/notifications.py @@ -3,7 +3,7 @@ from rest_framework.views import APIView from rest_framework.response import Response from common.drf.api import JMSGenericViewSet -from common.permissions import IsObjectOwner, IsSuperUser, OnlySuperUserCanList +from common.permissions import IsValidUser from notifications.notifications import system_msgs from notifications.models import SystemMsgSubscription, UserMsgSubscription from notifications.backends import BACKEND @@ -25,7 +25,7 @@ class BackendListView(APIView): 'name': backend, 'name_display': backend.label } - for backend in BACKEND + for backend in BACKEND.choices if backend.is_enable ] return Response(data=data) @@ -41,6 +41,9 @@ class SystemMsgSubscriptionViewSet(ListModelMixin, 'update': SystemMsgSubscriptionSerializer, 'partial_update': SystemMsgSubscriptionSerializer } + rbac_perms = { + + } def list(self, request, *args, **kwargs): data = [] @@ -80,9 +83,13 @@ class UserMsgSubscriptionViewSet(ListModelMixin, UpdateModelMixin, JMSGenericViewSet): lookup_field = 'user_id' - queryset = UserMsgSubscription.objects.all() serializer_class = UserMsgSubscriptionSerializer - permission_classes = (IsObjectOwner | IsSuperUser, OnlySuperUserCanList) + permission_classes = (IsValidUser,) + + def get_queryset(self): + queryset = UserMsgSubscription.objects.filter(user=self.request.user) + return queryset + def get_all_test_messages(request): diff --git a/apps/notifications/apps.py b/apps/notifications/apps.py index e888822d4..1c64497a1 100644 --- a/apps/notifications/apps.py +++ b/apps/notifications/apps.py @@ -1,8 +1,10 @@ from django.apps import AppConfig +from django.utils.translation import ugettext_lazy as _ class NotificationsConfig(AppConfig): name = 'notifications' + verbose_name = _('Notifications') def ready(self): from . import signals_handler diff --git a/apps/ops/api/adhoc.py b/apps/ops/api/adhoc.py index f7b32e4fd..3bb1acb53 100644 --- a/apps/ops/api/adhoc.py +++ b/apps/ops/api/adhoc.py @@ -5,8 +5,6 @@ from django.shortcuts import get_object_or_404 from rest_framework import viewsets, generics from rest_framework.views import Response -from common.drf.api import JMSBulkModelViewSet -from common.permissions import IsOrgAdmin from common.drf.serializers import CeleryTaskSerializer from ..models import Task, AdHoc, AdHocExecution from ..serializers import ( @@ -18,7 +16,6 @@ from ..serializers import ( ) from ..tasks import run_ansible_task from orgs.mixins.api import OrgBulkModelViewSet -from orgs.utils import current_org __all__ = [ 'TaskViewSet', 'TaskRun', 'AdHocViewSet', 'AdHocRunHistoryViewSet' @@ -30,7 +27,6 @@ class TaskViewSet(OrgBulkModelViewSet): filterset_fields = ("name",) search_fields = filterset_fields serializer_class = TaskSerializer - permission_classes = (IsOrgAdmin,) def get_serializer_class(self): if self.action == 'retrieve': @@ -46,7 +42,6 @@ class TaskViewSet(OrgBulkModelViewSet): class TaskRun(generics.RetrieveAPIView): queryset = Task.objects.all() serializer_class = CeleryTaskSerializer - permission_classes = (IsOrgAdmin,) def retrieve(self, request, *args, **kwargs): task = self.get_object() @@ -57,7 +52,6 @@ class TaskRun(generics.RetrieveAPIView): class AdHocViewSet(viewsets.ModelViewSet): queryset = AdHoc.objects.all() serializer_class = AdHocSerializer - permission_classes = (IsOrgAdmin,) def get_serializer_class(self): if self.action == 'retrieve': @@ -75,7 +69,6 @@ class AdHocViewSet(viewsets.ModelViewSet): class AdHocRunHistoryViewSet(viewsets.ModelViewSet): queryset = AdHocExecution.objects.all() serializer_class = AdHocExecutionSerializer - permission_classes = (IsOrgAdmin,) def get_queryset(self): task_id = self.request.query_params.get('task') diff --git a/apps/ops/api/celery.py b/apps/ops/api/celery.py index 3968b39b7..cd452c471 100644 --- a/apps/ops/api/celery.py +++ b/apps/ops/api/celery.py @@ -10,7 +10,7 @@ from celery.result import AsyncResult from rest_framework import generics from django_celery_beat.models import PeriodicTask -from common.permissions import IsValidUser, IsSuperUser +from common.permissions import IsValidUser from common.api import LogTailApi from ..models import CeleryTask from ..serializers import CeleryResultSerializer, CeleryPeriodTaskSerializer @@ -88,7 +88,6 @@ class CeleryResultApi(generics.RetrieveAPIView): class CeleryPeriodTaskViewSet(CommonApiMixin, viewsets.ModelViewSet): queryset = PeriodicTask.objects.all() serializer_class = CeleryPeriodTaskSerializer - permission_classes = (IsSuperUser,) http_method_names = ('get', 'head', 'options', 'patch') def get_queryset(self): diff --git a/apps/ops/migrations/0021_auto_20211130_1037.py b/apps/ops/migrations/0021_auto_20211130_1037.py new file mode 100644 index 000000000..355abeb40 --- /dev/null +++ b/apps/ops/migrations/0021_auto_20211130_1037.py @@ -0,0 +1,29 @@ +# Generated by Django 3.1.13 on 2021-11-30 02:37 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0020_adhoc_run_system_user'), + ] + + operations = [ + migrations.AlterModelOptions( + name='adhoc', + options={'get_latest_by': 'date_created', 'verbose_name': 'AdHoc'}, + ), + migrations.AlterModelOptions( + name='adhocexecution', + options={'get_latest_by': 'date_start', 'verbose_name': 'AdHoc execution'}, + ), + migrations.AlterModelOptions( + name='commandexecution', + options={'verbose_name': 'Command execution'}, + ), + migrations.AlterModelOptions( + name='task', + options={'get_latest_by': 'date_created', 'ordering': ('-date_updated',), 'verbose_name': 'Task'}, + ), + ] diff --git a/apps/ops/migrations/0022_auto_20220211_1423.py b/apps/ops/migrations/0022_auto_20220211_1423.py new file mode 100644 index 000000000..d5fa58734 --- /dev/null +++ b/apps/ops/migrations/0022_auto_20220211_1423.py @@ -0,0 +1,17 @@ +# Generated by Django 3.1.12 on 2022-02-11 06:23 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0021_auto_20211130_1037'), + ] + + operations = [ + migrations.AlterModelOptions( + name='task', + options={'get_latest_by': 'date_created', 'ordering': ('-date_updated',), 'permissions': [('view_taskmonitor', 'Can view task monitor')], 'verbose_name': 'Task'}, + ), + ] diff --git a/apps/ops/models/adhoc.py b/apps/ops/models/adhoc.py index d1bb8c7f9..1a6e02fd1 100644 --- a/apps/ops/models/adhoc.py +++ b/apps/ops/models/adhoc.py @@ -38,7 +38,8 @@ class Task(PeriodTaskModelMixin, OrgModelMixin): comment = models.TextField(blank=True, verbose_name=_("Comment")) date_created = models.DateTimeField(auto_now_add=True, db_index=True, verbose_name=_("Date created")) date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated")) - latest_adhoc = models.ForeignKey('ops.AdHoc', on_delete=models.SET_NULL, null=True, related_name='task_latest') + latest_adhoc = models.ForeignKey('ops.AdHoc', on_delete=models.SET_NULL, + null=True, related_name='task_latest') latest_execution = models.ForeignKey('ops.AdHocExecution', on_delete=models.SET_NULL, null=True, related_name='task_latest') total_run_amount = models.IntegerField(default=0) success_run_amount = models.IntegerField(default=0) @@ -131,7 +132,11 @@ class Task(PeriodTaskModelMixin, OrgModelMixin): db_table = 'ops_task' unique_together = ('name', 'org_id') ordering = ('-date_updated',) + verbose_name = _("Task") get_latest_by = 'date_created' + permissions = [ + ('view_taskmonitor', _('Can view task monitor')) + ] class AdHoc(OrgModelMixin): @@ -235,6 +240,7 @@ class AdHoc(OrgModelMixin): class Meta: db_table = "ops_adhoc" get_latest_by = 'date_created' + verbose_name = _('AdHoc') class AdHocExecution(OrgModelMixin): @@ -330,3 +336,4 @@ class AdHocExecution(OrgModelMixin): class Meta: db_table = "ops_adhoc_execution" get_latest_by = 'date_start' + verbose_name = _("AdHoc execution") diff --git a/apps/ops/models/celery.py b/apps/ops/models/celery.py index 51412988c..9ab5f49e1 100644 --- a/apps/ops/models/celery.py +++ b/apps/ops/models/celery.py @@ -2,6 +2,8 @@ # import uuid import os + +from django.utils.translation import gettext_lazy as _ from django.conf import settings from django.db import models diff --git a/apps/ops/models/command.py b/apps/ops/models/command.py index 81f9ce9e9..66bd8ed28 100644 --- a/apps/ops/models/command.py +++ b/apps/ops/models/command.py @@ -155,3 +155,6 @@ class CommandExecution(OrgModelMixin): self.save() print('-' * 10 + ' ' + ugettext('Task end') + ' ' + '-' * 10) return self.result + + class Meta: + verbose_name = _("Command execution") diff --git a/apps/ops/notifications.py b/apps/ops/notifications.py index b34b00e1f..e76c2037d 100644 --- a/apps/ops/notifications.py +++ b/apps/ops/notifications.py @@ -32,7 +32,11 @@ class ServerPerformanceMessage(SystemMessage): @classmethod def post_insert_to_db(cls, subscription: SystemMsgSubscription): - admins = User.objects.filter(role=User.ROLE.ADMIN) + from rbac.models import Role, RoleBinding + # Todo: 需要更改这里 + admin_role = Role.BuiltinRole.system_admin.get_role() + admins_ids = RoleBinding.objects.filter(role=admin_role).values_list('user_id', flat=True) + admins = User.objects.filter(id__in=admins_ids) subscription.users.add(*admins) subscription.receive_backends = [BACKEND.EMAIL] subscription.save() diff --git a/apps/ops/views.py b/apps/ops/views.py index 7b18b0f46..91e753514 100644 --- a/apps/ops/views.py +++ b/apps/ops/views.py @@ -3,15 +3,18 @@ from django.views.generic import TemplateView from django.conf import settings -from common.permissions import IsOrgAdmin, IsOrgAuditor from common.mixins.views import PermissionsMixin +from rbac.permissions import RBACPermission __all__ = ['CeleryTaskLogView'] class CeleryTaskLogView(PermissionsMixin, TemplateView): template_name = 'ops/celery_task_log.html' - permission_classes = [IsOrgAdmin | IsOrgAuditor] + permission_classes = [RBACPermission] + rbac_perms = { + 'GET': 'view_tasklog' + } def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) diff --git a/apps/orgs/api.py b/apps/orgs/api.py index 4a5c24fdb..e1a41a29f 100644 --- a/apps/orgs/api.py +++ b/apps/orgs/api.py @@ -2,20 +2,14 @@ # from django.utils.translation import ugettext as _ -from rest_framework import status -from rest_framework.views import Response from rest_framework_bulk import BulkModelViewSet from rest_framework.generics import RetrieveAPIView from rest_framework.exceptions import PermissionDenied -from common.permissions import IsSuperUserOrAppUser, IsValidUser, UserCanAnyPermCurrentOrg -from common.drf.api import JMSBulkRelationModelViewSet -from .models import Organization, ROLE +from common.permissions import IsValidUser +from .models import Organization from .serializers import ( - OrgSerializer, OrgReadSerializer, - OrgRetrieveSerializer, OrgMemberSerializer, - OrgMemberAdminSerializer, OrgMemberUserSerializer, - CurrentOrgSerializer + OrgSerializer, CurrentOrgSerializer ) from users.models import User, UserGroup from assets.models import ( @@ -26,8 +20,6 @@ from applications.models import Application from perms.models import AssetPermission, ApplicationPermission from orgs.utils import current_org, tmp_to_root_org from common.utils import get_logger -from .filters import OrgMemberRelationFilterSet -from .models import OrganizationMember logger = get_logger(__file__) @@ -47,23 +39,20 @@ class OrgViewSet(BulkModelViewSet): search_fields = ('name', 'comment') queryset = Organization.objects.all() serializer_class = OrgSerializer - permission_classes = (IsSuperUserOrAppUser,) ordering_fields = ('name',) ordering = ('name', ) def get_serializer_class(self): mapper = { - 'list': OrgReadSerializer, - 'retrieve': OrgRetrieveSerializer + 'list': OrgSerializer, + 'retrieve': OrgSerializer } return mapper.get(self.action, super().get_serializer_class()) @tmp_to_root_org() def get_data_from_model(self, org, model): if model == User: - data = model.objects.filter( - orgs__id=org.id, m2m_org_members__role__in=[ROLE.USER, ROLE.ADMIN, ROLE.AUDITOR] - ) + data = model.get_org_users(org=org) elif model == Node: # 根节点不能手动删除,所以排除检查 data = model.objects.filter(org_id=org.id).exclude(parent_key='', key__regex=r'^[0-9]+$') @@ -91,77 +80,9 @@ class OrgViewSet(BulkModelViewSet): super().perform_destroy(instance) -class OrgMemberRelationBulkViewSet(JMSBulkRelationModelViewSet): - permission_classes = (IsSuperUserOrAppUser,) - m2m_field = Organization.members.field - serializer_class = OrgMemberSerializer - filterset_class = OrgMemberRelationFilterSet - search_fields = ('user__name', 'user__username', 'org__name') - - def get_queryset(self): - queryset = super().get_queryset() - queryset = queryset.exclude(user__role=User.ROLE.APP) - return queryset - - def perform_bulk_destroy(self, queryset): - objs = list(queryset.all().prefetch_related('user', 'org')) - queryset.delete() - self.send_m2m_changed_signal(objs, action='post_remove') - - -class OrgMemberAdminRelationBulkViewSet(JMSBulkRelationModelViewSet): - permission_classes = (IsSuperUserOrAppUser,) - m2m_field = Organization.members.field - serializer_class = OrgMemberAdminSerializer - filterset_class = OrgMemberRelationFilterSet - search_fields = ('user__name', 'user__username', 'org__name') - lookup_field = 'user_id' - - def get_queryset(self): - queryset = super().get_queryset() - org_id = self.kwargs.get('org_id') - queryset = queryset.filter(org_id=org_id, role=ROLE.ADMIN) - return queryset - - def perform_bulk_create(self, serializer): - data = serializer.validated_data - relations = [OrganizationMember(**i) for i in data] - OrganizationMember.objects.bulk_create(relations, ignore_conflicts=True) - - def perform_bulk_destroy(self, queryset): - objs = list(queryset.all().prefetch_related('user', 'org')) - queryset.delete() - self.send_m2m_changed_signal(objs, action='post_remove') - - -class OrgMemberUserRelationBulkViewSet(JMSBulkRelationModelViewSet): - permission_classes = (IsSuperUserOrAppUser,) - m2m_field = Organization.members.field - serializer_class = OrgMemberUserSerializer - filterset_class = OrgMemberRelationFilterSet - search_fields = ('user__name', 'user__username', 'org__name') - lookup_field = 'user_id' - - def get_queryset(self): - queryset = super().get_queryset() - org_id = self.kwargs.get('org_id') - queryset = queryset.filter(org_id=org_id, role=ROLE.USER) - return queryset - - def perform_bulk_create(self, serializer): - data = serializer.validated_data - relations = [OrganizationMember(**i) for i in data] - OrganizationMember.objects.bulk_create(relations, ignore_conflicts=True) - - def perform_bulk_destroy(self, queryset): - objs = list(queryset.all().prefetch_related('user', 'org')) - queryset.delete() - self.send_m2m_changed_signal(objs, action='post_remove') - - class CurrentOrgDetailApi(RetrieveAPIView): serializer_class = CurrentOrgSerializer - permission_classes = (IsValidUser, UserCanAnyPermCurrentOrg) + permission_classes = (IsValidUser,) def get_object(self): return current_org diff --git a/apps/orgs/apps.py b/apps/orgs/apps.py index c83d67e3c..872fb8eb4 100644 --- a/apps/orgs/apps.py +++ b/apps/orgs/apps.py @@ -1,8 +1,10 @@ from django.apps import AppConfig +from django.utils.translation import ugettext_lazy as _ class OrgsConfig(AppConfig): name = 'orgs' + verbose_name = _('Organizations') def ready(self): from . import signals_handler diff --git a/apps/orgs/caches.py b/apps/orgs/caches.py index ae1a610ce..e8b0bdcba 100644 --- a/apps/orgs/caches.py +++ b/apps/orgs/caches.py @@ -6,11 +6,10 @@ from orgs.utils import current_org, tmp_to_org from common.cache import Cache, IntegerField from common.utils import get_logger from users.models import UserGroup, User -from assets.models import Node, AdminUser, SystemUser, Domain, Gateway, Asset +from assets.models import Node, SystemUser, Domain, Gateway, Asset from terminal.models import Session from applications.models import Application from perms.models import AssetPermission, ApplicationPermission -from .models import OrganizationMember logger = get_logger(__file__) @@ -84,13 +83,8 @@ class OrgResourceStatisticsCache(OrgRelatedCache): return SystemUser.objects.filter(type=SystemUser.Type.common).count() def compute_users_amount(self): - users = User.objects.exclude(role='App') - - if not self.org.is_root(): - users = users.filter(m2m_org_members__org_id=self.org.id) - - users_amount = users.values('id').distinct().count() - return users_amount + amount = User.get_org_users(self.org).count() + return amount def compute_assets_amount(self): if self.org.is_root(): diff --git a/apps/orgs/context_processor.py b/apps/orgs/context_processor.py index c78ed834a..bc1ca0ede 100644 --- a/apps/orgs/context_processor.py +++ b/apps/orgs/context_processor.py @@ -1,17 +1,12 @@ # -*- coding: utf-8 -*- # -from .utils import current_org, get_org_from_request -from .models import Organization +from .utils import get_org_from_request def org_processor(request): context = { - # 'ADMIN_ORGS': request.user.admin_orgs, - # 'AUDIT_ORGS': request.user.audit_orgs, - 'ADMIN_OR_AUDIT_ORGS': Organization.get_user_admin_or_audit_orgs(request.user), 'CURRENT_ORG': get_org_from_request(request), - # 'HAS_ORG_PERM': current_org.can_admin_by(request.user), } return context diff --git a/apps/orgs/filters.py b/apps/orgs/filters.py index c6c026198..ee68a0ed8 100644 --- a/apps/orgs/filters.py +++ b/apps/orgs/filters.py @@ -1,16 +1,6 @@ -from django_filters.rest_framework import filterset from django_filters.rest_framework import filters -from .models import OrganizationMember - class UUIDInFilter(filters.BaseInFilter, filters.UUIDFilter): pass - -class OrgMemberRelationFilterSet(filterset.FilterSet): - id = UUIDInFilter(field_name='id', lookup_expr='in') - - class Meta: - model = OrganizationMember - fields = ('org_id', 'user_id', 'org', 'user', 'role', 'id') diff --git a/apps/orgs/middleware.py b/apps/orgs/middleware.py index 2448fffc3..c1fd64fbe 100644 --- a/apps/orgs/middleware.py +++ b/apps/orgs/middleware.py @@ -2,6 +2,7 @@ # from .utils import get_org_from_request, set_current_org +from rbac.models import RoleBinding class OrgMiddleware: @@ -14,22 +15,19 @@ class OrgMiddleware: return if not request.user.is_authenticated: return - if request.user.is_common_user: - return + org = get_org_from_request(request) - if org.can_admin_by(request.user): - return - if org.can_audit_by(request.user): - return - admin_orgs = request.user.admin_orgs - if admin_orgs: - request.session['oid'] = str(admin_orgs[0].id) - return - audit_orgs = request.user.audit_orgs - if audit_orgs: - request.session['oid'] = str(audit_orgs[0].id) + + search_org = None if org.is_root() else org + has_roles = RoleBinding.objects.filter(user=request.user, org=search_org).exists() + if has_roles: return + roles_bindings = RoleBinding.objects.filter(user=request.user).exclude(org=None) + if roles_bindings: + org_id = str(list(roles_bindings.values_list('org_id', flat=True))[0]) + request.session['oid'] = org_id + def __call__(self, request): self.set_permed_org_if_need(request) org = get_org_from_request(request) diff --git a/apps/orgs/migrations/0011_auto_20211223_1913.py b/apps/orgs/migrations/0011_auto_20211223_1913.py new file mode 100644 index 000000000..5a1789ed4 --- /dev/null +++ b/apps/orgs/migrations/0011_auto_20211223_1913.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.13 on 2021-12-23 11:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('orgs', '0010_auto_20210219_1241'), + ] + + operations = [ + migrations.AlterField( + model_name='organizationmember', + name='role', + field=models.CharField(default='User', max_length=16, verbose_name='Role'), + ), + ] diff --git a/apps/orgs/migrations/0012_auto_20220118_1054.py b/apps/orgs/migrations/0012_auto_20220118_1054.py new file mode 100644 index 000000000..a7d21277c --- /dev/null +++ b/apps/orgs/migrations/0012_auto_20220118_1054.py @@ -0,0 +1,25 @@ +# Generated by Django 3.1.13 on 2022-01-18 02:54 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('rbac', '0004_auto_20211201_1901'), + ('orgs', '0011_auto_20211223_1913'), + ] + + operations = [ + migrations.AlterModelOptions( + name='organization', + options={'permissions': (('view_rootorg', 'Can view root org'),), 'verbose_name': 'Organization'}, + ), + migrations.AlterField( + model_name='organization', + name='members', + field=models.ManyToManyField(related_name='orgs', through='rbac.RoleBinding', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/apps/orgs/models.py b/apps/orgs/models.py index c8baec8fb..7dd93c4ed 100644 --- a/apps/orgs/models.py +++ b/apps/orgs/models.py @@ -1,22 +1,10 @@ import uuid -from functools import partial -from itertools import chain from django.db import models -from django.db.models import signals -from django.db.models import Q from django.utils.translation import ugettext_lazy as _ from common.utils import lazyproperty, settings -from common.const import choices from common.tree import TreeNode -from common.db.models import TextChoices - - -class ROLE(TextChoices): - ADMIN = choices.ADMIN, _('Organization administrator') - AUDITOR = choices.AUDITOR, _("Organization auditor") - USER = choices.USER, _('User') class Organization(models.Model): @@ -25,7 +13,7 @@ class Organization(models.Model): 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')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) - members = models.ManyToManyField('users.User', related_name='orgs', through='orgs.OrganizationMember', through_fields=('org', 'user')) + members = models.ManyToManyField('users.User', related_name='orgs', through='rbac.RoleBinding', through_fields=('org', 'user')) ROOT_ID = '00000000-0000-0000-0000-000000000000' ROOT_NAME = _('GLOBAL') @@ -35,6 +23,9 @@ class Organization(models.Model): class Meta: verbose_name = _("Organization") + permissions = ( + ('view_rootorg', _('Can view root org')), + ) def __str__(self): return str(self.name) @@ -79,113 +70,9 @@ class Organization(models.Model): def expire_orgs_mapping(cls): cls.orgs_mapping = None - def get_org_members_by_role(self, role): - from users.models import User - if not self.is_root(): - return self.members.filter(m2m_org_members__role=role) - users = User.objects.filter(role=role) - return users - - @property - def users(self): - return self.get_org_members_by_role(ROLE.USER) - - @property - def admins(self): - return self.get_org_members_by_role(ROLE.ADMIN) - - @property - def auditors(self): - return self.get_org_members_by_role(ROLE.AUDITOR) - def org_id(self): return self.id - def get_members(self, exclude=()): - from users.models import User - if self.is_root(): - members = User.objects.exclude(role__in=exclude) - else: - members = self.members.exclude(m2m_org_members__role__in=exclude) - return members.exclude(role=User.ROLE.APP).distinct() - - def can_admin_by(self, user): - if user.is_superuser: - return True - if self.admins.filter(id=user.id).exists(): - return True - return False - - def can_audit_by(self, user): - if user.is_superuser or user.is_super_auditor: - return True - if self.can_admin_by(user): - return True - if self.auditors.filter(id=user.id).exists(): - return True - return False - - def can_use_by(self, user): - if user.is_superuser or user.is_super_auditor: - return True - if self.can_audit_by(user): - return True - if self.users.filter(id=user.id).exists(): - return True - return False - - def can_any_by(self, user): - if user.is_superuser or user.is_super_auditor: - return True - return self.members.filter(id=user.id).exists() - - @classmethod - def get_user_orgs_by_role(cls, user, role): - if not isinstance(role, (tuple, list)): - role = (role, ) - - return cls.objects.filter( - m2m_org_members__role__in=role, - m2m_org_members__user_id=user.id - ).distinct() - - @classmethod - def get_user_all_orgs(cls, user): - return cls.objects.filter(members=user).distinct() - - @classmethod - def get_user_admin_orgs(cls, user): - if user.is_anonymous: - return cls.objects.none() - if user.is_superuser: - return [cls.root(), *cls.objects.all()] - return cls.get_user_orgs_by_role(user, ROLE.ADMIN) - - @classmethod - def get_user_user_orgs(cls, user): - if user.is_anonymous: - return cls.objects.none() - return [ - *cls.get_user_orgs_by_role(user, ROLE.USER), - cls.default() - ] - - @classmethod - def get_user_audit_orgs(cls, user): - if user.is_anonymous: - return cls.objects.none() - if user.is_super_auditor: - return [cls.root(), *cls.objects.all()] - return cls.get_user_orgs_by_role(user, ROLE.AUDITOR) - - @classmethod - def get_user_admin_or_audit_orgs(cls, user): - if user.is_anonymous: - return cls.objects.none() - if user.is_superuser or user.is_super_auditor: - return [cls.root(), *cls.objects.all()] - return cls.get_user_orgs_by_role(user, (ROLE.AUDITOR, ROLE.ADMIN)) - @classmethod def default(cls): defaults = dict(id=cls.DEFAULT_ID, name=cls.DEFAULT_NAME) @@ -209,13 +96,18 @@ class Organization(models.Model): @lazyproperty def resource_statistics_cache(self): + # Todo: 由于 redis 问题,没能获取到 + return {} from .caches import OrgResourceStatisticsCache return OrgResourceStatisticsCache(self) + def get_members(self): + return self.members.all().distinct() + def get_total_resources_amount(self): from django.apps import apps from orgs.mixins.models import OrgModelMixin - summary = {'users.Members': self.members.all().count()} + summary = {'users.Members': self.get_members().count()} for app_name, app_config in apps.app_configs.items(): models_cls = app_config.get_models() for model in models_cls: @@ -249,178 +141,24 @@ class Organization(models.Model): return node -def _convert_to_uuid_set(users): - rst = set() - for user in users: - if isinstance(user, models.Model): - rst.add(user.id) - elif not isinstance(user, uuid.UUID): - rst.add(uuid.UUID(user)) - return rst - - -def _none2list(*args): - return ([] if v is None else v for v in args) - - -def _users2pks_if_need(users, admins, auditors): - pks = [] - for user in chain(users, admins, auditors): - if hasattr(user, 'pk'): - pks.append(user.pk) - else: - pks.append(user) - return pks - - -class UserRoleMapper(dict): - def __init__(self, container=set): - super().__init__() - self.users = container() - self.admins = container() - self.auditors = container() - - self[ROLE.USER] = self.users - self[ROLE.ADMIN] = self.admins - self[ROLE.AUDITOR] = self.auditors - - -class OrgMemberManager(models.Manager): - - def remove_users(self, org, users): - from users.models import User - pk_set = [] - for user in users: - if hasattr(user, 'pk'): - pk_set.append(user.pk) - else: - pk_set.append(user) - - send = partial(signals.m2m_changed.send, sender=self.model, instance=org, reverse=False, - model=User, pk_set=pk_set, using=self.db) - send(action="pre_remove") - self.filter(org_id=org.id, user_id__in=pk_set).delete() - send(action="post_remove") - - def remove_users_by_role(self, org, users=None, admins=None, auditors=None): - from users.models import User - - if not any((users, admins, auditors)): - return - users, admins, auditors = _none2list(users, admins, auditors) - - send = partial(signals.m2m_changed.send, sender=self.model, instance=org, reverse=False, - model=User, pk_set=_users2pks_if_need(users, admins, auditors), using=self.db) - - send(action="pre_remove") - self.filter(org_id=org.id).filter( - Q(user__in=users, role=ROLE.USER) | - Q(user__in=admins, role=ROLE.ADMIN) | - Q(user__in=auditors, role=ROLE.AUDITOR) - ).delete() - send(action="post_remove") - - def add_users_by_role(self, org, users=None, admins=None, auditors=None): - from users.models import User - - if not any((users, admins, auditors)): - return - users, admins, auditors = _none2list(users, admins, auditors) - - add_mapper = ( - (users, ROLE.USER), - (admins, ROLE.ADMIN), - (auditors, ROLE.AUDITOR) - ) - - oms_add = [] - for _users, _role in add_mapper: - for _user in _users: - if isinstance(_user, models.Model): - _user = _user.id - oms_add.append(self.model(org_id=org.id, user_id=_user, role=_role)) - - pk_set = _users2pks_if_need(users, admins, auditors) - send = partial( - signals.m2m_changed.send, sender=self.model, instance=org, reverse=False, - model=User, pk_set=pk_set, using=self.db - ) - - send(action='pre_add') - self.bulk_create(oms_add, ignore_conflicts=True) - send(action='post_add') - - def _get_remove_add_set(self, new_users, old_users): - if new_users is None: - return None, None - new_users = _convert_to_uuid_set(new_users) - return (old_users - new_users), (new_users - old_users) - - def set_user_roles(self, org, user, roles): - """ - 设置某个用户在某个组织里的角色 - """ - old_roles = set(self.filter(org_id=org.id, user=user).values_list('role', flat=True)) - new_roles = set(roles) - - roles_remove = old_roles - new_roles - roles_add = new_roles - old_roles - - to_remove = UserRoleMapper() - to_add = UserRoleMapper() - - for role in roles_remove: - if role in to_remove: - to_remove[role].add(user) - for role in roles_add: - if role in to_add: - to_add[role].add(user) - - # 先添加再移除 (防止用户角色由组织用户->组织管理员时从组织清除用户) - self.add_users_by_role( - org, - to_add.users, - to_add.admins, - to_add.auditors - ) - - self.remove_users_by_role( - org, - to_remove.users, - to_remove.admins, - to_remove.auditors - ) - - def set_users_by_role(self, org, users=None, admins=None, auditors=None): - """ - 给组织设置带角色的用户 - """ - - oms = self.filter(org_id=org.id).values_list('role', 'user_id') - - old_mapper = UserRoleMapper() - - for role, user_id in oms: - if role in old_mapper: - old_mapper[role].add(user_id) - - users_remove, users_add = self._get_remove_add_set(users, old_mapper.users) - admins_remove, admins_add = self._get_remove_add_set(admins, old_mapper.admins) - auditors_remove, auditors_add = self._get_remove_add_set(auditors, old_mapper.auditors) - - self.remove_users_by_role( - org, - users_remove, - admins_remove, - auditors_remove - ) - - self.add_users_by_role( - org, - users_add, - admins_add, - auditors_add - ) +# class OrgMemberManager(models.Manager): +# def remove_users(self, org, users): +# from users.models import User +# pk_set = [] +# for user in users: +# if hasattr(user, 'pk'): +# pk_set.append(user.pk) +# else: +# pk_set.append(user) +# +# send = partial( +# signals.m2m_changed.send, sender=self.model, +# instance=org, reverse=False, model=User, +# pk_set=pk_set, using=self.db +# ) +# send(action="pre_remove") +# self.filter(org_id=org.id, user_id__in=pk_set).delete() +# send(action="post_remove") class OrganizationMember(models.Model): @@ -431,16 +169,15 @@ class OrganizationMember(models.Model): id = models.UUIDField(default=uuid.uuid4, primary_key=True) org = models.ForeignKey(Organization, related_name='m2m_org_members', on_delete=models.CASCADE, verbose_name=_('Organization')) user = models.ForeignKey('users.User', related_name='m2m_org_members', on_delete=models.CASCADE, verbose_name=_('User')) - role = models.CharField(max_length=16, choices=ROLE.choices, default=ROLE.USER, verbose_name=_("Role")) + role = models.CharField(max_length=16, default='User', verbose_name=_("Role")) date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created")) date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated")) created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by')) - - objects = OrgMemberManager() + # objects = OrgMemberManager() class Meta: unique_together = [('org', 'user', 'role')] db_table = 'orgs_organization_members' def __str__(self): - return '{} is {}: {}'.format(self.user.name, self.org.name, self.role) + return '{} | {}'.format(self.user, self.org) diff --git a/apps/orgs/serializers.py b/apps/orgs/serializers.py index 24ef3e8be..0ae933044 100644 --- a/apps/orgs/serializers.py +++ b/apps/orgs/serializers.py @@ -1,12 +1,8 @@ -from django.db.models import F from rest_framework.serializers import ModelSerializer from rest_framework import serializers -from django.utils.translation import ugettext_lazy as _ -from users.models.user import User -from common.drf.serializers import BulkModelSerializer -from common.db.models import concated_display as display -from .models import Organization, OrganizationMember, ROLE +from .utils import get_current_org +from .models import Organization class ResourceStatisticsSerializer(serializers.Serializer): @@ -25,11 +21,7 @@ class ResourceStatisticsSerializer(serializers.Serializer): app_perms_amount = serializers.IntegerField(required=False) -class OrgSerializer(BulkModelSerializer): - users = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects.all(), write_only=True, required=False) - admins = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects.all(), write_only=True, required=False) - auditors = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects.all(), write_only=True, required=False) - +class OrgSerializer(ModelSerializer): resource_statistics = ResourceStatisticsSerializer(source='resource_statistics_cache', read_only=True) class Meta: @@ -38,104 +30,26 @@ class OrgSerializer(BulkModelSerializer): fields_small = fields_mini + [ 'resource_statistics', 'is_default', 'is_root', - 'date_created', + 'date_created', 'created_by', 'comment', 'created_by', ] - fields_m2m = ['users', 'admins', 'auditors'] + fields_m2m = [] fields = fields_small + fields_m2m read_only_fields = ['created_by', 'date_created'] - def create(self, validated_data): - members = self._pop_members(validated_data) - instance = Organization.objects.create(**validated_data) - OrganizationMember.objects.add_users_by_role(instance, *members) - return instance - - def _pop_members(self, validated_data): - return ( - validated_data.pop('users', None), - validated_data.pop('admins', None), - validated_data.pop('auditors', None) - ) - - def update(self, instance, validated_data): - members = self._pop_members(validated_data) - for attr, value in validated_data.items(): - setattr(instance, attr, value) - instance.save() - OrganizationMember.objects.set_users_by_role(instance, *members) - return instance - - -class OrgReadSerializer(OrgSerializer): - pass - - -class OrgMemberSerializer(BulkModelSerializer): - org_display = serializers.CharField(read_only=True) - user_display = serializers.CharField(read_only=True) - role_display = serializers.CharField(source='get_role_display', read_only=True) - - class Meta: - model = OrganizationMember - fields_mini = ['id'] - fields_small = fields_mini + [ - 'role', 'role_display' - ] - fields_fk = ['org', 'user', 'org_display', 'user_display',] - fields = fields_small + fields_fk - use_model_bulk_create = True - model_bulk_create_kwargs = {'ignore_conflicts': True} - - def get_unique_together_validators(self): - if self.parent: - return [] - return super().get_unique_together_validators() - - @classmethod - def setup_eager_loading(cls, queryset): - return queryset.annotate( - org_display=F('org__name'), - user_display=display('user__name', 'user__username') - ).distinct() - - -class OrgMemberOldBaseSerializer(BulkModelSerializer): - organization = serializers.PrimaryKeyRelatedField( - label=_('Organization'), queryset=Organization.objects.all(), required=True, source='org' - ) - - def to_internal_value(self, data): - view = self.context['view'] - org_id = view.kwargs.get('org_id') - if org_id: - data['organization'] = org_id - return super().to_internal_value(data) - - class Meta: - model = OrganizationMember - fields = ('id', 'organization', 'user', 'role') - - -class OrgMemberAdminSerializer(OrgMemberOldBaseSerializer): - role = serializers.HiddenField(default=ROLE.ADMIN) - - -class OrgMemberUserSerializer(OrgMemberOldBaseSerializer): - role = serializers.HiddenField(default=ROLE.USER) - - -class OrgRetrieveSerializer(OrgReadSerializer): - admins = serializers.PrimaryKeyRelatedField(many=True, read_only=True) - auditors = serializers.PrimaryKeyRelatedField(many=True, read_only=True) - users = serializers.PrimaryKeyRelatedField(many=True, read_only=True) - - class Meta(OrgReadSerializer.Meta): - pass - class CurrentOrgSerializer(ModelSerializer): class Meta: model = Organization fields = ['id', 'name', 'is_default', 'is_root', 'comment'] + + +class CurrentOrgDefault: + requires_context = False + + def __call__(self, *args): + return get_current_org() + + def __repr__(self): + return '%s()' % self.__class__.__name__ diff --git a/apps/orgs/signals_handler/cache.py b/apps/orgs/signals_handler/cache.py index 734ad7af0..1d9ca3891 100644 --- a/apps/orgs/signals_handler/cache.py +++ b/apps/orgs/signals_handler/cache.py @@ -1,15 +1,13 @@ -from django.db.models.signals import m2m_changed from django.db.models.signals import post_save, pre_delete, pre_save, post_delete from django.dispatch import receiver -from orgs.models import Organization, OrganizationMember +from orgs.models import Organization from assets.models import Node from perms.models import (AssetPermission, ApplicationPermission) from users.models import UserGroup, User from applications.models import Application from terminal.models import Session -from assets.models import Asset, AdminUser, SystemUser, Domain, Gateway -from common.const.signals import POST_PREFIX +from assets.models import Asset, SystemUser, Domain, Gateway from orgs.caches import OrgResourceStatisticsCache @@ -32,20 +30,20 @@ def on_user_delete_refresh_cache(sender, instance, **kwargs): refresh_user_amount_on_user_create_or_delete(instance.id) -@receiver(m2m_changed, sender=OrganizationMember) -def on_org_user_changed_refresh_cache(sender, action, instance, reverse, pk_set, **kwargs): - if not action.startswith(POST_PREFIX): - return - - if reverse: - orgs = Organization.objects.filter(id__in=pk_set) - else: - orgs = [instance] - - for org in orgs: - org_cache = OrgResourceStatisticsCache(org) - org_cache.expire('users_amount') - OrgResourceStatisticsCache(Organization.root()).expire('users_amount') +# @receiver(m2m_changed, sender=OrganizationMember) +# def on_org_user_changed_refresh_cache(sender, action, instance, reverse, pk_set, **kwargs): +# if not action.startswith(POST_PREFIX): +# return +# +# if reverse: +# orgs = Organization.objects.filter(id__in=pk_set) +# else: +# orgs = [instance] +# +# for org in orgs: +# org_cache = OrgResourceStatisticsCache(org) +# org_cache.expire('users_amount') +# OrgResourceStatisticsCache(Organization.root()).expire('users_amount') class OrgResourceStatisticsRefreshUtil: diff --git a/apps/orgs/signals_handler/common.py b/apps/orgs/signals_handler/common.py index f49b32f19..84a807a87 100644 --- a/apps/orgs/signals_handler/common.py +++ b/apps/orgs/signals_handler/common.py @@ -10,7 +10,7 @@ from django.db.models.signals import m2m_changed from django.db.models.signals import post_save, pre_delete from orgs.utils import tmp_to_org -from orgs.models import Organization, OrganizationMember +from orgs.models import Organization from orgs.hands import set_current_org, Node, get_current_org from perms.models import (AssetPermission, ApplicationPermission) from users.models import UserGroup, User @@ -20,6 +20,7 @@ from common.signals import django_ready from common.utils import get_logger from common.utils.connection import RedisPubSub from assets.models import CommandFilterRule +from users.signals import post_user_leave_org logger = get_logger(__file__) @@ -55,6 +56,7 @@ def subscribe_orgs_mapping_expire(sender, **kwargs): t.start() +# 创建对应的root @receiver(post_save, sender=Organization) def on_org_create_or_update(sender, instance, created=False, **kwargs): # 必须放到最开始, 因为下面调用Node.save方法时会获取当前组织的org_id(即instance.org_id), 如果不过期会找不到 @@ -72,9 +74,6 @@ def on_org_create_or_update(sender, instance, created=False, **kwargs): def on_org_delete(sender, instance, **kwargs): expire_orgs_mapping_for_memory(instance.id) - -@receiver(pre_delete, sender=Organization) -def on_org_delete(sender, instance, **kwargs): # 删除该组织下所有 节点 with tmp_to_org(instance): root_node = Node.org_root() @@ -144,25 +143,6 @@ def _clear_users_from_org(org, users): _remove_users(CommandFilterRule, users, org, user_field_name='reviewers') -@receiver(m2m_changed, sender=OrganizationMember) -def on_org_user_changed(action, instance, reverse, pk_set, **kwargs): - if action == 'post_remove': - if reverse: - user = instance - org_pk_set = pk_set - - orgs = Organization.objects.filter(id__in=org_pk_set) - for org in orgs: - if not org.members.filter(id=user.id).exists(): - _clear_users_from_org(org, user) - else: - org = instance - user_pk_set = pk_set - - leaved_users = set(pk_set) - set(org.members.filter(id__in=user_pk_set).values_list('id', flat=True)) - _clear_users_from_org(org, leaved_users) - - @receiver(post_save, sender=User) @on_transaction_commit def on_user_created_set_default_org(sender, instance, created, **kwargs): @@ -171,3 +151,9 @@ def on_user_created_set_default_org(sender, instance, created, **kwargs): if instance.orgs.count() > 0: return Organization.default().members.add(instance) + + +@receiver(post_user_leave_org) +def on_user_leave_org(sender, user=None, org=None, **kwargs): + logger.debug('User leave org signal recv: {} <> {}'.format(user, org)) + _clear_users_from_org(org, [user]) diff --git a/apps/orgs/urls/api_urls.py b/apps/orgs/urls/api_urls.py index 61fad175e..e064d226f 100644 --- a/apps/orgs/urls/api_urls.py +++ b/apps/orgs/urls/api_urls.py @@ -11,13 +11,6 @@ app_name = 'orgs' router = BulkRouter() router.register(r'orgs', api.OrgViewSet, 'org') -router.register(r'org-member-relation', api.OrgMemberRelationBulkViewSet, 'org-member-relation') - -router.register(r'orgs/(?P[0-9a-zA-Z\-]{36})/membership/admins', - api.OrgMemberAdminRelationBulkViewSet, 'membership-admins') -router.register(r'orgs/(?P[0-9a-zA-Z\-]{36})/membership/users', - api.OrgMemberUserRelationBulkViewSet, 'membership-users'), - urlpatterns = [ path('orgs/current/', api.CurrentOrgDetailApi.as_view(), name='current-org-detail'), diff --git a/apps/perms/api/application/application_permission_relation.py b/apps/perms/api/application/application_permission_relation.py index 770463f7e..1266662d3 100644 --- a/apps/perms/api/application/application_permission_relation.py +++ b/apps/perms/api/application/application_permission_relation.py @@ -9,7 +9,6 @@ from applications.models import Application from orgs.mixins.api import OrgRelationMixin from orgs.mixins.api import OrgBulkModelViewSet from orgs.utils import current_org -from common.permissions import IsOrgAdmin from perms import serializers from perms import models @@ -36,7 +35,6 @@ class RelationMixin(OrgRelationMixin, OrgBulkModelViewSet): class ApplicationPermissionUserRelationViewSet(RelationMixin): serializer_class = serializers.ApplicationPermissionUserRelationSerializer m2m_field = models.ApplicationPermission.users.field - permission_classes = (IsOrgAdmin,) filterset_fields = [ 'id', "user", "applicationpermission", ] @@ -51,7 +49,6 @@ class ApplicationPermissionUserRelationViewSet(RelationMixin): class ApplicationPermissionUserGroupRelationViewSet(RelationMixin): serializer_class = serializers.ApplicationPermissionUserGroupRelationSerializer m2m_field = models.ApplicationPermission.user_groups.field - permission_classes = (IsOrgAdmin,) filterset_fields = [ 'id', "usergroup", "applicationpermission" ] @@ -66,7 +63,6 @@ class ApplicationPermissionUserGroupRelationViewSet(RelationMixin): class ApplicationPermissionApplicationRelationViewSet(RelationMixin): serializer_class = serializers.ApplicationPermissionApplicationRelationSerializer m2m_field = models.ApplicationPermission.applications.field - permission_classes = (IsOrgAdmin,) filterset_fields = [ 'id', 'application', 'applicationpermission', ] @@ -81,7 +77,6 @@ class ApplicationPermissionApplicationRelationViewSet(RelationMixin): class ApplicationPermissionSystemUserRelationViewSet(RelationMixin): serializer_class = serializers.ApplicationPermissionSystemUserRelationSerializer m2m_field = models.ApplicationPermission.system_users.field - permission_classes = (IsOrgAdmin,) filterset_fields = [ 'id', 'systemuser', 'applicationpermission', ] @@ -100,7 +95,6 @@ class ApplicationPermissionSystemUserRelationViewSet(RelationMixin): class ApplicationPermissionAllApplicationListApi(generics.ListAPIView): - permission_classes = (IsOrgAdmin,) serializer_class = serializers.ApplicationPermissionAllApplicationSerializer only_fields = serializers.ApplicationPermissionAllApplicationSerializer.Meta.only_fields filterset_fields = ('name',) @@ -115,7 +109,6 @@ class ApplicationPermissionAllApplicationListApi(generics.ListAPIView): class ApplicationPermissionAllUserListApi(generics.ListAPIView): - permission_classes = (IsOrgAdmin,) serializer_class = serializers.ApplicationPermissionAllUserSerializer only_fields = serializers.ApplicationPermissionAllUserSerializer.Meta.only_fields filterset_fields = ('username', 'name') diff --git a/apps/perms/api/application/user_group_permission.py b/apps/perms/api/application/user_group_permission.py index 34c3eefaa..0237227ec 100644 --- a/apps/perms/api/application/user_group_permission.py +++ b/apps/perms/api/application/user_group_permission.py @@ -4,7 +4,6 @@ from django.db.models import Q from rest_framework.generics import ListAPIView -from common.permissions import IsOrgAdminOrAppUser from common.mixins.api import CommonApiMixin from applications.models import Application from perms import serializers @@ -18,11 +17,13 @@ class UserGroupGrantedApplicationsApi(CommonApiMixin, ListAPIView): """ 获取用户组直接授权的应用 """ - permission_classes = (IsOrgAdminOrAppUser,) serializer_class = serializers.AppGrantedSerializer only_fields = serializers.AppGrantedSerializer.Meta.only_fields filterset_fields = ['id', 'name', 'category', 'type', 'comment'] search_fields = ['name', 'comment'] + rbac_perms = { + 'list': 'perms.view_applicationpermission' + } def get_queryset(self): user_group_id = self.kwargs.get('pk', '') diff --git a/apps/perms/api/application/user_permission/common.py b/apps/perms/api/application/user_permission/common.py index 707e2ca72..e5787c004 100644 --- a/apps/perms/api/application/user_permission/common.py +++ b/apps/perms/api/application/user_permission/common.py @@ -16,8 +16,7 @@ from perms.utils.application.permission import ( get_application_system_user_ids, validate_permission, ) -from perms.api.asset.user_permission.mixin import RoleAdminMixin, RoleUserMixin -from common.permissions import IsOrgAdminOrAppUser +from .mixin import RoleAdminMixin, RoleUserMixin from perms.hands import User, SystemUser from perms import serializers @@ -56,7 +55,9 @@ class MyGrantedApplicationSystemUsersApi(RoleUserMixin, GrantedApplicationSystem @method_decorator(tmp_to_root_org(), name='get') class ValidateUserApplicationPermissionApi(APIView): - permission_classes = (IsOrgAdminOrAppUser,) + rbac_perms = { + 'GET': 'view_applicationpermission' + } def get(self, request, *args, **kwargs): user_id = request.query_params.get('user_id', '') diff --git a/apps/perms/api/application/user_permission/mixin.py b/apps/perms/api/application/user_permission/mixin.py new file mode 100644 index 000000000..c0bd07928 --- /dev/null +++ b/apps/perms/api/application/user_permission/mixin.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# + +from common.mixins.api import RoleAdminMixin as _RoleAdminMixin +from common.mixins.api import RoleUserMixin as _RoleUserMixin +from orgs.utils import tmp_to_root_org + + +class RoleAdminMixin(_RoleAdminMixin): + rbac_perms = ( + ('list', 'perms.view_userapp'), + ('retrieve', 'perms.view_userapps'), + ('get_tree', 'perms.view_userapps'), + ('GET', 'perms.view_userapps'), + ) + + +class RoleUserMixin(_RoleUserMixin): + rbac_perms = ( + ('list', 'perms.view_myapps'), + ('retrieve', 'perms.view_myapps'), + ('get_tree', 'perms.view_myapps'), + ('GET', 'perms.view_myapps'), + ) + + def get(self, request, *args, **kwargs): + with tmp_to_root_org(): + return super().get(request, *args, **kwargs) diff --git a/apps/perms/api/asset/asset_permission.py b/apps/perms/api/asset/asset_permission.py index 7b3be59c1..afadc456c 100644 --- a/apps/perms/api/asset/asset_permission.py +++ b/apps/perms/api/asset/asset_permission.py @@ -4,19 +4,15 @@ from perms.filters import AssetPermissionFilter from perms.models import AssetPermission from orgs.mixins.api import OrgBulkModelViewSet from perms import serializers -from common.permissions import IsOrgAdmin -__all__ = [ - 'AssetPermissionViewSet', -] +__all__ = ['AssetPermissionViewSet'] class AssetPermissionViewSet(OrgBulkModelViewSet): """ 资产授权列表的增删改查api """ - permission_classes = (IsOrgAdmin,) model = AssetPermission serializer_class = serializers.AssetPermissionSerializer filterset_class = AssetPermissionFilter diff --git a/apps/perms/api/asset/asset_permission_relation.py b/apps/perms/api/asset/asset_permission_relation.py index ed3c30964..1d27d2870 100644 --- a/apps/perms/api/asset/asset_permission_relation.py +++ b/apps/perms/api/asset/asset_permission_relation.py @@ -2,15 +2,12 @@ # from rest_framework import generics from django.db.models import F, Value -from django.db.models import Q from django.db.models.functions import Concat from django.shortcuts import get_object_or_404 -from assets.models import Node, Asset from orgs.mixins.api import OrgRelationMixin from orgs.mixins.api import OrgBulkModelViewSet from orgs.utils import current_org -from common.permissions import IsOrgAdmin from perms import serializers from perms import models from perms.utils.asset.user_permission import UserGrantedAssetsQueryUtils @@ -36,7 +33,6 @@ class RelationMixin(OrgRelationMixin, OrgBulkModelViewSet): class AssetPermissionUserRelationViewSet(RelationMixin): serializer_class = serializers.AssetPermissionUserRelationSerializer m2m_field = models.AssetPermission.users.field - permission_classes = (IsOrgAdmin,) filterset_fields = [ 'id', "user", "assetpermission", ] @@ -50,7 +46,6 @@ class AssetPermissionUserRelationViewSet(RelationMixin): class AssetPermissionAllUserListApi(generics.ListAPIView): - permission_classes = (IsOrgAdmin,) serializer_class = serializers.AssetPermissionAllUserSerializer filterset_fields = ("username", "name") search_fields = filterset_fields @@ -67,7 +62,6 @@ class AssetPermissionAllUserListApi(generics.ListAPIView): class AssetPermissionUserGroupRelationViewSet(RelationMixin): serializer_class = serializers.AssetPermissionUserGroupRelationSerializer m2m_field = models.AssetPermission.user_groups.field - permission_classes = (IsOrgAdmin,) filterset_fields = [ 'id', "usergroup", "assetpermission" ] @@ -83,7 +77,6 @@ class AssetPermissionUserGroupRelationViewSet(RelationMixin): class AssetPermissionAssetRelationViewSet(RelationMixin): serializer_class = serializers.AssetPermissionAssetRelationSerializer m2m_field = models.AssetPermission.assets.field - permission_classes = (IsOrgAdmin,) filterset_fields = [ 'id', 'asset', 'assetpermission', ] @@ -97,7 +90,6 @@ class AssetPermissionAssetRelationViewSet(RelationMixin): class AssetPermissionAllAssetListApi(generics.ListAPIView): - permission_classes = (IsOrgAdmin,) serializer_class = serializers.AssetPermissionAllAssetSerializer filterset_fields = ("hostname", "ip") search_fields = filterset_fields @@ -112,7 +104,6 @@ class AssetPermissionAllAssetListApi(generics.ListAPIView): class AssetPermissionNodeRelationViewSet(RelationMixin): serializer_class = serializers.AssetPermissionNodeRelationSerializer m2m_field = models.AssetPermission.nodes.field - permission_classes = (IsOrgAdmin,) filterset_fields = [ 'id', 'node', 'assetpermission', ] @@ -128,7 +119,6 @@ class AssetPermissionNodeRelationViewSet(RelationMixin): class AssetPermissionSystemUserRelationViewSet(RelationMixin): serializer_class = serializers.AssetPermissionSystemUserRelationSerializer m2m_field = models.AssetPermission.system_users.field - permission_classes = (IsOrgAdmin,) filterset_fields = [ 'id', 'systemuser', 'assetpermission', ] diff --git a/apps/perms/api/asset/user_group_permission.py b/apps/perms/api/asset/user_group_permission.py index e8cea21b5..5be59efe2 100644 --- a/apps/perms/api/asset/user_group_permission.py +++ b/apps/perms/api/asset/user_group_permission.py @@ -6,7 +6,6 @@ from django.db.models import Q from rest_framework.generics import ListAPIView from rest_framework.response import Response -from common.permissions import IsOrgAdminOrAppUser from common.utils import lazyproperty from perms.models import AssetPermission from assets.models import Asset, Node @@ -32,11 +31,13 @@ class UserGroupMixin: class UserGroupGrantedAssetsApi(ListAPIView): - permission_classes = (IsOrgAdminOrAppUser,) serializer_class = serializers.AssetGrantedSerializer only_fields = serializers.AssetGrantedSerializer.Meta.only_fields filterset_fields = ['hostname', 'ip', 'id', 'comment'] search_fields = ['hostname', 'ip', 'comment'] + rbac_perms = { + 'list': 'perms.view_userassets' + } def get_queryset(self): user_group_id = self.kwargs.get('pk', '') @@ -65,11 +66,13 @@ class UserGroupGrantedAssetsApi(ListAPIView): class UserGroupGrantedNodeAssetsApi(ListAPIView): - permission_classes = (IsOrgAdminOrAppUser,) serializer_class = serializers.AssetGrantedSerializer only_fields = serializers.AssetGrantedSerializer.Meta.only_fields filterset_fields = ['hostname', 'ip', 'id', 'comment'] search_fields = ['hostname', 'ip', 'comment'] + rbac_perms = { + 'list': 'perms.view_userassets' + } def get_queryset(self): if getattr(self, 'swagger_fake_view', False): @@ -119,7 +122,9 @@ class UserGroupGrantedNodeAssetsApi(ListAPIView): class UserGroupGrantedNodesApi(ListAPIView): serializer_class = serializers.NodeGrantedSerializer - permission_classes = (IsOrgAdminOrAppUser,) + rbac_perms = { + 'list': 'view_userassets' + } def get_queryset(self): user_group_id = self.kwargs.get('pk', '') @@ -131,7 +136,9 @@ class UserGroupGrantedNodesApi(ListAPIView): class UserGroupGrantedNodeChildrenAsTreeApi(SerializeToTreeNodeMixin, ListAPIView): - permission_classes = (IsOrgAdminOrAppUser,) + rbac_perms = { + 'list': 'view_userassets' + } def get_children_nodes(self, parent_key): return Node.objects.filter(parent_key=parent_key) diff --git a/apps/perms/api/asset/user_permission/common.py b/apps/perms/api/asset/user_permission/common.py index e874d591f..c5ca3a711 100644 --- a/apps/perms/api/asset/user_permission/common.py +++ b/apps/perms/api/asset/user_permission/common.py @@ -13,7 +13,7 @@ from rest_framework.generics import ( from orgs.utils import tmp_to_root_org from perms.utils.asset.permission import get_asset_system_user_ids_with_actions_by_user, validate_permission -from common.permissions import IsOrgAdminOrAppUser, IsOrgAdmin, IsValidUser +from common.permissions import IsValidUser from common.utils import get_logger, lazyproperty from perms.hands import User, Asset, SystemUser @@ -33,8 +33,10 @@ __all__ = [ @method_decorator(tmp_to_root_org(), name='get') class GetUserAssetPermissionActionsApi(RetrieveAPIView): - permission_classes = (IsOrgAdminOrAppUser,) serializer_class = serializers.ActionsSerializer + rbac_perms = { + 'retrieve': 'perms.view_userassets' + } def get_user(self): user_id = self.request.query_params.get('user_id', '') @@ -61,10 +63,9 @@ class GetUserAssetPermissionActionsApi(RetrieveAPIView): @method_decorator(tmp_to_root_org(), name='get') class ValidateUserAssetPermissionApi(APIView): - permission_classes = (IsOrgAdminOrAppUser,) - - def get_cache_policy(self): - return 0 + rbac_perms = { + 'GET': 'perms.view_userassets' + } def get(self, request, *args, **kwargs): user_id = self.request.query_params.get('user_id', '') @@ -97,16 +98,16 @@ class ValidateUserAssetPermissionApi(APIView): # TODO 删除 class RefreshAssetPermissionCacheApi(RetrieveAPIView): - permission_classes = (IsOrgAdmin,) - def retrieve(self, request, *args, **kwargs): return Response({'msg': True}, status=200) class UserGrantedAssetSystemUsersForAdminApi(ListAPIView): - permission_classes = (IsOrgAdminOrAppUser,) serializer_class = serializers.AssetSystemUserSerializer only_fields = serializers.AssetSystemUserSerializer.Meta.only_fields + rbac_perms = { + 'list': 'perms.view_userassets' + } @lazyproperty def user(self): @@ -142,7 +143,5 @@ class MyGrantedAssetSystemUsersApi(UserGrantedAssetSystemUsersForAdminApi): # TODO 删除 class UserAssetPermissionsCacheApi(DestroyAPIView): - permission_classes = (IsOrgAdmin,) - def destroy(self, request, *args, **kwargs): return Response(status=204) diff --git a/apps/perms/api/asset/user_permission/mixin.py b/apps/perms/api/asset/user_permission/mixin.py index c0f25b8a4..a0fe97ec2 100644 --- a/apps/perms/api/asset/user_permission/mixin.py +++ b/apps/perms/api/asset/user_permission/mixin.py @@ -2,7 +2,6 @@ # from rest_framework.request import Request -from common.permissions import IsOrgAdminOrAppUser, IsValidUser from common.http import is_true from common.mixins.api import RoleAdminMixin as _RoleAdminMixin from common.mixins.api import RoleUserMixin as _RoleUserMixin @@ -22,11 +21,21 @@ class PermBaseMixin: class RoleAdminMixin(PermBaseMixin, _RoleAdminMixin): - permission_classes = (IsOrgAdminOrAppUser,) + rbac_perms = ( + ('list', 'perms.view_userassets'), + ('retrieve', 'perms.view_userassets'), + ('get_tree', 'perms.view_userassets'), + ('GET', 'perms.view_userassets'), + ) class RoleUserMixin(PermBaseMixin, _RoleUserMixin): - permission_classes = (IsValidUser,) + rbac_perms = ( + ('list', 'perms.view_myassets'), + ('retrieve', 'perms.view_myassets'), + ('get_tree', 'perms.view_myassets'), + ('GET', 'perms.view_myassets'), + ) def get(self, request, *args, **kwargs): with tmp_to_root_org(): diff --git a/apps/perms/api/asset/user_permission/user_permission_nodes.py b/apps/perms/api/asset/user_permission/user_permission_nodes.py index 2a5cfacf2..558b888c2 100644 --- a/apps/perms/api/asset/user_permission/user_permission_nodes.py +++ b/apps/perms/api/asset/user_permission/user_permission_nodes.py @@ -113,7 +113,9 @@ class UserGrantedNodeChildrenAsTreeForAdminApi(RoleAdminMixin, UserGrantedNodeCh class MyGrantedNodeChildrenAsTreeApi(RoleUserMixin, UserGrantedNodeChildrenMixin, BaseNodeChildrenAsTreeApi): - pass + def get_permissions(self): + permissions = super().get_permissions() + return permissions class UserGrantedNodesForAdminApi(RoleAdminMixin, UserGrantedNodesMixin, BaseGrantedNodeApi): diff --git a/apps/perms/api/base.py b/apps/perms/api/base.py index 8c0028baf..0e285796e 100644 --- a/apps/perms/api/base.py +++ b/apps/perms/api/base.py @@ -1,5 +1,4 @@ from django.db.models import Q -from common.permissions import IsOrgAdmin from common.utils import get_object_or_none from orgs.mixins.api import OrgBulkModelViewSet from assets.models import SystemUser @@ -14,7 +13,6 @@ class BasePermissionViewSet(OrgBulkModelViewSet): 'user_id', 'username', 'system_user_id', 'system_user', 'user_group_id', 'user_group' ] - permission_classes = (IsOrgAdmin,) def filter_valid(self, queryset): valid_query = self.request.query_params.get('is_valid', None) diff --git a/apps/perms/apps.py b/apps/perms/apps.py index d6ce1f752..664765029 100644 --- a/apps/perms/apps.py +++ b/apps/perms/apps.py @@ -1,10 +1,12 @@ from __future__ import unicode_literals from django.apps import AppConfig +from django.utils.translation import ugettext_lazy as _ class PermsConfig(AppConfig): name = 'perms' + verbose_name = _('Permissions') def ready(self): super().ready() diff --git a/apps/perms/migrations/0021_auto_20220104_1537.py b/apps/perms/migrations/0021_auto_20220104_1537.py new file mode 100644 index 000000000..e74afff2a --- /dev/null +++ b/apps/perms/migrations/0021_auto_20220104_1537.py @@ -0,0 +1,21 @@ +# Generated by Django 3.1.13 on 2022-01-04 07:37 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('perms', '0020_auto_20210910_1103'), + ] + + operations = [ + migrations.AlterModelOptions( + name='applicationpermission', + options={'ordering': ('name',), 'permissions': [('view_myapps', 'Can view my apps'), ('connect_myapps', 'Can connect my apps'), ('view_userapps', 'Can view user apps'), ('view_usergroupapps', 'Can view usergroup apps')], 'verbose_name': 'Application permission'}, + ), + migrations.AlterModelOptions( + name='assetpermission', + options={'ordering': ('name',), 'permissions': [('view_myassets', 'Can view my assets'), ('connect_myassets', 'Can connect my assets'), ('view_userassets', 'Can view user assets'), ('view_usergroupassets', 'Can view usergroup assets')], 'verbose_name': 'Asset permission'}, + ), + ] diff --git a/apps/perms/models/application_permission.py b/apps/perms/models/application_permission.py index bec5f3da5..5f8a94b45 100644 --- a/apps/perms/models/application_permission.py +++ b/apps/perms/models/application_permission.py @@ -36,6 +36,12 @@ class ApplicationPermission(BasePermission): unique_together = [('org_id', 'name')] verbose_name = _('Application permission') ordering = ('name',) + permissions = [ + ('view_myapps', _('Can view my apps')), + ('connect_myapps', _('Can connect my apps')), + ('view_userapps', _('Can view user apps')), + ('view_usergroupapps', _('Can view usergroup apps')), + ] @property def category_remote_app(self): diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index 7947449e7..596164461 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -29,6 +29,12 @@ class AssetPermission(BasePermission): unique_together = [('org_id', 'name')] verbose_name = _("Asset permission") ordering = ('name',) + permissions = [ + ('view_myassets', _('Can view my assets')), + ('connect_myassets', _('Can connect my assets')), + ('view_userassets', _('Can view user assets')), + ('view_usergroupassets', _('Can view usergroup assets')), + ] @lazyproperty def users_amount(self): diff --git a/apps/rbac/__init__.py b/apps/rbac/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/rbac/admin.py b/apps/rbac/admin.py new file mode 100644 index 000000000..8c38f3f3d --- /dev/null +++ b/apps/rbac/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/rbac/api/__init__.py b/apps/rbac/api/__init__.py new file mode 100644 index 000000000..894655045 --- /dev/null +++ b/apps/rbac/api/__init__.py @@ -0,0 +1,4 @@ +from .permission import * +from .role import * +from .rolebinding import * + diff --git a/apps/rbac/api/permission.py b/apps/rbac/api/permission.py new file mode 100644 index 000000000..2589975dd --- /dev/null +++ b/apps/rbac/api/permission.py @@ -0,0 +1,54 @@ +from rest_framework.response import Response +from rest_framework.decorators import action +from django.shortcuts import get_object_or_404 + +from common.tree import TreeNodeSerializer +from common.drf.api import JMSModelViewSet +from ..models import Permission, Role +from ..serializers import PermissionSerializer + +__all__ = ['PermissionViewSet'] + + +class PermissionViewSet(JMSModelViewSet): + filterset_fields = ['codename'] + serializer_classes = { + 'get_tree': TreeNodeSerializer, + 'default': PermissionSerializer + } + scope = 'org' + check_disabled = False + http_method_names = ['get', 'option', 'head'] + + @action(methods=['GET'], detail=False, url_path='tree') + def get_tree(self, request, *args, **kwargs): + queryset = self.filter_queryset(self.get_queryset()).distinct() + tree_nodes = Permission.create_tree_nodes( + queryset, scope=self.scope, check_disabled=self.check_disabled + ) + serializer = self.get_serializer(tree_nodes, many=True) + return Response(serializer.data) + + def get_queryset(self): + self.scope = self.request.query_params.get('scope') or 'org' + role_id = self.request.query_params.get('role') + + if role_id: + role = get_object_or_404(Role, pk=role_id) + self.scope = role.scope + queryset = role.get_permissions() + else: + queryset = Permission.get_permissions(self.scope) + queryset = queryset.prefetch_related('content_type') + return queryset + + +# class UserPermsApi(ListAPIView): +# serializer_class = UserPermsSerializer +# permission_classes = (IsValidUser,) +# +# def list(self, request, *args, **kwargs): +# perms = RoleBinding.get_user_perms(request.user) +# serializer = super().get_serializer(data={'perms': perms}) +# return Res + diff --git a/apps/rbac/api/role.py b/apps/rbac/api/role.py new file mode 100644 index 000000000..251f7eeb7 --- /dev/null +++ b/apps/rbac/api/role.py @@ -0,0 +1,77 @@ +from django.db.models import Count +from django.utils.translation import ugettext as _ +from rest_framework.exceptions import PermissionDenied +from rest_framework.decorators import action + +from common.drf.api import JMSModelViewSet +from ..serializers import RoleSerializer, RoleUserSerializer +from ..models import Role, SystemRole, OrgRole +from .permission import PermissionViewSet + +__all__ = [ + 'RoleViewSet', 'RolePermissionsViewSet', + 'SystemRoleViewSet', 'OrgRoleViewSet' +] + + +class RoleViewSet(JMSModelViewSet): + queryset = Role.objects.all() + serializer_classes = { + 'default': RoleSerializer, + 'users': RoleUserSerializer, + } + filterset_fields = ['name', 'scope', 'builtin'] + search_fields = filterset_fields + rbac_perms = { + 'users': 'rbac.view_rolebinding' + } + + def perform_destroy(self, instance): + if instance.builtin: + error = _("Internal role, can't be destroy") + raise PermissionDenied(error) + return super().perform_destroy(instance) + + def perform_update(self, serializer): + instance = serializer.instance + if instance.builtin: + error = _("Internal role, can't be update") + raise PermissionDenied(error) + return super().perform_update(serializer) + + def get_queryset(self): + queryset = super().get_queryset()\ + .annotate(permissions_amount=Count('permissions')) + return queryset + + @action(methods=['GET'], detail=True) + def users(self, *args, **kwargs): + role = self.get_object() + queryset = role.users + return self.get_paginated_response_with_query_set(queryset) + + +class SystemRoleViewSet(RoleViewSet): + queryset = SystemRole.objects.all() + + +class OrgRoleViewSet(RoleViewSet): + queryset = OrgRole.objects.all() + + +# Sub view set +class RolePermissionsViewSet(PermissionViewSet): + rbac_perms = ( + ('get_tree', 'role.view_role'), + ) + http_method_names = ['get', 'option'] + check_disabled = False + + def get_queryset(self): + role_id = self.kwargs.get('role_pk') + role = Role.objects.get(id=role_id) + self.scope = role.scope + self.check_disabled = role.builtin + queryset = role.get_permissions()\ + .prefetch_related('content_type') + return queryset diff --git a/apps/rbac/api/rolebinding.py b/apps/rbac/api/rolebinding.py new file mode 100644 index 000000000..ad24f43bf --- /dev/null +++ b/apps/rbac/api/rolebinding.py @@ -0,0 +1,52 @@ + +from django.db.models import F, Value +from django.db.models.functions import Concat + +from orgs.mixins.api import OrgBulkModelViewSet +from orgs.utils import current_org +from .. import serializers +from ..models import RoleBinding, SystemRoleBinding, OrgRoleBinding + +__all__ = ['RoleBindingViewSet', 'SystemRoleBindingViewSet', 'OrgRoleBindingViewSet'] + + +class RoleBindingViewSet(OrgBulkModelViewSet): + model = RoleBinding + serializer_class = serializers.RoleBindingSerializer + filterset_fields = [ + 'scope', 'user', 'role', 'org', + 'user__name', 'user__username', 'role__name' + ] + search_fields = [ + 'user__name', 'user__username', 'role__name' + ] + + def get_queryset(self): + queryset = super().get_queryset()\ + .prefetch_related('user', 'role') \ + .annotate( + user_display=Concat( + F('user__name'), Value('('), + F('user__username'), Value(')') + ), + role_display=F('role__name') + ) + return queryset + + +class SystemRoleBindingViewSet(RoleBindingViewSet): + model = SystemRoleBinding + serializer_class = serializers.SystemRoleBindingSerializer + + +class OrgRoleBindingViewSet(RoleBindingViewSet): + model = OrgRoleBinding + serializer_class = serializers.OrgRoleBindingSerializer + + def perform_bulk_create(self, serializer): + validated_data = serializer.validated_data + bindings = [ + OrgRoleBinding(role=d['role'], user=d['user'], org_id=current_org.id, scope='org') + for d in validated_data + ] + OrgRoleBinding.objects.bulk_create(bindings, ignore_conflicts=True) diff --git a/apps/rbac/apps.py b/apps/rbac/apps.py new file mode 100644 index 000000000..f9cc34f88 --- /dev/null +++ b/apps/rbac/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig +from django.utils.translation import ugettext_lazy as _ + + +class RBACConfig(AppConfig): + name = 'rbac' + verbose_name = _('RBAC') diff --git a/apps/rbac/backends.py b/apps/rbac/backends.py new file mode 100644 index 000000000..8796d5dfc --- /dev/null +++ b/apps/rbac/backends.py @@ -0,0 +1,13 @@ +from django.contrib.auth.backends import ModelBackend + + +class RBACBackend(ModelBackend): + def has_perm(self, user_obj, perm, obj=None): + if not user_obj.is_active: + return False + + has_perm = perm in user_obj.perms + return has_perm + # + # def has_module_perms(self, user_obj, app_label): + # return True diff --git a/apps/rbac/builtin.py b/apps/rbac/builtin.py new file mode 100644 index 000000000..130b8e334 --- /dev/null +++ b/apps/rbac/builtin.py @@ -0,0 +1,134 @@ +from django.utils.translation import ugettext_noop + +from .const import Scope, system_exclude_permissions, org_exclude_permissions + + +auditor_perms = ( + ('audits', '*', '*', '*'), + ('rbac', 'menupermission', 'view', 'auditview'), + ('terminal', 'session', '*', '*'), + ('terminal', 'command', '*', '*'), +) + +user_perms = ( + ('rbac', 'menupermission', 'view', 'userview'), + ('perms', 'assetpermission', 'view,connect', 'myassets'), + ('perms', 'applicationpermission', 'view,connect', 'myapps'), +) + +app_exclude_perms = [ + ('users', 'user', 'add,delete', 'user'), + ('orgs', 'org', 'add,delete,change', 'org'), + ('rbac', '*', '*', '*'), +] + +need_check = [ + *auditor_perms, *user_perms, *app_exclude_perms, + *system_exclude_permissions, *org_exclude_permissions +] +defines_errors = [d for d in need_check if len(d) != 4] +if len(defines_errors) != 0: + raise ValueError('Perms define error: {}'.format(defines_errors)) + + +class PreRole: + id_prefix = '00000000-0000-0000-0000-00000000000' + + def __init__(self, index, name, scope, perms, perms_type='include'): + self.id = self.id_prefix + index + self.name = name + self.scope = scope + self.perms = perms + self.perms_type = perms_type + + def get_role(self): + from rbac.models import Role + return Role.objects.get(id=self.id) + + def get_defaults(self): + from rbac.models import Permission + q = Permission.get_define_permissions_q(self.perms) + permissions = Permission.get_permissions(self.scope) + + if not q: + permissions = permissions.none() + elif self.perms_type == 'include': + permissions = permissions.filter(q) + else: + permissions = permissions.exclude(q) + + perms = permissions.values_list('id', flat=True) + defaults = { + 'id': self.id, 'name': self.name, 'scope': self.scope, + 'builtin': True, 'permissions': perms + } + return defaults + + def get_or_create_role(self): + from rbac.models import Role + defaults = self.get_defaults() + permissions = defaults.pop('permissions', []) + role, created = Role.objects.get_or_create(defaults, id=self.id) + role.permissions.set(permissions) + return role, created + + +class BuiltinRole: + system_admin = PreRole( + '1', ugettext_noop('SystemAdmin'), Scope.system, [] + ) + system_auditor = PreRole( + '2', ugettext_noop('SystemAuditor'), Scope.system, auditor_perms + ) + system_app = PreRole( + '4', ugettext_noop('SystemApp'), Scope.system, app_exclude_perms, 'exclude' + ) + system_user = PreRole( + '3', ugettext_noop('User'), Scope.system, [] + ) + org_admin = PreRole( + '5', ugettext_noop('OrgAdmin'), Scope.org, [] + ) + org_auditor = PreRole( + '6', ugettext_noop('OrgAuditor'), Scope.org, auditor_perms + ) + org_user = PreRole( + '7', ugettext_noop('OrgUser'), Scope.org, user_perms + ) + + @classmethod + def get_roles(cls): + roles = { + k: v + for k, v in cls.__dict__.items() + if isinstance(v, PreRole) + } + return roles + + @classmethod + def get_system_role_by_old_name(cls, name): + mapper = { + 'App': cls.system_app, + 'Admin': cls.system_admin, + 'User': cls.system_user, + 'Auditor': cls.system_auditor + } + return mapper[name].get_role() + + @classmethod + def get_org_role_by_old_name(cls, name): + mapper = { + 'Admin': cls.org_admin, + 'User': cls.org_user, + 'Auditor': cls.org_auditor, + } + return mapper[name].get_role() + + @classmethod + def sync_to_db(cls): + roles = cls.get_roles() + + for pre_role in roles.values(): + role, created = pre_role.get_or_create_role() + print("Create builtin Role: {} - {}".format(role.name, created)) + diff --git a/apps/rbac/const.py b/apps/rbac/const.py new file mode 100644 index 000000000..995b2056d --- /dev/null +++ b/apps/rbac/const.py @@ -0,0 +1,76 @@ +from django.db import models +from django.utils.translation import ugettext_lazy as _ + + +class Scope(models.TextChoices): + system = 'system', _('System') + org = 'org', _('Organization') + + +exclude_permissions = ( + # ('App', 'Model', 'Action', 'Resource') Model 和 Resource 可能不同 + # users.add_user + ('auth', '*', '*', '*'), + ('authentication', 'loginconfirmsetting', '*', '*'), + ('captcha', '*', '*', '*'), + ('contenttypes', '*', '*', '*'), + ('django_cas_ng', '*', '*', '*'), + ('django_celery_beat', '*', '*', '*'), + ('jms_oidc_rp', '*', '*', '*'), + ('admin', '*', '*', '*'), + ('sessions', '*', '*', '*'), + ('notifications', '*', '*', '*'), + + ('applications', 'historicalaccount', '*', '*'), + ('applications', 'databaseapp', '*', '*'), + ('applications', 'k8sapp', '*', '*'), + ('applications', 'remoteapp', '*', '*'), + ('assets', 'adminuser', '*', '*'), + ('assets', 'assetgroup', '*', '*'), + ('assets', 'cluster', '*', '*'), + ('assets', 'favoriteasset', '*', '*'), + ('assets', 'historicalauthbook', '*', '*'), + ('assets', 'assetuser', '*', '*'), + ('authentication', 'privatetoken', '*', '*'), + ('perms', 'databaseapppermission', '*', '*'), + ('perms', 'k8sapppermission', '*', '*'), + ('perms', 'remoteapppermission', '*', '*'), + ('perms', 'userassetgrantedtreenoderelation', '*', '*'), + ('perms', 'usergrantedmappingnode', '*', '*'), + ('perms', 'permnode', '*', '*'), + ('perms', 'rebuildusertreetask', '*', '*'), + ('rbac', 'contenttype', '*', '*'), + ('rbac', 'permission', 'add,delete,change', 'permission'), + ('ops', 'adhoc', '*', '*'), + ('ops', 'adhocexecution', '*', '*'), + ('ops', 'celerytask', '*', '*'), + ('ops', 'task', 'add,change', 'task'), + ('orgs', 'organizationmember', '*', '*'), + ('settings', 'setting', 'add,delete', 'setting'), + ('audits', 'operatelog', 'add,delete,change', 'operatelog'), + ('audits', 'passwordchangelog', 'add,change,delete', 'passwordchangelog'), + ('audits', 'userloginlog', 'change,delete,change', 'userloginlog'), + ('audits', 'ftplog', 'change,delete', 'ftplog'), + ('terminal', 'session', 'delete', 'session'), + ('tickets', '*', '*', '*'), + ('users', 'userpasswordhistory', '*', '*'), + ('xpack', 'interface', 'add,delete', 'interface'), +) + + +only_system_permissions = ( + ('users', 'user', 'delete', 'user'), + ('rbac', 'role', 'delete,add,change', 'role'), + ('rbac', 'systemrole', '*', '*'), + ('rbac', 'rolebinding', '*', '*'), + ('rbac', 'systemrolebinding', '*', '*'), + ('orgs', 'organization', '*', '*'), + ('xpack', 'license', '*', '*'), + ('settings', 'setting', '*', '*'), +) + +only_org_permissions = ( +) + +system_exclude_permissions = list(exclude_permissions) + list(only_org_permissions) +org_exclude_permissions = list(exclude_permissions) + list(only_system_permissions) diff --git a/apps/rbac/migrations/0001_initial.py b/apps/rbac/migrations/0001_initial.py new file mode 100644 index 000000000..772a37258 --- /dev/null +++ b/apps/rbac/migrations/0001_initial.py @@ -0,0 +1,151 @@ +# Generated by Django 3.1.13 on 2021-11-19 08:29 + +from django.conf import settings +import django.contrib.auth.models +import django.contrib.contenttypes.models +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('auth', '0012_alter_user_first_name_max_length'), + ('contenttypes', '0002_remove_content_type_name'), + ('orgs', '0010_auto_20210219_1241'), + ] + + operations = [ + migrations.CreateModel( + name='MenuPermission', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ], + options={ + 'verbose_name': 'Menu permission', + 'permissions': [('view_adminview', 'Admin view'), ('view_auditview', 'Audit view'), ('view_userview', 'User view')], + 'default_permissions': [], + }, + ), + migrations.CreateModel( + name='Role', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated 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')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=128, verbose_name='Name')), + ('scope', models.CharField(choices=[('system', 'System'), ('org', 'Organization')], default='system', max_length=128, verbose_name='Scope')), + ('builtin', models.BooleanField(default=False, verbose_name='Built-in')), + ('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')), + ], + ), + migrations.CreateModel( + name='ContentType', + fields=[ + ], + options={ + 'proxy': True, + 'indexes': [], + 'constraints': [], + }, + bases=('contenttypes.contenttype',), + managers=[ + ('objects', django.contrib.contenttypes.models.ContentTypeManager()), + ], + ), + migrations.CreateModel( + name='Permission', + fields=[ + ], + options={ + 'proxy': True, + 'indexes': [], + 'constraints': [], + }, + bases=('auth.permission',), + managers=[ + ('objects', django.contrib.auth.models.PermissionManager()), + ], + ), + migrations.CreateModel( + name='RoleBinding', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated 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')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('scope', models.CharField(choices=[('system', 'System'), ('org', 'Organization')], default='system', max_length=128, verbose_name='Scope')), + ('org', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='role_bindings', to='orgs.organization', verbose_name='Organization')), + ('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='role_bindings', to='rbac.role', verbose_name='Role')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='role_bindings', to=settings.AUTH_USER_MODEL, verbose_name='User')), + ], + options={ + 'verbose_name': 'Role binding', + 'unique_together': {('user', 'role', 'org')}, + }, + ), + migrations.AddField( + model_name='role', + name='permissions', + field=models.ManyToManyField(blank=True, related_name='roles', to='rbac.Permission', verbose_name='Permissions'), + ), + migrations.AlterUniqueTogether( + name='role', + unique_together={('name', 'scope')}, + ), + migrations.CreateModel( + name='OrgRoleBinding', + fields=[ + ], + options={ + 'verbose_name': 'Organization role binding', + 'proxy': True, + 'indexes': [], + 'constraints': [], + }, + bases=('rbac.rolebinding',), + ), + migrations.CreateModel( + name='SystemRoleBinding', + fields=[ + ], + options={ + 'verbose_name': 'System role binding', + 'proxy': True, + 'indexes': [], + 'constraints': [], + }, + bases=('rbac.rolebinding',), + ), + migrations.CreateModel( + name='OrgRole', + fields=[ + ], + options={ + 'verbose_name': 'Organization role', + 'proxy': True, + 'indexes': [], + 'constraints': [], + }, + bases=('rbac.role',), + ), + migrations.CreateModel( + name='SystemRole', + fields=[ + ], + options={ + 'verbose_name': 'System role', + 'proxy': True, + 'indexes': [], + 'constraints': [], + }, + bases=('rbac.role',), + ), + ] diff --git a/apps/rbac/migrations/0002_auto_20210929_1409.py b/apps/rbac/migrations/0002_auto_20210929_1409.py new file mode 100644 index 000000000..e280d4f20 --- /dev/null +++ b/apps/rbac/migrations/0002_auto_20210929_1409.py @@ -0,0 +1,21 @@ +# Generated by Django 3.1.13 on 2021-11-30 02:37 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('rbac', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='permission', + options={'verbose_name': 'Permission'}, + ), + migrations.AlterModelOptions( + name='role', + options={'verbose_name': 'Role'}, + ) + ] diff --git a/apps/rbac/migrations/0003_auto_20211130_1037.py b/apps/rbac/migrations/0003_auto_20211130_1037.py new file mode 100644 index 000000000..5cd8a3b00 --- /dev/null +++ b/apps/rbac/migrations/0003_auto_20211130_1037.py @@ -0,0 +1,19 @@ +# Generated by Django 3.1.13 on 2021-12-01 11:01 + +from django.db import migrations +from rbac.builtin import BuiltinRole + + +def create_builtin_roles(apps, schema_editor): + BuiltinRole.sync_to_db() + + +class Migration(migrations.Migration): + + dependencies = [ + ('rbac', '0002_auto_20210929_1409'), + ] + + operations = [ + migrations.RunPython(create_builtin_roles) + ] diff --git a/apps/rbac/migrations/0004_auto_20211201_1901.py b/apps/rbac/migrations/0004_auto_20211201_1901.py new file mode 100644 index 000000000..74664784e --- /dev/null +++ b/apps/rbac/migrations/0004_auto_20211201_1901.py @@ -0,0 +1,54 @@ +# Generated by Django 3.1.13 on 2021-12-01 11:01 + +from django.db import migrations + +from rbac.builtin import BuiltinRole + + +def migrate_system_role_binding(apps, schema_editor): + db_alias = schema_editor.connection.alias + user_model = apps.get_model('users', 'User') + role_binding_model = apps.get_model('rbac', 'SystemRoleBinding') + users = user_model.objects.using(db_alias).all() + + role_bindings = [] + for user in users: + role = BuiltinRole.get_system_role_by_old_name(user.role) + role_binding = role_binding_model( + scope='system', + user_id=user.id, + role_id=role.id, + ) + role_bindings.append(role_binding) + role_binding_model.objects.bulk_create(role_bindings, ignore_conflicts=True) + + +def migrate_org_role_binding(apps, schema_editor): + db_alias = schema_editor.connection.alias + org_member_model = apps.get_model('orgs', 'OrganizationMember') + role_binding_model = apps.get_model('rbac', 'RoleBinding') + members = org_member_model.objects.using(db_alias).all() + + role_bindings = [] + for member in members: + role = BuiltinRole.get_org_role_by_old_name(member.role) + role_binding = role_binding_model( + scope='org', + user_id=member.user.id, + role_id=role.id, + org_id=member.org.id + ) + role_bindings.append(role_binding) + role_binding_model.objects.bulk_create(role_bindings) + + +class Migration(migrations.Migration): + + dependencies = [ + ('rbac', '0003_auto_20211130_1037'), + ] + + operations = [ + migrations.RunPython(migrate_system_role_binding), + migrations.RunPython(migrate_org_role_binding) + ] diff --git a/apps/rbac/migrations/__init__.py b/apps/rbac/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/rbac/models/__init__.py b/apps/rbac/models/__init__.py new file mode 100644 index 000000000..7a8b0548e --- /dev/null +++ b/apps/rbac/models/__init__.py @@ -0,0 +1,4 @@ +from .permission import * +from .rolebinding import * +from .role import * +from .menu import * diff --git a/apps/rbac/models/menu.py b/apps/rbac/models/menu.py new file mode 100644 index 000000000..4a8e5b82c --- /dev/null +++ b/apps/rbac/models/menu.py @@ -0,0 +1,18 @@ +import uuid + +from django.utils.translation import gettext_lazy as _ +from django.db import models + + +class MenuPermission(models.Model): + """ 附加权限位类,用来定义无资源类的权限,不做实体资源 """ + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + + class Meta: + default_permissions = [] + verbose_name = _('Menu permission') + permissions = [ + ('view_adminview', _('Admin view')), + ('view_auditview', _('Audit view')), + ('view_userview', _('User view')), + ] diff --git a/apps/rbac/models/permission.py b/apps/rbac/models/permission.py new file mode 100644 index 000000000..e51c46fb8 --- /dev/null +++ b/apps/rbac/models/permission.py @@ -0,0 +1,279 @@ +import uuid +from typing import Callable + +from django.db import models +from django.db.models import F, Count, Q +from django.apps import apps +from django.utils.translation import ugettext_lazy as _, ugettext +from django.contrib.auth.models import Permission as DjangoPermission +from django.contrib.auth.models import ContentType as DjangoContentType + +from common.tree import TreeNode +from .. import const + +Scope = const.Scope + +__all__ = ['Permission', 'ContentType'] + + +class ContentType(DjangoContentType): + class Meta: + proxy = True + + +class PermissionTreeUtil: + get_permissions: Callable + + def __init__(self, permissions, scope, check_disabled=False): + self.permissions = self.prefetch_permissions(permissions) + self.all_permissions = self.prefetch_permissions( + Permission.get_permissions(scope) + ) + self.check_disabled = check_disabled + + @staticmethod + def prefetch_permissions(perms): + return perms.select_related('content_type') \ + .annotate(app=F('content_type__app_label')) \ + .annotate(model=F('content_type__model')) + + def _create_apps_tree_nodes(self): + app_counts = self.all_permissions.values('app')\ + .order_by('app').annotate(count=Count('app')) + app_checked_counts = self.permissions.values('app')\ + .order_by('app').annotate(count=Count('app')) + app_checked_counts_mapper = { + i['app']: i['count'] + for i in app_checked_counts + } + all_apps = apps.get_app_configs() + apps_name_mapper = { + app.name: app.verbose_name + for app in all_apps if hasattr(app, 'verbose_name') + } + nodes = [] + + for i in app_counts: + app = i['app'] + total_counts = i['count'] + check_counts = app_checked_counts_mapper.get(app, 0) + name = apps_name_mapper.get(app, app) + full_name = f'{name}({check_counts}/{total_counts})' + + node = TreeNode(**{ + 'id': app, + 'name': full_name, + 'title': name, + 'pId': '$ROOT$', + 'isParent': True, + 'open': False, + 'chkDisabled': self.check_disabled, + 'checked': total_counts == check_counts, + 'iconSkin': '', + 'meta': { + 'type': 'app', + } + }) + nodes.append(node) + return nodes + + def _create_models_tree_nodes(self): + content_types = ContentType.objects.all() + + model_counts = self.all_permissions \ + .values('model', 'app', 'content_type') \ + .order_by('content_type') \ + .annotate(count=Count('content_type')) + model_check_counts = self.permissions \ + .values('content_type', 'model') \ + .order_by('content_type') \ + .annotate(count=Count('content_type')) + model_counts_mapper = { + i['content_type']: i['count'] + for i in model_counts + } + model_check_counts_mapper = { + i['content_type']: i['count'] + for i in model_check_counts + } + + nodes = [] + for ct in content_types: + total_counts = model_counts_mapper.get(ct.id, 0) + if total_counts == 0: + continue + check_counts = model_check_counts_mapper.get(ct.id, 0) + model_id = f'{ct.app_label}_{ct.model}' + name = f'{ct.name}({check_counts}/{total_counts})' + node = TreeNode(**{ + 'id': model_id, + 'name': name, + 'title': name, + 'pId': ct.app_label, + 'chkDisabled': self.check_disabled, + 'isParent': True, + 'open': False, + 'checked': total_counts == check_counts, + 'meta': { + 'type': 'model', + } + }) + nodes.append(node) + return nodes + + @staticmethod + def _get_permission_name(p, content_types_name_mapper): + code_name = p.codename + action_mapper = { + 'add': ugettext('Create'), + 'view': ugettext('View'), + 'change': ugettext('Update'), + 'delete': ugettext('Delete') + } + name = '' + ct = '' + if 'add_' in p.codename: + name = action_mapper['add'] + ct = code_name.replace('add_', '') + elif 'view_' in p.codename: + name = action_mapper['view'] + ct = code_name.replace('view_', '') + elif 'change_' in p.codename: + name = action_mapper['change'] + ct = code_name.replace('change_', '') + elif 'delete' in code_name: + name = action_mapper['delete'] + ct = code_name.replace('delete_', '') + + if ct in content_types_name_mapper: + name += content_types_name_mapper[ct] + else: + name = p.name + return name + + def _create_perms_tree_nodes(self): + permissions_id = self.permissions.values_list('id', flat=True) + nodes = [] + content_types = ContentType.objects.all() + content_types_name_mapper = {ct.model: ct.name for ct in content_types} + for p in self.all_permissions: + model_id = f'{p.app}_{p.model}' + name = self._get_permission_name(p, content_types_name_mapper) + + node = TreeNode(**{ + 'id': p.id, + 'name': name + '({})'.format(p.app_label_codename), + 'title': p.name, + 'pId': model_id, + 'isParent': False, + 'chkDisabled': self.check_disabled, + 'iconSkin': 'file', + 'checked': p.id in permissions_id, + 'open': False, + 'meta': { + 'type': 'perm', + } + }) + nodes.append(node) + return nodes + + def _create_root_tree_node(self): + total_counts = self.all_permissions.count() + check_counts = self.permissions.count() + node = TreeNode(**{ + 'id': '$ROOT$', + 'name': f'所有权限({check_counts}/{total_counts})', + 'title': '所有权限', + 'pId': '', + 'chkDisabled': self.check_disabled, + 'isParent': True, + 'checked': total_counts == check_counts, + 'open': True, + 'meta': { + 'type': 'root', + } + }) + return node + + def create_tree_nodes(self): + nodes = [self._create_root_tree_node()] + apps_nodes = self._create_apps_tree_nodes() + models_nodes = self._create_models_tree_nodes() + perms_nodes = self._create_perms_tree_nodes() + + nodes += apps_nodes + models_nodes + perms_nodes + return nodes + + +class Permission(DjangoPermission): + """ 权限类 """ + class Meta: + proxy = True + verbose_name = _('Permission') + + @classmethod + def to_perms(cls, queryset): + perms = queryset.values_list( + 'content_type__app_label', 'codename' + ) + perms = list(set(["%s.%s" % (ct, codename) for ct, codename in perms])) + return sorted(perms) + + @property + def app_label_codename(self): + return '%s.%s' % (self.content_type.app_label, self.codename) + + @classmethod + def get_define_permissions_q(cls, defines): + """ + :param defines: [(app, model, codename),] + :return: + """ + if not defines: + return None + q = Q() + + for define in defines: + app_label, model, actions, resource, *args = list(define) + kwargs = {} + if app_label != '*': + kwargs['content_type__app_label'] = app_label + if model != '*': + kwargs['content_type__model'] = model + + actions_list = [a.strip() for a in actions.split(',')] + actions_regex = '|'.join(actions_list) + if actions == '*' and resource == '*': + pass + elif actions == '*' and resource != '*': + kwargs['codename__iregex'] = r'[a-z]+_{}'.format(resource) + elif actions != '*' and resource == '*': + kwargs['codename__iregex'] = r'({})_\w+'.format(actions_regex) + else: + kwargs['codename__iregex'] = r'({})_{}'.format(actions_regex, resource) + q |= Q(**kwargs) + return q + + @classmethod + def clean_permissions(cls, permissions, scope=Scope.system): + if scope == Scope.org: + excludes = const.org_exclude_permissions + else: + excludes = const.system_exclude_permissions + q = cls.get_define_permissions_q(excludes) + if q: + permissions = permissions.exclude(q) + return permissions + + @staticmethod + def create_tree_nodes(permissions, scope, check_disabled=False): + util = PermissionTreeUtil(permissions, scope, check_disabled) + return util.create_tree_nodes() + + @classmethod + def get_permissions(cls, scope): + permissions = cls.objects.all() + permissions = cls.clean_permissions(permissions, scope=scope) + return permissions + + diff --git a/apps/rbac/models/role.py b/apps/rbac/models/role.py new file mode 100644 index 000000000..75e8adb31 --- /dev/null +++ b/apps/rbac/models/role.py @@ -0,0 +1,135 @@ +from django.utils.translation import ugettext_lazy as _, gettext +from django.db import models + +from common.db.models import JMSModel +from common.utils import lazyproperty +from .permission import Permission +from ..builtin import BuiltinRole +from .. import const + +__all__ = ['Role', 'SystemRole', 'OrgRole'] + + +class SystemRoleManager(models.Manager): + def get_queryset(self): + queryset = super().get_queryset() + return queryset.filter(scope=const.Scope.system) + + +class OrgRoleManager(models.Manager): + def get_queryset(self): + queryset = super().get_queryset() + return queryset.filter(scope=const.Scope.org) + + +class Role(JMSModel): + """ 定义 角色 | 角色-权限 关系 """ + Scope = const.Scope + + name = models.CharField(max_length=128, verbose_name=_('Name')) + scope = models.CharField( + max_length=128, choices=Scope.choices, default=Scope.system, verbose_name=_('Scope') + ) + permissions = models.ManyToManyField( + 'rbac.Permission', related_name='roles', blank=True, verbose_name=_('Permissions') + ) + builtin = models.BooleanField(default=False, verbose_name=_('Built-in')) + comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment')) + + BuiltinRole = BuiltinRole + objects = models.Manager() + org_roles = OrgRoleManager() + system_roles = SystemRoleManager() + + class Meta: + unique_together = [('name', 'scope')] + verbose_name = _('Role') + + def __str__(self): + return '%s(%s)' % (self.name, self.get_scope_display()) + + def is_system_admin(self): + return self.name == self.BuiltinRole.system_admin.name and self.builtin + + def is_org_admin(self): + return self.name == self.BuiltinRole.org_admin.name and self.builtin + + def is_admin(self): + yes = self.is_system_admin() or self.is_org_admin() + return yes + + @staticmethod + def get_scope_roles_perms(roles, scope): + has_admin = any([r.is_admin() for r in roles]) + if has_admin: + perms = Permission.objects.all() + else: + perms = Permission.objects.filter(roles__in=roles).distinct() + perms = Permission.clean_permissions(perms, scope=scope) + return perms + + @classmethod + def get_roles_permissions(cls, roles): + org_roles = [role for role in roles if role.scope == cls.Scope.org] + org_perms_id = cls.get_scope_roles_perms(org_roles, cls.Scope.org)\ + .values_list('id', flat=True) + + system_roles = [role for role in roles if role.scope == cls.Scope.system] + system_perms_id = cls.get_scope_roles_perms(system_roles, cls.Scope.system)\ + .values_list('id', flat=True) + perms_id = set(org_perms_id) | set(system_perms_id) + permissions = Permission.objects.filter(id__in=perms_id)\ + .prefetch_related('content_type') + return permissions + + @classmethod + def get_roles_perms(cls, roles): + permissions = cls.get_roles_permissions(roles) + return Permission.to_perms(permissions) + + def get_permissions(self): + if self.is_admin(): + permissions = Permission.objects.all() + else: + permissions = self.permissions.all() + permissions = Permission.clean_permissions(permissions, self.scope) + return permissions + + @lazyproperty + def users(self): + from .rolebinding import RoleBinding + return RoleBinding.get_role_users(self) + + @lazyproperty + def users_amount(self): + return self.users.count() + + @lazyproperty + def permissions_amount(self): + return self.permissions.count() + + @classmethod + def create_builtin_roles(cls): + BuiltinRole.sync_to_db() + + @property + def display_name(self): + if not self.builtin: + return self.name + return gettext(self.name) + + +class SystemRole(Role): + objects = SystemRoleManager() + + class Meta: + proxy = True + verbose_name = _('System role') + + +class OrgRole(Role): + objects = OrgRoleManager() + + class Meta: + proxy = True + verbose_name = _('Organization role') diff --git a/apps/rbac/models/rolebinding.py b/apps/rbac/models/rolebinding.py new file mode 100644 index 000000000..c4c68fbf5 --- /dev/null +++ b/apps/rbac/models/rolebinding.py @@ -0,0 +1,137 @@ +from django.utils.translation import gettext_lazy as _ +from django.db import models +from django.db.models import Q +from rest_framework.serializers import ValidationError + +from common.db.models import JMSModel +from common.utils import lazyproperty +from orgs.utils import current_org +from .role import Role +from ..const import Scope + +__all__ = ['RoleBinding', 'SystemRoleBinding', 'OrgRoleBinding'] + + +class RoleBindingManager(models.Manager): + def get_queryset(self): + queryset = super(RoleBindingManager, self).get_queryset() + q = Q(scope=Scope.system) + if not current_org.is_root(): + q |= Q(org_id=current_org.id, scope=Scope.org) + queryset = queryset.filter(q) + return queryset + + +class RoleBinding(JMSModel): + Scope = Scope + """ 定义 用户-角色 关系 """ + scope = models.CharField( + max_length=128, choices=Scope.choices, default=Scope.system, + verbose_name=_('Scope') + ) + user = models.ForeignKey( + 'users.User', related_name='role_bindings', on_delete=models.CASCADE, verbose_name=_('User') + ) + role = models.ForeignKey( + Role, related_name='role_bindings', on_delete=models.CASCADE, verbose_name=_('Role') + ) + org = models.ForeignKey( + 'orgs.Organization', related_name='role_bindings', blank=True, null=True, + on_delete=models.CASCADE, verbose_name=_('Organization') + ) + objects = RoleBindingManager() + + class Meta: + verbose_name = _('Role binding') + unique_together = [ + ('user', 'role', 'org'), + ] + + def __str__(self): + display = '{user} & {role}'.format(user=self.user, role=self.role) + if self.org: + display += ' | {org}'.format(org=self.org) + return display + + def save(self, *args, **kwargs): + self.scope = self.role.scope + return super().save(*args, **kwargs) + + @classmethod + def get_user_perms(cls, user): + roles = cls.get_user_roles(user) + return Role.get_roles_perms(roles) + + @classmethod + def get_role_users(cls, role): + from users.models import User + bindings = cls.objects.filter(role=role, scope=role.scope) + user_ids = bindings.values_list('user', flat=True).distinct() + return User.objects.filter(id__in=user_ids) + + @classmethod + def get_user_roles(cls, user): + bindings = cls.objects.filter(user=user) + roles_id = bindings.values_list('role', flat=True).distinct() + return Role.objects.filter(id__in=roles_id) + + @lazyproperty + def user_display(self): + return self.user.name + + @lazyproperty + def role_display(self): + return self.role.display_name + + +class OrgRoleBindingManager(models.Manager): + def get_queryset(self): + queryset = super().get_queryset() + if current_org.is_root(): + return queryset.none() + + queryset = queryset.filter(org=current_org.id) + return queryset + + +class OrgRoleBinding(RoleBinding): + objects = OrgRoleBindingManager() + + def save(self, *args, **kwargs): + self.org_id = current_org.id + self.scope = Scope.org + return super().save(*args, **kwargs) + + def delete(self, **kwargs): + has_other_role = self.__class__.objects \ + .filter(user=self.user, scope=self.scope) \ + .exclude(id=self.id) \ + .exists() + if not has_other_role: + error = _('User last role in org, can not be delete, ' + 'you can remove user from org instead') + raise ValidationError({'error': error}) + return super().delete(**kwargs) + + class Meta: + proxy = True + verbose_name = _('Organization role binding') + + +class SystemRoleBindingManager(models.Manager): + def get_queryset(self): + queryset = super().get_queryset().\ + filter(scope=Scope.org) + return queryset + + +class SystemRoleBinding(RoleBinding): + objects = SystemRoleBindingManager() + + class Meta: + proxy = True + verbose_name = _('System role binding') + + def save(self, *args, **kwargs): + self.scope = Scope.system + return super().save(*args, **kwargs) diff --git a/apps/rbac/permissions.py b/apps/rbac/permissions.py new file mode 100644 index 000000000..efe25e58a --- /dev/null +++ b/apps/rbac/permissions.py @@ -0,0 +1,115 @@ +from rest_framework import permissions, exceptions + +from common.utils import get_logger + +logger = get_logger(__name__) + + +class RBACPermission(permissions.DjangoModelPermissions): + default_rbac_perms_tmpl = ( + ('list', '%(app_label)s.view_%(model_name)s'), + ('retrieve', '%(app_label)s.view_%(model_name)s'), + ('create', '%(app_label)s.add_%(model_name)s'), + ('update', '%(app_label)s.change_%(model_name)s'), + ('partial_update', '%(app_label)s.change_%(model_name)s'), + ('destroy', '%(app_label)s.delete_%(model_name)s'), + ('metadata', ''), + ('GET', '%(app_label)s.view_%(model_name)s'), + ('OPTIONS', ''), + ('HEAD', '%(app_label)s.view_%(model_name)s'), + ('POST', '%(app_label)s.add_%(model_name)s'), + ('PUT', '%(app_label)s.change_%(model_name)s'), + ('PATCH', '%(app_label)s.change_%(model_name)s'), + ('DELETE', '%(app_label)s.delete_%(model_name)s'), + ) + # rbac_perms = ((), ()) + # def get_rbac_perms(): + # return {} + + @staticmethod + def format_perms(perms_tmpl, model_cls): + perms = set() + if not perms_tmpl: + return perms + if isinstance(perms_tmpl, str): + perms_tmpl = [perms_tmpl] + for perm_tmpl in perms_tmpl: + perm = perm_tmpl % { + 'app_label': model_cls._meta.app_label, + 'model_name': model_cls._meta.model_name + } + perms.add(perm) + return perms + + @classmethod + def parse_action_model_perms(cls, action, model_cls): + perm_tmpl = dict(cls.default_rbac_perms_tmpl).get(action) + return cls.format_perms(perm_tmpl, model_cls) + + def get_default_action_perms(self, model_cls): + if model_cls is None: + return {} + perms = {} + for action, tmpl in dict(self.default_rbac_perms_tmpl).items(): + perms[action] = self.format_perms(tmpl, model_cls) + return perms + + def get_rbac_perms(self, view, model_cls) -> dict: + if hasattr(view, 'get_rbac_perms'): + return dict(view.get_rbac_perms()) + perms = self.get_default_action_perms(model_cls) + if hasattr(view, 'rbac_perms'): + perms.update(dict(view.rbac_perms)) + return perms + + def _get_action_perms(self, action, model_cls, view): + action_perms_map = self.get_rbac_perms(view, model_cls) + if action not in action_perms_map: + msg = 'Action not allowed: {}, only `{}` supported'.format( + action, ','.join(list(action_perms_map.keys())) + ) + logger.error(msg) + raise exceptions.PermissionDenied(msg) + perms = action_perms_map[action] + return perms + + def get_require_perms(self, request, view): + """ + 获取 request, view 需要的 perms + :param request: + :param view: + :return: + """ + try: + queryset = self._queryset(view) + model_cls = queryset.model + except AssertionError: + model_cls = None + + action = getattr(view, 'action', None) + if not action: + action = request.method + perms = self._get_action_perms(action, model_cls, view) + return perms + + def has_permission(self, request, view): + # Workaround to ensure DjangoModelPermissions are not applied + # to the root view when using DefaultRouter. + if getattr(view, '_ignore_rbac_permissions', False): + return True + if not request.user: + return False + if request.user.is_anonymous and self.authenticated_users_only: + return False + + action = getattr(view, 'action', None) + if action == 'metadata': + return True + + perms = self.get_require_perms(request, view) + if isinstance(perms, str): + perms = [perms] + has = request.user.has_perms(perms) + logger.debug('View require perms: {}, result: {}'.format(perms, has)) + return has + diff --git a/apps/rbac/serializers/__init__.py b/apps/rbac/serializers/__init__.py new file mode 100644 index 000000000..894655045 --- /dev/null +++ b/apps/rbac/serializers/__init__.py @@ -0,0 +1,4 @@ +from .permission import * +from .role import * +from .rolebinding import * + diff --git a/apps/rbac/serializers/permission.py b/apps/rbac/serializers/permission.py new file mode 100644 index 000000000..809b86636 --- /dev/null +++ b/apps/rbac/serializers/permission.py @@ -0,0 +1,32 @@ +from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers +from django.contrib.auth.models import ContentType + +from ..models import Permission + + +__all__ = ['PermissionSerializer', 'UserPermsSerializer'] + + +class ContentTypeSerializer(serializers.ModelSerializer): + class Meta: + model = ContentType + fields = ['id', 'app_label', 'model'] + + +class PermissionSerializer(serializers.ModelSerializer): + content_type = ContentTypeSerializer(read_only=True) + + class Meta: + model = Permission + fields = ['id', 'name', 'content_type', 'codename'] + + +class UserPermsSerializer(serializers.Serializer): + perms = serializers.ListField(label=_('Perms'), read_only=True) + + def create(self, validated_data): + pass + + def update(self, instance, validated_data): + pass diff --git a/apps/rbac/serializers/role.py b/apps/rbac/serializers/role.py new file mode 100644 index 000000000..6070fc8dc --- /dev/null +++ b/apps/rbac/serializers/role.py @@ -0,0 +1,34 @@ +from rest_framework import serializers +from django.utils.translation import ugettext_lazy as _ + +from users.models import User +from ..models import Role + +__all__ = ['RoleSerializer', 'RoleUserSerializer'] + + +class RoleSerializer(serializers.ModelSerializer): + scope_display = serializers.ReadOnlyField(source='get_scope_display', label=_('Scope display')) + + class Meta: + model = Role + fields_mini = ['id', 'name', 'display_name', 'scope'] + read_only_fields = [ + 'users_amount', 'builtin', 'scope_display', + 'date_created', 'date_updated', + 'created_by', 'updated_by', + ] + fields = fields_mini + read_only_fields + [ + 'comment', 'permissions' + ] + extra_kwargs = { + 'permissions': {'write_only': True}, + 'users_amount': {'label': _('Users amount')}, + 'display_name': {'label': _('Display name')} + } + + +class RoleUserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ['id', 'name', 'username'] diff --git a/apps/rbac/serializers/rolebinding.py b/apps/rbac/serializers/rolebinding.py new file mode 100644 index 000000000..75f0ed903 --- /dev/null +++ b/apps/rbac/serializers/rolebinding.py @@ -0,0 +1,60 @@ +from rest_framework import serializers +from django.utils.translation import ugettext_lazy as _ + +from orgs.serializers import CurrentOrgDefault +from orgs.utils import current_org +from ..models import RoleBinding, SystemRoleBinding, OrgRoleBinding + +__all__ = [ + 'RoleBindingSerializer', 'OrgRoleBindingSerializer', 'SystemRoleBindingSerializer' +] + + +class RoleBindingSerializer(serializers.ModelSerializer): + class Meta: + model = RoleBinding + fields = [ + 'id', 'user', 'user_display', 'role', 'role_display', + 'scope', 'org', + ] + read_only_fields = ['scope'] + extra_kwargs = { + 'user_display': {'label': _('User display')}, + 'role_display': {'label': _('Role display')}, + } + + +class SystemRoleBindingSerializer(RoleBindingSerializer): + org = None + + class Meta(RoleBindingSerializer.Meta): + model = SystemRoleBinding + + def get_field_names(self, *args): + names = super().get_field_names(*args) + return list(set(names) - {'org'}) + + +class OrgRoleBindingSerializer(RoleBindingSerializer): + org = serializers.PrimaryKeyRelatedField( + default=CurrentOrgDefault(), label=_("Organization"), read_only=True + ) + + class Meta(RoleBindingSerializer.Meta): + model = OrgRoleBinding + validators = [] + + def validate(self, attrs): + data = self.initial_data + many = isinstance(data, list) + if not many: + user = attrs.get('user') + role = attrs.get('role') + role_bindings = OrgRoleBinding.objects.filter(user=user, role=role) + + if not self.instance and role_bindings.exists(): + raise serializers.ValidationError({'role': _('Has bound this role')}) + return attrs + + + diff --git a/apps/rbac/tests.py b/apps/rbac/tests.py new file mode 100644 index 000000000..889a58af7 --- /dev/null +++ b/apps/rbac/tests.py @@ -0,0 +1,23 @@ +from django.test import TestCase + +# Create your tests here. + +# TODO: Model +# 用户 User +# 角色 Role +# 权限 Permission +# 用户-角色 关系 RoleBinding +# 角色-权限 关系 Role + + +# TODO: +# 1. 创建用户、邀请用户 (给用户添加角色) +# 2. 创建角色 (创建角色并指定权限集) +# 3. APIView 控制用户访问权限 (获取用户访问API行为的codename,获取用户角色-权限,判断是否包含) +# 4. 获取权限集 (分类获取 scope: system、org、app) +# 5. 定义权限位 (整理所有权限位并分类,同时在Model中重新定义权限名称) +# 7. 添加内置角色 +# 6. 修改用户Model/Serializer/API,删除旧role字段,关联新role +# 8. 权限位名称翻译 (整理一个dict,key为codename,value为翻译) +# 9. 修改用户-组织关联的角色,修改表结构 +# 10. 前端获取所有权限,给每个按钮添加对应的权限控制指令 diff --git a/apps/rbac/urls/__init__.py b/apps/rbac/urls/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/rbac/urls/api_urls.py b/apps/rbac/urls/api_urls.py new file mode 100644 index 000000000..6c8945880 --- /dev/null +++ b/apps/rbac/urls/api_urls.py @@ -0,0 +1,24 @@ +# coding:utf-8 +from rest_framework_bulk.routes import BulkRouter +from rest_framework_nested import routers + +from .. import api + +app_name = 'rbac' + + +router = BulkRouter() +router.register(r'roles', api.RoleViewSet, 'role') +router.register(r'system-roles', api.SystemRoleViewSet, 'system-role') +router.register(r'org-roles', api.OrgRoleViewSet, 'org-role') +router.register(r'role-bindings', api.RoleBindingViewSet, 'role-binding') +router.register(r'system-role-bindings', api.SystemRoleBindingViewSet, 'system-role-binding') +router.register(r'org-role-bindings', api.OrgRoleBindingViewSet, 'org-role-binding') +router.register(r'permissions', api.PermissionViewSet, 'permission') + +role_router = routers.NestedDefaultRouter(router, r'roles', lookup='role') +role_router.register(r'permissions', api.RolePermissionsViewSet, 'role-permission') + +urlpatterns = [] + +urlpatterns += router.urls + role_router.urls diff --git a/apps/rbac/utils.py b/apps/rbac/utils.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/settings/api/alibaba_sms.py b/apps/settings/api/alibaba_sms.py index ab79d58bc..b00487d51 100644 --- a/apps/settings/api/alibaba_sms.py +++ b/apps/settings/api/alibaba_sms.py @@ -6,15 +6,16 @@ from django.utils.translation import gettext_lazy as _ from common.sdk.sms.alibaba import AlibabaSMS from settings.models import Setting -from common.permissions import IsSuperUser from common.exceptions import JMSException from .. import serializers class AlibabaSMSTestingAPI(GenericAPIView): - permission_classes = (IsSuperUser,) serializer_class = serializers.AlibabaSMSSettingSerializer + rbac_perms = { + 'POST': 'settings.change_setting' + } def post(self, request): serializer = self.serializer_class(data=request.data) diff --git a/apps/settings/api/dingtalk.py b/apps/settings/api/dingtalk.py index 8164198de..196f259dc 100644 --- a/apps/settings/api/dingtalk.py +++ b/apps/settings/api/dingtalk.py @@ -5,14 +5,11 @@ from rest_framework import status from django.utils.translation import gettext_lazy as _ from django.conf import settings -from common.permissions import IsSuperUser from common.sdk.im.dingtalk import DingTalk - from .. import serializers class DingTalkTestingAPI(GenericAPIView): - permission_classes = (IsSuperUser,) serializer_class = serializers.DingTalkSettingSerializer def post(self, request): diff --git a/apps/settings/api/email.py b/apps/settings/api/email.py index 0d758237b..78a406b24 100644 --- a/apps/settings/api/email.py +++ b/apps/settings/api/email.py @@ -6,7 +6,6 @@ from rest_framework.views import Response, APIView from django.core.mail import send_mail, get_connection from django.utils.translation import ugettext_lazy as _ -from common.permissions import IsSuperUser from common.utils import get_logger from .. import serializers from django.conf import settings @@ -17,9 +16,11 @@ __all__ = ['MailTestingAPI'] class MailTestingAPI(APIView): - permission_classes = (IsSuperUser,) serializer_class = serializers.MailTestSerializer success_message = _("Test mail sent to {}, please check") + rbac_perms = { + 'POST': 'settings.change_setting' + } def post(self, request): serializer = self.serializer_class(data=request.data) diff --git a/apps/settings/api/feishu.py b/apps/settings/api/feishu.py index e9b3a391a..07d1d4ebd 100644 --- a/apps/settings/api/feishu.py +++ b/apps/settings/api/feishu.py @@ -5,15 +5,16 @@ from rest_framework import status from django.utils.translation import gettext_lazy as _ from settings.models import Setting -from common.permissions import IsSuperUser from common.sdk.im.feishu import FeiShu from .. import serializers class FeiShuTestingAPI(GenericAPIView): - permission_classes = (IsSuperUser,) serializer_class = serializers.FeiShuSettingSerializer + rbac_perms = { + 'POST': 'settings.change_setting' + } def post(self, request): serializer = self.serializer_class(data=request.data) diff --git a/apps/settings/api/ldap.py b/apps/settings/api/ldap.py index 1a35fb491..406b2f18e 100644 --- a/apps/settings/api/ldap.py +++ b/apps/settings/api/ldap.py @@ -13,7 +13,6 @@ from ..utils import ( LDAP_USE_CACHE_FLAGS, LDAPTestUtil ) from ..tasks import sync_ldap_user -from common.permissions import IsSuperUser from common.utils import get_logger, is_uuid from ..serializers import ( LDAPTestConfigSerializer, LDAPUserSerializer, @@ -26,8 +25,10 @@ logger = get_logger(__file__) class LDAPTestingConfigAPI(APIView): - permission_classes = (IsSuperUser,) serializer_class = LDAPTestConfigSerializer + rbac_perms = { + 'POST': 'settings.change_setting' + } def post(self, request): serializer = self.serializer_class(data=request.data) @@ -66,8 +67,10 @@ class LDAPTestingConfigAPI(APIView): class LDAPTestingLoginAPI(APIView): - permission_classes = (IsSuperUser,) serializer_class = LDAPTestLoginSerializer + rbac_perms = { + 'POST': 'settings.change_setting' + } def post(self, request): serializer = self.serializer_class(data=request.data) @@ -81,8 +84,10 @@ class LDAPTestingLoginAPI(APIView): class LDAPUserListApi(generics.ListAPIView): - permission_classes = (IsSuperUser,) serializer_class = LDAPUserSerializer + rbac_perms = { + 'POST': 'settings.change_setting' + } def get_queryset_from_cache(self): search_value = self.request.query_params.get('search') @@ -170,8 +175,6 @@ class LDAPUserListApi(generics.ListAPIView): class LDAPUserImportAPI(APIView): - permission_classes = (IsSuperUser,) - def get_org(self): org_id = self.request.data.get('org_id') if is_uuid(org_id): @@ -210,8 +213,6 @@ class LDAPUserImportAPI(APIView): class LDAPCacheRefreshAPI(generics.RetrieveAPIView): - permission_classes = (IsSuperUser,) - def retrieve(self, request, *args, **kwargs): try: LDAPSyncUtil().clear_cache() diff --git a/apps/settings/api/settings.py b/apps/settings/api/settings.py index 02fb9004c..7864b7c1c 100644 --- a/apps/settings/api/settings.py +++ b/apps/settings/api/settings.py @@ -5,7 +5,7 @@ from rest_framework import generics from django.conf import settings from jumpserver.conf import Config -from common.permissions import IsSuperUser +from rbac.permissions import RBACPermission from common.utils import get_logger from .. import serializers from ..models import Setting @@ -14,7 +14,8 @@ logger = get_logger(__file__) class SettingsApi(generics.RetrieveUpdateAPIView): - permission_classes = (IsSuperUser,) + permission_classes = (RBACPermission,) + serializer_class_mapper = { 'all': serializers.SettingsSerializer, 'basic': serializers.BasicSettingSerializer, @@ -40,6 +41,9 @@ class SettingsApi(generics.RetrieveUpdateAPIView): 'tencent': serializers.TencentSMSSettingSerializer, } + def get_queryset(self): + return Setting.objects.all() + def get_serializer_class(self): category = self.request.query_params.get('category', 'basic') default = serializers.BasicSettingSerializer diff --git a/apps/settings/api/sms.py b/apps/settings/api/sms.py index de36b0317..bb30fa3aa 100644 --- a/apps/settings/api/sms.py +++ b/apps/settings/api/sms.py @@ -1,14 +1,15 @@ from rest_framework.generics import ListAPIView from rest_framework.response import Response -from common.permissions import IsSuperUser from common.sdk.sms import BACKENDS from settings.serializers.sms import SMSBackendSerializer class SMSBackendAPI(ListAPIView): - permission_classes = (IsSuperUser,) serializer_class = SMSBackendSerializer + rbac_perms = { + 'list': 'settings.view_setting' + } def list(self, request, *args, **kwargs): data = [ @@ -16,7 +17,7 @@ class SMSBackendAPI(ListAPIView): 'name': b, 'label': b.label } - for b in BACKENDS + for b in BACKENDS.choices ] return Response(data) diff --git a/apps/settings/api/tencent_sms.py b/apps/settings/api/tencent_sms.py index b944c163c..7b3d41061 100644 --- a/apps/settings/api/tencent_sms.py +++ b/apps/settings/api/tencent_sms.py @@ -8,15 +8,16 @@ from django.utils.translation import gettext_lazy as _ from common.sdk.sms.tencent import TencentSMS from settings.models import Setting -from common.permissions import IsSuperUser from common.exceptions import JMSException from .. import serializers class TencentSMSTestingAPI(GenericAPIView): - permission_classes = (IsSuperUser,) serializer_class = serializers.TencentSMSSettingSerializer + rbac_perms = { + 'POST': 'settings.change_setting' + } def post(self, request): serializer = self.serializer_class(data=request.data) diff --git a/apps/settings/api/wecom.py b/apps/settings/api/wecom.py index fce5ddad5..622fde923 100644 --- a/apps/settings/api/wecom.py +++ b/apps/settings/api/wecom.py @@ -5,15 +5,16 @@ from rest_framework import status from django.utils.translation import gettext_lazy as _ from settings.models import Setting -from common.permissions import IsSuperUser from common.sdk.im.wecom import WeCom from .. import serializers class WeComTestingAPI(GenericAPIView): - permission_classes = (IsSuperUser,) serializer_class = serializers.WeComSettingSerializer + rbac_perms = { + 'POST': 'settings.change_setting' + } def post(self, request): serializer = self.serializer_class(data=request.data) diff --git a/apps/settings/apps.py b/apps/settings/apps.py index 05af85c46..bcb1b7693 100644 --- a/apps/settings/apps.py +++ b/apps/settings/apps.py @@ -1,8 +1,10 @@ from django.apps import AppConfig +from django.utils.translation import ugettext_lazy as _ class SettingsConfig(AppConfig): name = 'settings' + verbose_name = _('Settings') def ready(self): from . import signals_handler diff --git a/apps/settings/migrations/0004_auto_20220211_1401.py b/apps/settings/migrations/0004_auto_20220211_1401.py new file mode 100644 index 000000000..72c9c62f8 --- /dev/null +++ b/apps/settings/migrations/0004_auto_20220211_1401.py @@ -0,0 +1,17 @@ +# Generated by Django 3.1.12 on 2022-02-11 06:01 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('settings', '0003_auto_20210901_1035'), + ] + + operations = [ + migrations.AlterModelOptions( + name='setting', + options={'verbose_name': 'System setting'}, + ), + ] diff --git a/apps/settings/models.py b/apps/settings/models.py index 0690590b6..6095b7afe 100644 --- a/apps/settings/models.py +++ b/apps/settings/models.py @@ -192,4 +192,4 @@ class Setting(models.Model): class Meta: db_table = "settings_setting" - verbose_name = _("Setting") + verbose_name = _("System setting") diff --git a/apps/static/js/angular-route.min.js b/apps/static/js/angular-route.min.js deleted file mode 100644 index e764c4d4e..000000000 --- a/apps/static/js/angular-route.min.js +++ /dev/null @@ -1,13 +0,0 @@ -/* - AngularJS v1.2.5 - (c) 2010-2014 Google, Inc. http://angularjs.org - License: MIT -*/ -(function(h,e,A){'use strict';function u(w,q,k){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,c,b,f,n){function y(){l&&(l.$destroy(),l=null);g&&(k.leave(g),g=null)}function v(){var b=w.current&&w.current.locals;if(b&&b.$template){var b=a.$new(),f=w.current;g=n(b,function(d){k.enter(d,null,g||c,function(){!e.isDefined(t)||t&&!a.$eval(t)||q()});y()});l=f.scope=b;l.$emit("$viewContentLoaded");l.$eval(h)}else y()}var l,g,t=b.autoscroll,h=b.onload||"";a.$on("$routeChangeSuccess", -v);v()}}}function z(e,h,k){return{restrict:"ECA",priority:-400,link:function(a,c){var b=k.current,f=b.locals;c.html(f.$template);var n=e(c.contents());b.controller&&(f.$scope=a,f=h(b.controller,f),b.controllerAs&&(a[b.controllerAs]=f),c.data("$ngControllerController",f),c.children().data("$ngControllerController",f));n(a)}}}h=e.module("ngRoute",["ng"]).provider("$route",function(){function h(a,c){return e.extend(new (e.extend(function(){},{prototype:a})),c)}function q(a,e){var b=e.caseInsensitiveMatch, -f={originalPath:a,regexp:a},h=f.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?|\*])?/g,function(a,e,b,c){a="?"===c?c:null;c="*"===c?c:null;h.push({name:b,optional:!!a});e=e||"";return""+(a?"":e)+"(?:"+(a?e:"")+(c&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");f.regexp=RegExp("^"+a+"$",b?"i":"");return f}var k={};this.when=function(a,c){k[a]=e.extend({reloadOnSearch:!0},c,a&&q(a,c));if(a){var b="/"==a[a.length-1]?a.substr(0,a.length-1):a+"/";k[b]=e.extend({redirectTo:a}, -q(b,c))}return this};this.otherwise=function(a){this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$http","$templateCache","$sce",function(a,c,b,f,n,q,v,l){function g(){var d=t(),m=r.current;if(d&&m&&d.$$route===m.$$route&&e.equals(d.pathParams,m.pathParams)&&!d.reloadOnSearch&&!x)m.params=d.params,e.copy(m.params,b),a.$broadcast("$routeUpdate",m);else if(d||m)x=!1,a.$broadcast("$routeChangeStart",d,m),(r.current=d)&&d.redirectTo&&(e.isString(d.redirectTo)? -c.path(u(d.redirectTo,d.params)).search(d.params).replace():c.url(d.redirectTo(d.pathParams,c.path(),c.search())).replace()),f.when(d).then(function(){if(d){var a=e.extend({},d.resolve),c,b;e.forEach(a,function(d,c){a[c]=e.isString(d)?n.get(d):n.invoke(d)});e.isDefined(c=d.template)?e.isFunction(c)&&(c=c(d.params)):e.isDefined(b=d.templateUrl)&&(e.isFunction(b)&&(b=b(d.params)),b=l.getTrustedResourceUrl(b),e.isDefined(b)&&(d.loadedTemplateUrl=b,c=q.get(b,{cache:v}).then(function(a){return a.data}))); -e.isDefined(c)&&(a.$template=c);return f.all(a)}}).then(function(c){d==r.current&&(d&&(d.locals=c,e.copy(d.params,b)),a.$broadcast("$routeChangeSuccess",d,m))},function(c){d==r.current&&a.$broadcast("$routeChangeError",d,m,c)})}function t(){var a,b;e.forEach(k,function(f,k){var p;if(p=!b){var s=c.path();p=f.keys;var l={};if(f.regexp)if(s=f.regexp.exec(s)){for(var g=1,q=s.length;g").append(b).html();try{return b[0].nodeType===pb?z(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+z(b)})}catch(d){return z(c)}}function rc(b){try{return decodeURIComponent(b)}catch(a){}} -function sc(b){var a={},c,d;r((b||"").split("&"),function(b){b&&(c=b.replace(/\+/g,"%20").split("="),d=rc(c[0]),y(d)&&(b=y(c[1])?rc(c[1]):!0,tc.call(a,d)?H(a[d])?a[d].push(b):a[d]=[a[d],b]:a[d]=b))});return a}function Pb(b){var a=[];r(b,function(b,d){H(b)?r(b,function(b){a.push(Ea(d,!0)+(!0===b?"":"="+Ea(b,!0)))}):a.push(Ea(d,!0)+(!0===b?"":"="+Ea(b,!0)))});return a.length?a.join("&"):""}function qb(b){return Ea(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function Ea(b,a){return encodeURIComponent(b).replace(/%40/gi, -"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%3B/gi,";").replace(/%20/g,a?"%20":"+")}function Id(b,a){var c,d,e=rb.length;b=A(b);for(d=0;d/,">"));}a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);c.debugInfoEnabled&&a.push(["$compileProvider",function(a){a.debugInfoEnabled(!0)}]);a.unshift("ng");d=ab(a,c.strictDi);d.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector", -d);c(b)(a)})}]);return d},e=/^NG_ENABLE_DEBUG_INFO!/,f=/^NG_DEFER_BOOTSTRAP!/;Q&&e.test(Q.name)&&(c.debugInfoEnabled=!0,Q.name=Q.name.replace(e,""));if(Q&&!f.test(Q.name))return d();Q.name=Q.name.replace(f,"");ca.resumeBootstrap=function(b){r(b,function(b){a.push(b)});return d()};G(ca.resumeDeferredBootstrap)&&ca.resumeDeferredBootstrap()}function Kd(){Q.name="NG_ENABLE_DEBUG_INFO!"+Q.name;Q.location.reload()}function Ld(b){b=ca.element(b).injector();if(!b)throw Ja("test");return b.get("$$testability")} -function vc(b,a){a=a||"_";return b.replace(Md,function(b,d){return(d?a:"")+b.toLowerCase()})}function Nd(){var b;wc||((ta=Q.jQuery)&&ta.fn.on?(A=ta,w(ta.fn,{scope:Ka.scope,isolateScope:Ka.isolateScope,controller:Ka.controller,injector:Ka.injector,inheritedData:Ka.inheritedData}),b=ta.cleanData,ta.cleanData=function(a){var c;if(Qb)Qb=!1;else for(var d=0,e;null!=(e=a[d]);d++)(c=ta._data(e,"events"))&&c.$destroy&&ta(e).triggerHandler("$destroy");b(a)}):A=T,ca.element=A,wc=!0)}function Rb(b,a,c){if(!b)throw Ja("areq", -a||"?",c||"required");return b}function sb(b,a,c){c&&H(b)&&(b=b[b.length-1]);Rb(G(b),a,"not a function, got "+(b&&"object"===typeof b?b.constructor.name||"Object":typeof b));return b}function La(b,a){if("hasOwnProperty"===b)throw Ja("badname",a);}function xc(b,a,c){if(!a)return b;a=a.split(".");for(var d,e=b,f=a.length,g=0;g")+d[2];for(d=d[0];d--;)c=c.lastChild;f=Ya(f,c.childNodes);c=e.firstChild;c.textContent=""}else f.push(a.createTextNode(b));e.textContent="";e.innerHTML="";r(f,function(a){e.appendChild(a)}); -return e}function T(b){if(b instanceof T)return b;var a;C(b)&&(b=N(b),a=!0);if(!(this instanceof T)){if(a&&"<"!=b.charAt(0))throw Tb("nosel");return new T(b)}if(a){a=W;var c;b=(c=gf.exec(b))?[a.createElement(c[1])]:(c=Hc(b,a))?c.childNodes:[]}Ic(this,b)}function Ub(b){return b.cloneNode(!0)}function wb(b,a){a||xb(b);if(b.querySelectorAll)for(var c=b.querySelectorAll("*"),d=0,e=c.length;d 4096 bytes)!"));else{if(p.cookie!==y)for(y=p.cookie,d=y.split("; "),fa={},f=0;fk&&this.remove(q.key),b},get:function(a){if(k").parent()[0])});var f=S(a,b,a,c,d,e);D.$$addScopeClass(a); -var g=null;return function(b,c,d){Rb(b,"scope");d=d||{};var e=d.parentBoundTranscludeFn,h=d.transcludeControllers;d=d.futureParentElement;e&&e.$$boundTransclude&&(e=e.$$boundTransclude);g||(g=(d=d&&d[0])?"foreignobject"!==va(d)&&d.toString().match(/SVG/)?"svg":"html":"html");d="html"!==g?A(Xb(g,A("
").append(a).html())):c?Ka.clone.call(a):a;if(h)for(var k in h)d.data("$"+k+"Controller",h[k].instance);D.$$addScopeInfo(d,b);c&&c(d,b);f&&f(b,d,d,e);return d}}function S(a,b,c,d,e,f){function g(a, -c,d,e){var f,k,l,q,p,s,M;if(m)for(M=Array(c.length),q=0;qK.priority)break;if(V=K.scope)K.templateUrl||(J(V)?(Na("new/isolated scope",P||F,K,w),P=K):Na("new/isolated scope",P,K,w)),F=F||K;da=K.name;!K.templateUrl&&K.controller&&(V=K.controller,S=S||{},Na("'"+da+"' controller",S[da],K,w),S[da]=K);if(V=K.transclude)ka=!0,K.$$tlb||(Na("transclusion",fa,K,w),fa=K),"element"==V?(E=!0,I=K.priority,V=w,w=e.$$element=A(W.createComment(" "+da+": "+ -e[da]+" ")),d=w[0],T(g,Za.call(V,0),d),fb=D(V,f,I,k&&k.name,{nonTlbTranscludeDirective:fa})):(V=A(Ub(d)).contents(),w.empty(),fb=D(V,f));if(K.template)if(x=!0,Na("template",ma,K,w),ma=K,V=G(K.template)?K.template(w,e):K.template,V=Tc(V),K.replace){k=K;V=Sb.test(V)?Uc(Xb(K.templateNamespace,N(V))):[];d=V[0];if(1!=V.length||d.nodeType!==qa)throw la("tplrt",da,"");T(g,w,d);Q={$attr:{}};V=X(d,[],Q);var aa=a.splice(z+1,a.length-(z+1));P&&y(V);a=a.concat(V).concat(aa);R(e,Q);Q=a.length}else w.html(V);if(K.templateUrl)x= -!0,Na("template",ma,K,w),ma=K,K.replace&&(k=K),B=of(a.splice(z,a.length-z),w,e,g,ka&&fb,l,p,{controllerDirectives:S,newIsolateScopeDirective:P,templateDirective:ma,nonTlbTranscludeDirective:fa}),Q=a.length;else if(K.compile)try{za=K.compile(w,e,fb),G(za)?s(null,za,Oa,U):za&&s(za.pre,za.post,Oa,U)}catch(pf){c(pf,wa(w))}K.terminal&&(B.terminal=!0,I=Math.max(I,K.priority))}B.scope=F&&!0===F.scope;B.transcludeOnThisElement=ka;B.elementTranscludeOnThisElement=E;B.templateOnThisElement=x;B.transclude=fb; -m.hasElementTranscludeDirective=E;return B}function y(a){for(var b=0,c=a.length;bq.priority)&&-1!=q.restrict.indexOf(f)&&(k&&(q=Ob(q,{$$start:k,$$end:l})),b.push(q),h=q)}catch(M){c(M)}}return h}function x(b){if(d.hasOwnProperty(b))for(var c=a.get(b+"Directive"),e=0,f=c.length;e"+b+"";return c.childNodes[0].childNodes;default:return b}} -function Q(a,b){if("srcdoc"==b)return Z.HTML;var c=va(a);if("xlinkHref"==b||"form"==c&&"action"==b||"img"!=c&&("src"==b||"ngSrc"==b))return Z.RESOURCE_URL}function Oa(a,c,d,e,f){var h=Q(a,e);f=g[e]||f;var k=b(d,!0,h,f);if(k){if("multiple"===e&&"select"===va(a))throw la("selmulti",wa(a));c.push({priority:100,compile:function(){return{pre:function(a,c,g){c=g.$$observers||(g.$$observers={});if(l.test(e))throw la("nodomevents");var m=g[e];m!==d&&(k=m&&b(m,!0,h,f),d=m);k&&(g[e]=k(a),(c[e]||(c[e]=[])).$$inter= -!0,(g.$$observers&&g.$$observers[e].$$scope||a).$watch(k,function(a,b){"class"===e&&a!=b?g.$updateClass(a,b):g.$set(e,a)}))}}}})}}function T(a,b,c){var d=b[0],e=b.length,f=d.parentNode,g,h;if(a)for(g=0,h=a.length;g=a)return b;for(;a--;)8===b[a].nodeType&&qf.call(b,a,1);return b}function Fe(){var b={},a=!1,c=/^(\S+)(\s+as\s+(\w+))?$/;this.register=function(a,c){La(a, -"controller");J(a)?w(b,a):b[a]=c};this.allowGlobals=function(){a=!0};this.$get=["$injector","$window",function(d,e){function f(a,b,c,d){if(!a||!J(a.$scope))throw R("$controller")("noscp",d,b);a.$scope[b]=c}return function(g,h,l,k){var n,p,q;l=!0===l;k&&C(k)&&(q=k);if(C(g)){k=g.match(c);if(!k)throw rf("ctrlfmt",g);p=k[1];q=q||k[3];g=b.hasOwnProperty(p)?b[p]:xc(h.$scope,p,!0)||(a?xc(e,p,!0):t);sb(g,p,!0)}if(l)return l=(H(g)?g[g.length-1]:g).prototype,n=Object.create(l||null),q&&f(h,q,n,p||g.name),w(function(){d.invoke(g, -n,h,p);return n},{instance:n,identifier:q});n=d.instantiate(g,h,p);q&&f(h,q,n,p||g.name);return n}}]}function Ge(){this.$get=["$window",function(b){return A(b.document)}]}function He(){this.$get=["$log",function(b){return function(a,c){b.error.apply(b,arguments)}}]}function Zb(b,a){if(C(b)){var c=b.replace(sf,"").trim();if(c){var d=a("Content-Type");(d=d&&0===d.indexOf(Wc))||(d=(d=c.match(tf))&&uf[d[0]].test(c));d&&(b=qc(c))}}return b}function Xc(b){var a=ia(),c,d,e;if(!b)return a;r(b.split("\n"), -function(b){e=b.indexOf(":");c=z(N(b.substr(0,e)));d=N(b.substr(e+1));c&&(a[c]=a[c]?a[c]+", "+d:d)});return a}function Yc(b){var a=J(b)?b:t;return function(c){a||(a=Xc(b));return c?(c=a[z(c)],void 0===c&&(c=null),c):a}}function Zc(b,a,c,d){if(G(d))return d(b,a,c);r(d,function(d){b=d(b,a,c)});return b}function Ke(){var b=this.defaults={transformResponse:[Zb],transformRequest:[function(a){return J(a)&&"[object File]"!==Ca.call(a)&&"[object Blob]"!==Ca.call(a)&&"[object FormData]"!==Ca.call(a)?$a(a): -a}],headers:{common:{Accept:"applications/json, text/plain, */*"},post:sa($b),put:sa($b),patch:sa($b)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN"},a=!1;this.useApplyAsync=function(b){return y(b)?(a=!!b,this):a};var c=this.interceptors=[];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(d,e,f,g,h,l){function k(a){function c(a){var b=w({},a);b.data=a.data?Zc(a.data,a.headers,a.status,e.transformResponse):a.data;a=a.status;return 200<=a&&300>a? -b:h.reject(b)}function d(a){var b,c={};r(a,function(a,d){G(a)?(b=a(),null!=b&&(c[d]=b)):c[d]=a});return c}if(!ca.isObject(a))throw R("$http")("badreq",a);var e=w({method:"get",transformRequest:b.transformRequest,transformResponse:b.transformResponse},a);e.headers=function(a){var c=b.headers,e=w({},a.headers),f,g,c=w({},c.common,c[z(a.method)]);a:for(f in c){a=z(f);for(g in e)if(z(g)===a)continue a;e[f]=c[f]}return d(e)}(a);e.method=ub(e.method);var f=[function(a){var d=a.headers,e=Zc(a.data,Yc(d), -t,a.transformRequest);x(e)&&r(d,function(a,b){"content-type"===z(b)&&delete d[b]});x(a.withCredentials)&&!x(b.withCredentials)&&(a.withCredentials=b.withCredentials);return n(a,e).then(c,c)},t],g=h.when(e);for(r(u,function(a){(a.request||a.requestError)&&f.unshift(a.request,a.requestError);(a.response||a.responseError)&&f.push(a.response,a.responseError)});f.length;){a=f.shift();var k=f.shift(),g=g.then(a,k)}g.success=function(a){g.then(function(b){a(b.data,b.status,b.headers,e)});return g};g.error= -function(a){g.then(null,function(b){a(b.data,b.status,b.headers,e)});return g};return g}function n(c,f){function l(b,c,d,e){function f(){m(c,b,d,e)}I&&(200<=b&&300>b?I.put(P,[b,c,Xc(d),e]):I.remove(P));a?g.$applyAsync(f):(f(),g.$$phase||g.$apply())}function m(a,b,d,e){b=Math.max(b,0);(200<=b&&300>b?L.resolve:L.reject)({data:a,status:b,headers:Yc(d),config:c,statusText:e})}function n(a){m(a.data,a.status,sa(a.headers()),a.statusText)}function u(){var a=k.pendingRequests.indexOf(c);-1!==a&&k.pendingRequests.splice(a, -1)}var L=h.defer(),B=L.promise,I,D,S=c.headers,P=p(c.url,c.params);k.pendingRequests.push(c);B.then(u,u);!c.cache&&!b.cache||!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method||(I=J(c.cache)?c.cache:J(b.cache)?b.cache:q);I&&(D=I.get(P),y(D)?D&&G(D.then)?D.then(n,n):H(D)?m(D[1],D[0],sa(D[2]),D[3]):m(D,200,{},"OK"):I.put(P,B));x(D)&&((D=$c(c.url)?e.cookies()[c.xsrfCookieName||b.xsrfCookieName]:t)&&(S[c.xsrfHeaderName||b.xsrfHeaderName]=D),d(c.method,P,f,l,S,c.timeout,c.withCredentials,c.responseType)); -return B}function p(a,b){if(!b)return a;var c=[];Ed(b,function(a,b){null===a||x(a)||(H(a)||(a=[a]),r(a,function(a){J(a)&&(a=ga(a)?a.toISOString():$a(a));c.push(Ea(b)+"="+Ea(a))}))});0=l&&(s.resolve(q),p(M.$$intervalId),delete f[M.$$intervalId]);u||b.$apply()},h);f[M.$$intervalId]=s;return M}var f={};e.cancel=function(b){return b&&b.$$intervalId in f?(f[b.$$intervalId].reject("canceled"),a.clearInterval(b.$$intervalId), -delete f[b.$$intervalId],!0):!1};return e}]}function Rd(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January February March April May June July August September October November December".split(" "),SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "), -DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a",ERANAMES:["Before Christ","Anno Domini"],ERAS:["BC","AD"]},pluralCat:function(b){return 1===b?"one":"other"}}}}function bc(b){b=b.split("/");for(var a=b.length;a--;)b[a]=qb(b[a]); -return b.join("/")}function ad(b,a){var c=Aa(b);a.$$protocol=c.protocol;a.$$host=c.hostname;a.$$port=aa(c.port)||xf[c.protocol]||null}function bd(b,a){var c="/"!==b.charAt(0);c&&(b="/"+b);var d=Aa(b);a.$$path=decodeURIComponent(c&&"/"===d.pathname.charAt(0)?d.pathname.substring(1):d.pathname);a.$$search=sc(d.search);a.$$hash=decodeURIComponent(d.hash);a.$$path&&"/"!=a.$$path.charAt(0)&&(a.$$path="/"+a.$$path)}function ya(b,a){if(0===a.indexOf(b))return a.substr(b.length)}function Ga(b){var a=b.indexOf("#"); -return-1==a?b:b.substr(0,a)}function Fb(b){return b.replace(/(#.+)|#$/,"$1")}function cc(b){return b.substr(0,Ga(b).lastIndexOf("/")+1)}function dc(b,a){this.$$html5=!0;a=a||"";var c=cc(b);ad(b,this);this.$$parse=function(a){var b=ya(c,a);if(!C(b))throw Gb("ipthprfx",a,c);bd(b,this);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var a=Pb(this.$$search),b=this.$$hash?"#"+qb(this.$$hash):"";this.$$url=bc(this.$$path)+(a?"?"+a:"")+b;this.$$absUrl=c+this.$$url.substr(1)};this.$$parseLinkUrl= -function(d,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;(f=ya(b,d))!==t?(g=f,g=(f=ya(a,f))!==t?c+(ya("/",f)||f):b+g):(f=ya(c,d))!==t?g=c+f:c==d+"/"&&(g=c);g&&this.$$parse(g);return!!g}}function ec(b,a){var c=cc(b);ad(b,this);this.$$parse=function(d){d=ya(b,d)||ya(c,d);var e;"#"===d.charAt(0)?(e=ya(a,d),x(e)&&(e=d)):e=this.$$html5?d:"";bd(e,this);d=this.$$path;var f=/^\/[A-Z]:(\/.*)/;0===e.indexOf(b)&&(e=e.replace(b,""));f.exec(e)||(d=(e=f.exec(d))?e[1]:d);this.$$path=d;this.$$compose()}; -this.$$compose=function(){var c=Pb(this.$$search),e=this.$$hash?"#"+qb(this.$$hash):"";this.$$url=bc(this.$$path)+(c?"?"+c:"")+e;this.$$absUrl=b+(this.$$url?a+this.$$url:"")};this.$$parseLinkUrl=function(a,c){return Ga(b)==Ga(a)?(this.$$parse(a),!0):!1}}function cd(b,a){this.$$html5=!0;ec.apply(this,arguments);var c=cc(b);this.$$parseLinkUrl=function(d,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;b==Ga(d)?f=d:(g=ya(c,d))?f=b+a+g:c===d+"/"&&(f=c);f&&this.$$parse(f);return!!f};this.$$compose= -function(){var c=Pb(this.$$search),e=this.$$hash?"#"+qb(this.$$hash):"";this.$$url=bc(this.$$path)+(c?"?"+c:"")+e;this.$$absUrl=b+a+this.$$url}}function Hb(b){return function(){return this[b]}}function dd(b,a){return function(c){if(x(c))return this[b];this[b]=a(c);this.$$compose();return this}}function Me(){var b="",a={enabled:!1,requireBase:!0,rewriteLinks:!0};this.hashPrefix=function(a){return y(a)?(b=a,this):b};this.html5Mode=function(b){return Wa(b)?(a.enabled=b,this):J(b)?(Wa(b.enabled)&&(a.enabled= -b.enabled),Wa(b.requireBase)&&(a.requireBase=b.requireBase),Wa(b.rewriteLinks)&&(a.rewriteLinks=b.rewriteLinks),this):a};this.$get=["$rootScope","$browser","$sniffer","$rootElement","$window",function(c,d,e,f,g){function h(a,b,c){var e=k.url(),f=k.$$state;try{d.url(a,b,c),k.$$state=d.state()}catch(g){throw k.url(e),k.$$state=f,g;}}function l(a,b){c.$broadcast("$locationChangeSuccess",k.absUrl(),a,k.$$state,b)}var k,n;n=d.baseHref();var p=d.url(),q;if(a.enabled){if(!n&&a.requireBase)throw Gb("nobase"); -q=p.substring(0,p.indexOf("/",p.indexOf("//")+2))+(n||"/");n=e.history?dc:cd}else q=Ga(p),n=ec;k=new n(q,"#"+b);k.$$parseLinkUrl(p,p);k.$$state=d.state();var u=/^\s*(javascript|mailto):/i;f.on("click",function(b){if(a.rewriteLinks&&!b.ctrlKey&&!b.metaKey&&!b.shiftKey&&2!=b.which&&2!=b.button){for(var e=A(b.target);"a"!==va(e[0]);)if(e[0]===f[0]||!(e=e.parent())[0])return;var h=e.prop("href"),l=e.attr("href")||e.attr("xlink:href");J(h)&&"[object SVGAnimatedString]"===h.toString()&&(h=Aa(h.animVal).href); -u.test(h)||!h||e.attr("target")||b.isDefaultPrevented()||!k.$$parseLinkUrl(h,l)||(b.preventDefault(),k.absUrl()!=d.url()&&(c.$apply(),g.angular["ff-684208-preventDefault"]=!0))}});Fb(k.absUrl())!=Fb(p)&&d.url(k.absUrl(),!0);var s=!0;d.onUrlChange(function(a,b){c.$evalAsync(function(){var d=k.absUrl(),e=k.$$state,f;k.$$parse(a);k.$$state=b;f=c.$broadcast("$locationChangeStart",a,d,b,e).defaultPrevented;k.absUrl()===a&&(f?(k.$$parse(d),k.$$state=e,h(d,!1,e)):(s=!1,l(d,e)))});c.$$phase||c.$digest()}); -c.$watch(function(){var a=Fb(d.url()),b=Fb(k.absUrl()),f=d.state(),g=k.$$replace,q=a!==b||k.$$html5&&e.history&&f!==k.$$state;if(s||q)s=!1,c.$evalAsync(function(){var b=k.absUrl(),d=c.$broadcast("$locationChangeStart",b,a,k.$$state,f).defaultPrevented;k.absUrl()===b&&(d?(k.$$parse(a),k.$$state=f):(q&&h(b,g,f===k.$$state?null:k.$$state),l(a,f)))});k.$$replace=!1});return k}]}function Ne(){var b=!0,a=this;this.debugEnabled=function(a){return y(a)?(b=a,this):b};this.$get=["$window",function(c){function d(a){a instanceof -Error&&(a.stack?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=c.console||{},e=b[a]||b.log||E;a=!1;try{a=!!e.apply}catch(l){}return a?function(){var a=[];r(arguments,function(b){a.push(d(b))});return e.apply(b,a)}:function(a,b){e(a,null==b?"":b)}}return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){b&&c.apply(a, -arguments)}}()}}]}function ua(b,a){if("__defineGetter__"===b||"__defineSetter__"===b||"__lookupGetter__"===b||"__lookupSetter__"===b||"__proto__"===b)throw na("isecfld",a);return b}function oa(b,a){if(b){if(b.constructor===b)throw na("isecfn",a);if(b.window===b)throw na("isecwindow",a);if(b.children&&(b.nodeName||b.prop&&b.attr&&b.find))throw na("isecdom",a);if(b===Object)throw na("isecobj",a);}return b}function fc(b){return b.constant}function hb(b,a,c,d,e){oa(b,e);oa(a,e);c=c.split(".");for(var f, -g=0;1h?ed(g[0],g[1],g[2],g[3],g[4],c,d):function(a,b){var e=0,f;do f=ed(g[e++],g[e++],g[e++],g[e++],g[e++],c,d)(a,b),b=t,a=f;while(e=this.promise.$$state.status&&d&&d.length&&b(function(){for(var b,e,f=0,g=d.length;fa)for(b in l++,f)e.hasOwnProperty(b)||(u--,delete f[b])}else f!==e&&(f=e,l++);return l}}c.$stateful=!0;var d=this,e,f,g,k=1r&&(M=4-r,O[M]||(O[M]=[]),O[M].push({msg:G(b.exp)?"fn: "+(b.exp.name||b.exp.toString()):b.exp,newVal:f,oldVal:h}));else if(b=== -d){n=!1;break a}}catch(A){g(A)}if(!(k=t.$$childHead||t!==this&&t.$$nextSibling))for(;t!==this&&!(k=t.$$nextSibling);)t=t.$parent}while(t=k);if((n||m.length)&&!r--)throw v.$$phase=null,c("infdig",a,O);}while(n||m.length);for(v.$$phase=null;F.length;)try{F.shift()()}catch(x){g(x)}},$destroy:function(){if(!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;if(this!==v){for(var b in this.$$listenerCount)q(this,this.$$listenerCount[b],b);a.$$childHead==this&&(a.$$childHead= -this.$$nextSibling);a.$$childTail==this&&(a.$$childTail=this.$$prevSibling);this.$$prevSibling&&(this.$$prevSibling.$$nextSibling=this.$$nextSibling);this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=this.$$prevSibling);this.$destroy=this.$digest=this.$apply=this.$evalAsync=this.$applyAsync=E;this.$on=this.$watch=this.$watchGroup=function(){return E};this.$$listeners={};this.$parent=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=this.$root=this.$$watchers=null}}},$eval:function(a, -b){return h(a)(this,b)},$evalAsync:function(a,b){v.$$phase||m.length||l.defer(function(){m.length&&v.$digest()});m.push({scope:this,expression:a,locals:b})},$$postDigest:function(a){F.push(a)},$apply:function(a){try{return p("$apply"),this.$eval(a)}catch(b){g(b)}finally{v.$$phase=null;try{v.$digest()}catch(c){throw g(c),c;}}},$applyAsync:function(a){function b(){c.$eval(a)}var c=this;a&&t.push(b);M()},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);var d=this;do d.$$listenerCount[a]|| -(d.$$listenerCount[a]=0),d.$$listenerCount[a]++;while(d=d.$parent);var e=this;return function(){var d=c.indexOf(b);-1!==d&&(c[d]=null,q(e,1,a))}},$emit:function(a,b){var c=[],d,e=this,f=!1,h={name:a,targetScope:e,stopPropagation:function(){f=!0},preventDefault:function(){h.defaultPrevented=!0},defaultPrevented:!1},k=Ya([h],arguments,1),l,q;do{d=e.$$listeners[a]||c;h.currentScope=e;l=0;for(q=d.length;lQa)throw Ba("iequirks");var d=sa(pa);d.isEnabled=function(){return b};d.trustAs=c.trustAs;d.getTrusted=c.getTrusted;d.valueOf=c.valueOf;b||(d.trustAs= -d.getTrusted=function(a,b){return b},d.valueOf=ra);d.parseAs=function(b,c){var e=a(c);return e.literal&&e.constant?e:a(c,function(a){return d.getTrusted(b,a)})};var e=d.parseAs,f=d.getTrusted,g=d.trustAs;r(pa,function(a,b){var c=z(b);d[db("parse_as_"+c)]=function(b){return e(a,b)};d[db("get_trusted_"+c)]=function(b){return f(a,b)};d[db("trust_as_"+c)]=function(b){return g(a,b)}});return d}]}function Ue(){this.$get=["$window","$document",function(b,a){var c={},d=aa((/android (\d+)/.exec(z((b.navigator|| -{}).userAgent))||[])[1]),e=/Boxee/i.test((b.navigator||{}).userAgent),f=a[0]||{},g,h=/^(Moz|webkit|ms)(?=[A-Z])/,l=f.body&&f.body.style,k=!1,n=!1;if(l){for(var p in l)if(k=h.exec(p)){g=k[0];g=g.substr(0,1).toUpperCase()+g.substr(1);break}g||(g="WebkitOpacity"in l&&"webkit");k=!!("transition"in l||g+"Transition"in l);n=!!("animation"in l||g+"Animation"in l);!d||k&&n||(k=C(f.body.style.webkitTransition),n=C(f.body.style.webkitAnimation))}return{history:!(!b.history||!b.history.pushState||4>d||e),hasEvent:function(a){if("input"=== -a&&11>=Qa)return!1;if(x(c[a])){var b=f.createElement("div");c[a]="on"+a in b}return c[a]},csp:bb(),vendorPrefix:g,transitions:k,animations:n,android:d}}]}function We(){this.$get=["$templateCache","$http","$q",function(b,a,c){function d(e,f){d.totalPendingRequests++;var g=a.defaults&&a.defaults.transformResponse;H(g)?g=g.filter(function(a){return a!==Zb}):g===Zb&&(g=null);return a.get(e,{cache:b,transformResponse:g})["finally"](function(){d.totalPendingRequests--}).then(function(a){return a.data}, -function(a){if(!f)throw la("tpload",e);return c.reject(a)})}d.totalPendingRequests=0;return d}]}function Xe(){this.$get=["$rootScope","$browser","$location",function(b,a,c){return{findBindings:function(a,b,c){a=a.getElementsByClassName("ng-binding");var g=[];r(a,function(a){var d=ca.element(a).data("$binding");d&&r(d,function(d){c?(new RegExp("(^|\\s)"+gd(b)+"(\\s|\\||$)")).test(d)&&g.push(a):-1!=d.indexOf(b)&&g.push(a)})});return g},findModels:function(a,b,c){for(var g=["ng-","data-ng-","ng\\:"], -h=0;hb;b=Math.abs(b);var g=b+"",h="",l=[],k=!1;if(-1!==g.indexOf("e")){var n=g.match(/([\d\.]+)e(-?)(\d+)/);n&&"-"==n[2]&&n[3]>e+1?b=0:(h=g,k=!0)}if(k)0b&&(h=b.toFixed(e),b=parseFloat(h));else{g=(g.split(od)[1]|| -"").length;x(e)&&(e=Math.min(Math.max(a.minFrac,g),a.maxFrac));b=+(Math.round(+(b.toString()+"e"+e)).toString()+"e"+-e);var g=(""+b).split(od),k=g[0],g=g[1]||"",p=0,q=a.lgSize,u=a.gSize;if(k.length>=q+u)for(p=k.length-q,n=0;nb&&(d="-",b=-b);for(b=""+b;b.length-c)e+=c;0===e&&-12==c&&(e=12);return Ib(e,a,d)}}function Jb(b,a){return function(c,d){var e=c["get"+b](),f=ub(a?"SHORT"+b:b);return d[f][e]}}function pd(b){var a=(new Date(b,0,1)).getDay();return new Date(b,0,(4>=a?5:12)-a)}function qd(b){return function(a){var c=pd(a.getFullYear());a=+new Date(a.getFullYear(),a.getMonth(),a.getDate()+ -(4-a.getDay()))-+c;a=1+Math.round(a/6048E5);return Ib(a,b)}}function ic(b,a){return 0>=b.getFullYear()?a.ERAS[0]:a.ERAS[1]}function kd(b){function a(a){var b;if(b=a.match(c)){a=new Date(0);var f=0,g=0,h=b[8]?a.setUTCFullYear:a.setFullYear,l=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=aa(b[9]+b[10]),g=aa(b[9]+b[11]));h.call(a,aa(b[1]),aa(b[2])-1,aa(b[3]));f=aa(b[4]||0)-f;g=aa(b[5]||0)-g;h=aa(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));l.call(a,f,g,h,b)}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; -return function(c,e,f){var g="",h=[],l,k;e=e||"mediumDate";e=b.DATETIME_FORMATS[e]||e;C(c)&&(c=Kf.test(c)?aa(c):a(c));Y(c)&&(c=new Date(c));if(!ga(c))return c;for(;e;)(k=Lf.exec(e))?(h=Ya(h,k,1),e=h.pop()):(h.push(e),e=null);f&&"UTC"===f&&(c=new Date(c.getTime()),c.setMinutes(c.getMinutes()+c.getTimezoneOffset()));r(h,function(a){l=Mf[a];g+=l?l(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function Ff(){return function(b,a){x(a)&&(a=2);return $a(b,a)}}function Gf(){return function(b, -a){Y(b)&&(b=b.toString());return H(b)||C(b)?(a=Infinity===Math.abs(Number(a))?Number(a):aa(a))?0b||37<=b&&40>=b||n(a,this,this.value)});if(e.hasEvent("paste"))a.on("paste cut",n)}a.on("change",l);d.$render=function(){a.val(d.$isEmpty(d.$viewValue)?"":d.$viewValue)}}function Mb(b,a){return function(c,d){var e,f;if(ga(c))return c;if(C(c)){'"'==c.charAt(0)&&'"'==c.charAt(c.length-1)&&(c=c.substring(1,c.length-1));if(Nf.test(c))return new Date(c);b.lastIndex= -0;if(e=b.exec(c))return e.shift(),f=d?{yyyy:d.getFullYear(),MM:d.getMonth()+1,dd:d.getDate(),HH:d.getHours(),mm:d.getMinutes(),ss:d.getSeconds(),sss:d.getMilliseconds()/1E3}:{yyyy:1970,MM:1,dd:1,HH:0,mm:0,ss:0,sss:0},r(e,function(b,c){c=r}; -g.$observe("min",function(a){r=q(a);h.$validate()})}if(y(g.max)||g.ngMax){var v;h.$validators.max=function(a){return!p(a)||x(v)||c(a)<=v};g.$observe("max",function(a){v=q(a);h.$validate()})}}}function td(b,a,c,d){(d.$$hasNativeValidators=J(a[0].validity))&&d.$parsers.push(function(b){var c=a.prop("validity")||{};return c.badInput&&!c.typeMismatch?t:b})}function ud(b,a,c,d,e){if(y(d)){b=b(d);if(!b.constant)throw R("ngModel")("constexpr",c,d);return b(a)}return e}function kc(b,a){b="ngClass"+b;return["$animate", -function(c){function d(a,b){var c=[],d=0;a:for(;d(?:<\/\1>|)$/,Sb=/<|&#?\w+;/,ef=/<([\w:]+)/,ff=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,ja={option:[1,'"],thead:[1,"","
"],col:[2,"", -"
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ja.optgroup=ja.option;ja.tbody=ja.tfoot=ja.colgroup=ja.caption=ja.thead;ja.th=ja.td;var Ka=T.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=!1;"complete"===W.readyState?setTimeout(a):(this.on("DOMContentLoaded",a),T(Q).on("load",a))},toString:function(){var b=[];r(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return 0<= -b?A(this[b]):A(this[this.length+b])},length:0,push:Pf,sort:[].sort,splice:[].splice},Eb={};r("multiple selected checked disabled readOnly required open".split(" "),function(b){Eb[z(b)]=b});var Oc={};r("input select option textarea button form details".split(" "),function(b){Oc[b]=!0});var Pc={ngMinlength:"minlength",ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern"};r({data:Vb,removeData:xb},function(b,a){T[a]=b});r({data:Vb,inheritedData:Db,scope:function(b){return A.data(b,"$scope")|| -Db(b.parentNode||b,["$isolateScope","$scope"])},isolateScope:function(b){return A.data(b,"$isolateScope")||A.data(b,"$isolateScopeNoTemplate")},controller:Kc,injector:function(b){return Db(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:Ab,css:function(b,a,c){a=db(a);if(y(c))b.style[a]=c;else return b.style[a]},attr:function(b,a,c){var d=z(a);if(Eb[d])if(y(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||E).specified? -d:t;else if(y(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),null===b?t:b},prop:function(b,a,c){if(y(c))b[a]=c;else return b[a]},text:function(){function b(a,b){if(x(b)){var d=a.nodeType;return d===qa||d===pb?a.textContent:""}a.textContent=b}b.$dv="";return b}(),val:function(b,a){if(x(a)){if(b.multiple&&"select"===va(b)){var c=[];r(b.options,function(a){a.selected&&c.push(a.value||a.text)});return 0===c.length?null:c}return b.value}b.value=a},html:function(b,a){if(x(a))return b.innerHTML; -wb(b,!0);b.innerHTML=a},empty:Lc},function(b,a){T.prototype[a]=function(a,d){var e,f,g=this.length;if(b!==Lc&&(2==b.length&&b!==Ab&&b!==Kc?a:d)===t){if(J(a)){for(e=0;e":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a,c,d,e){return d(a,c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)}, -"||":function(a,c,d,e){return d(a,c)||e(a,c)},"!":function(a,c,d){return!d(a,c)},"=":!0,"|":!0}),Zf={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},hc=function(a){this.options=a};hc.prototype={constructor:hc,lex:function(a){this.text=a;this.index=0;for(this.tokens=[];this.index=a&&"string"===typeof a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdent:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isExpOperator:function(a){return"-"===a||"+"===a||this.isNumber(a)},throwError:function(a,c,d){d=d||this.index;c=y(c)?"s "+c+"-"+this.index+" ["+this.text.substring(c,d)+"]":" "+d;throw na("lexerr",a,c,this.text);},readNumber:function(){for(var a="",c=this.index;this.index< -this.text.length;){var d=z(this.text.charAt(this.index));if("."==d||this.isNumber(d))a+=d;else{var e=this.peek();if("e"==d&&this.isExpOperator(e))a+=d;else if(this.isExpOperator(d)&&e&&this.isNumber(e)&&"e"==a.charAt(a.length-1))a+=d;else if(!this.isExpOperator(d)||e&&this.isNumber(e)||"e"!=a.charAt(a.length-1))break;else this.throwError("Invalid exponent")}this.index++}this.tokens.push({index:c,text:a,constant:!0,value:Number(a)})},readIdent:function(){for(var a=this.index;this.indexa){a=this.tokens[a];var g=a.text;if(g===c||g===d||g===e||g=== -f||!(c||d||e||f))return a}return!1},expect:function(a,c,d,e){return(a=this.peek(a,c,d,e))?(this.tokens.shift(),a):!1},consume:function(a){if(0===this.tokens.length)throw na("ueoe",this.text);var c=this.expect(a);c||this.throwError("is unexpected, expecting ["+a+"]",this.peek());return c},unaryFn:function(a,c){var d=nb[a];return w(function(a,f){return d(a,f,c)},{constant:c.constant,inputs:[c]})},binaryFn:function(a,c,d,e){var f=nb[c];return w(function(c,e){return f(c,e,a,d)},{constant:a.constant&& -d.constant,inputs:!e&&[a,d]})},identifier:function(){for(var a=this.consume().text;this.peek(".")&&this.peekAhead(1).identifier&&!this.peekAhead(2,"(");)a+=this.consume().text+this.consume().text;return zf(a,this.options,this.text)},constant:function(){var a=this.consume().value;return w(function(){return a},{constant:!0,literal:!0})},statements:function(){for(var a=[];;)if(0","<=",">=");)a=this.binaryFn(a,c.text, -this.additive());return a},additive:function(){for(var a=this.multiplicative(),c;c=this.expect("+","-");)a=this.binaryFn(a,c.text,this.multiplicative());return a},multiplicative:function(){for(var a=this.unary(),c;c=this.expect("*","/","%");)a=this.binaryFn(a,c.text,this.unary());return a},unary:function(){var a;return this.expect("+")?this.primary():(a=this.expect("-"))?this.binaryFn(ib.ZERO,a.text,this.unary()):(a=this.expect("!"))?this.unaryFn(a.text,this.unary()):this.primary()},fieldAccess:function(a){var c= -this.identifier();return w(function(d,e,f){d=f||a(d,e);return null==d?t:c(d)},{assign:function(d,e,f){var g=a(d,f);g||a.assign(d,g={},f);return c.assign(g,e)}})},objectIndex:function(a){var c=this.text,d=this.expression();this.consume("]");return w(function(e,f){var g=a(e,f),h=d(e,f);ua(h,c);return g?oa(g[h],c):t},{assign:function(e,f,g){var h=ua(d(e,g),c),l=oa(a(e,g),c);l||a.assign(e,l={},g);return l[h]=f}})},functionCall:function(a,c){var d=[];if(")"!==this.peekToken().text){do d.push(this.expression()); -while(this.expect(","))}this.consume(")");var e=this.text,f=d.length?[]:null;return function(g,h){var l=c?c(g,h):y(c)?t:g,k=a(g,h,l)||E;if(f)for(var n=d.length;n--;)f[n]=oa(d[n](g,h),e);oa(l,e);if(k){if(k.constructor===k)throw na("isecfn",e);if(k===Wf||k===Xf||k===Yf)throw na("isecff",e);}l=k.apply?k.apply(l,f):k(f[0],f[1],f[2],f[3],f[4]);f&&(f.length=0);return oa(l,e)}},arrayDeclaration:function(){var a=[];if("]"!==this.peekToken().text){do{if(this.peek("]"))break;a.push(this.expression())}while(this.expect(",")) -}this.consume("]");return w(function(c,d){for(var e=[],f=0,g=a.length;fa.getHours()?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){a=-1*a.getTimezoneOffset();return a=(0<=a?"+":"")+(Ib(Math[0=a.getFullYear()?c.ERANAMES[0]:c.ERANAMES[1]}},Lf=/((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/, -Kf=/^\-?\d+$/;kd.$inject=["$locale"];var Hf=ea(z),If=ea(ub);md.$inject=["$parse"];var Td=ea({restrict:"E",compile:function(a,c){if(!c.href&&!c.xlinkHref&&!c.name)return function(a,c){if("a"===c[0].nodeName.toLowerCase()){var f="[object SVGAnimatedString]"===Ca.call(c.prop("href"))?"xlink:href":"href";c.on("click",function(a){c.attr(f)||a.preventDefault()})}}}}),vb={};r(Eb,function(a,c){if("multiple"!=a){var d=xa("ng-"+c);vb[d]=function(){return{restrict:"A",priority:100,link:function(a,f,g){a.$watch(g[d], -function(a){g.$set(c,!!a)})}}}}});r(Pc,function(a,c){vb[c]=function(){return{priority:100,link:function(a,e,f){if("ngPattern"===c&&"/"==f.ngPattern.charAt(0)&&(e=f.ngPattern.match(Of))){f.$set("ngPattern",new RegExp(e[1],e[2]));return}a.$watch(f[c],function(a){f.$set(c,a)})}}}});r(["src","srcset","href"],function(a){var c=xa("ng-"+a);vb[c]=function(){return{priority:99,link:function(d,e,f){var g=a,h=a;"href"===a&&"[object SVGAnimatedString]"===Ca.call(e.prop("href"))&&(h="xlinkHref",f.$attr[h]="xlink:href", -g=null);f.$observe(c,function(c){c?(f.$set(h,c),Qa&&g&&e.prop(g,f[h])):"href"===a&&f.$set(h,null)})}}}});var Kb={$addControl:E,$$renameControl:function(a,c){a.$name=c},$removeControl:E,$setValidity:E,$setDirty:E,$setPristine:E,$setSubmitted:E};rd.$inject=["$element","$attrs","$scope","$animate","$interpolate"];var yd=function(a){return["$timeout",function(c){return{name:"form",restrict:a?"EAC":"E",controller:rd,compile:function(d,e){d.addClass(Ra).addClass(lb);var f=e.name?"name":a&&e.ngForm?"ngForm": -!1;return{pre:function(a,d,e,k){if(!("action"in e)){var n=function(c){a.$apply(function(){k.$commitViewValue();k.$setSubmitted()});c.preventDefault()};d[0].addEventListener("submit",n,!1);d.on("$destroy",function(){c(function(){d[0].removeEventListener("submit",n,!1)},0,!1)})}var p=k.$$parentForm;f&&(hb(a,null,k.$name,k,k.$name),e.$observe(f,function(c){k.$name!==c&&(hb(a,null,k.$name,t,k.$name),p.$$renameControl(k,c),hb(a,null,k.$name,k,k.$name))}));d.on("$destroy",function(){p.$removeControl(k); -f&&hb(a,null,e[f],t,k.$name);w(k,Kb)})}}}}}]},Ud=yd(),ge=yd(!0),Nf=/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/,$f=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,ag=/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i,bg=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,zd=/^(\d{4})-(\d{2})-(\d{2})$/,Ad=/^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,lc=/^(\d{4})-W(\d\d)$/,Bd=/^(\d{4})-(\d\d)$/, -Cd=/^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,Dd={text:function(a,c,d,e,f,g){jb(a,c,d,e,f,g);jc(e)},date:kb("date",zd,Mb(zd,["yyyy","MM","dd"]),"yyyy-MM-dd"),"datetime-local":kb("datetimelocal",Ad,Mb(Ad,"yyyy MM dd HH mm ss sss".split(" ")),"yyyy-MM-ddTHH:mm:ss.sss"),time:kb("time",Cd,Mb(Cd,["HH","mm","ss","sss"]),"HH:mm:ss.sss"),week:kb("week",lc,function(a,c){if(ga(a))return a;if(C(a)){lc.lastIndex=0;var d=lc.exec(a);if(d){var e=+d[1],f=+d[2],g=d=0,h=0,l=0,k=pd(e),f=7*(f-1);c&&(d=c.getHours(),g= -c.getMinutes(),h=c.getSeconds(),l=c.getMilliseconds());return new Date(e,0,k.getDate()+f,d,g,h,l)}}return NaN},"yyyy-Www"),month:kb("month",Bd,Mb(Bd,["yyyy","MM"]),"yyyy-MM"),number:function(a,c,d,e,f,g){td(a,c,d,e);jb(a,c,d,e,f,g);e.$$parserName="number";e.$parsers.push(function(a){return e.$isEmpty(a)?null:bg.test(a)?parseFloat(a):t});e.$formatters.push(function(a){if(!e.$isEmpty(a)){if(!Y(a))throw Nb("numfmt",a);a=a.toString()}return a});if(y(d.min)||d.ngMin){var h;e.$validators.min=function(a){return e.$isEmpty(a)|| -x(h)||a>=h};d.$observe("min",function(a){y(a)&&!Y(a)&&(a=parseFloat(a,10));h=Y(a)&&!isNaN(a)?a:t;e.$validate()})}if(y(d.max)||d.ngMax){var l;e.$validators.max=function(a){return e.$isEmpty(a)||x(l)||a<=l};d.$observe("max",function(a){y(a)&&!Y(a)&&(a=parseFloat(a,10));l=Y(a)&&!isNaN(a)?a:t;e.$validate()})}},url:function(a,c,d,e,f,g){jb(a,c,d,e,f,g);jc(e);e.$$parserName="url";e.$validators.url=function(a,c){var d=a||c;return e.$isEmpty(d)||$f.test(d)}},email:function(a,c,d,e,f,g){jb(a,c,d,e,f,g);jc(e); -e.$$parserName="email";e.$validators.email=function(a,c){var d=a||c;return e.$isEmpty(d)||ag.test(d)}},radio:function(a,c,d,e){x(d.name)&&c.attr("name",++ob);c.on("click",function(a){c[0].checked&&e.$setViewValue(d.value,a&&a.type)});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e,f,g,h,l){var k=ud(l,a,"ngTrueValue",d.ngTrueValue,!0),n=ud(l,a,"ngFalseValue",d.ngFalseValue,!1);c.on("click",function(a){e.$setViewValue(c[0].checked,a&& -a.type)});e.$render=function(){c[0].checked=e.$viewValue};e.$isEmpty=function(a){return!1===a};e.$formatters.push(function(a){return ha(a,k)});e.$parsers.push(function(a){return a?k:n})},hidden:E,button:E,submit:E,reset:E,file:E},zc=["$browser","$sniffer","$filter","$parse",function(a,c,d,e){return{restrict:"E",require:["?ngModel"],link:{pre:function(f,g,h,l){l[0]&&(Dd[z(h.type)]||Dd.text)(f,g,h,l[0],c,a,d,e)}}}}],cg=/^(true|false|\d+)$/,ye=function(){return{restrict:"A",priority:100,compile:function(a, -c){return cg.test(c.ngValue)?function(a,c,f){f.$set("value",a.$eval(f.ngValue))}:function(a,c,f){a.$watch(f.ngValue,function(a){f.$set("value",a)})}}}},Zd=["$compile",function(a){return{restrict:"AC",compile:function(c){a.$$addBindingClass(c);return function(c,e,f){a.$$addBindingInfo(e,f.ngBind);e=e[0];c.$watch(f.ngBind,function(a){e.textContent=a===t?"":a})}}}}],ae=["$interpolate","$compile",function(a,c){return{compile:function(d){c.$$addBindingClass(d);return function(d,f,g){d=a(f.attr(g.$attr.ngBindTemplate)); -c.$$addBindingInfo(f,d.expressions);f=f[0];g.$observe("ngBindTemplate",function(a){f.textContent=a===t?"":a})}}}}],$d=["$sce","$parse","$compile",function(a,c,d){return{restrict:"A",compile:function(e,f){var g=c(f.ngBindHtml),h=c(f.ngBindHtml,function(a){return(a||"").toString()});d.$$addBindingClass(e);return function(c,e,f){d.$$addBindingInfo(e,f.ngBindHtml);c.$watch(h,function(){e.html(a.getTrustedHtml(g(c))||"")})}}}}],xe=ea({restrict:"A",require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}), -be=kc("",!0),de=kc("Odd",0),ce=kc("Even",1),ee=Ia({compile:function(a,c){c.$set("ngCloak",t);a.removeClass("ng-cloak")}}),fe=[function(){return{restrict:"A",scope:!0,controller:"@",priority:500}}],Ec={},dg={blur:!0,focus:!0};r("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),function(a){var c=xa("ng-"+a);Ec[c]=["$parse","$rootScope",function(d,e){return{restrict:"A",compile:function(f,g){var h= -d(g[c],null,!0);return function(c,d){d.on(a,function(d){var f=function(){h(c,{$event:d})};dg[a]&&e.$$phase?c.$evalAsync(f):c.$apply(f)})}}}}]});var ie=["$animate",function(a){return{multiElement:!0,transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(c,d,e,f,g){var h,l,k;c.$watch(e.ngIf,function(c){c?l||g(function(c,f){l=f;c[c.length++]=W.createComment(" end ngIf: "+e.ngIf+" ");h={clone:c};a.enter(c,d.parent(),d)}):(k&&(k.remove(),k=null),l&&(l.$destroy(),l=null),h&&(k= -tb(h.clone),a.leave(k).then(function(){k=null}),h=null))})}}}],je=["$templateRequest","$anchorScroll","$animate","$sce",function(a,c,d,e){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element",controller:ca.noop,compile:function(f,g){var h=g.ngInclude||g.src,l=g.onload||"",k=g.autoscroll;return function(f,g,q,r,s){var t=0,v,m,F,w=function(){m&&(m.remove(),m=null);v&&(v.$destroy(),v=null);F&&(d.leave(F).then(function(){m=null}),m=F,F=null)};f.$watch(e.parseAsResourceUrl(h),function(e){var h= -function(){!y(k)||k&&!f.$eval(k)||c()},m=++t;e?(a(e,!0).then(function(a){if(m===t){var c=f.$new();r.template=a;a=s(c,function(a){w();d.enter(a,null,g).then(h)});v=c;F=a;v.$emit("$includeContentLoaded",e);f.$eval(l)}},function(){m===t&&(w(),f.$emit("$includeContentError",e))}),f.$emit("$includeContentRequested",e)):(w(),r.template=null)})}}}}],Ae=["$compile",function(a){return{restrict:"ECA",priority:-400,require:"ngInclude",link:function(c,d,e,f){/SVG/.test(d[0].toString())?(d.empty(),a(Hc(f.template, -W).childNodes)(c,function(a){d.append(a)},{futureParentElement:d})):(d.html(f.template),a(d.contents())(c))}}}],ke=Ia({priority:450,compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),we=function(){return{restrict:"A",priority:100,require:"ngModel",link:function(a,c,d,e){var f=c.attr(d.$attr.ngList)||", ",g="false"!==d.ngTrim,h=g?N(f):f;e.$parsers.push(function(a){if(!x(a)){var c=[];a&&r(a.split(h),function(a){a&&c.push(g?N(a):a)});return c}});e.$formatters.push(function(a){return H(a)? -a.join(f):t});e.$isEmpty=function(a){return!a||!a.length}}}},lb="ng-valid",vd="ng-invalid",Ra="ng-pristine",Lb="ng-dirty",xd="ng-pending",Nb=new R("ngModel"),eg=["$scope","$exceptionHandler","$attrs","$element","$parse","$animate","$timeout","$rootScope","$q","$interpolate",function(a,c,d,e,f,g,h,l,k,n){this.$modelValue=this.$viewValue=Number.NaN;this.$$rawModelValue=t;this.$validators={};this.$asyncValidators={};this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$untouched=!0; -this.$touched=!1;this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$error={};this.$$success={};this.$pending=t;this.$name=n(d.name||"",!1)(a);var p=f(d.ngModel),q=p.assign,u=p,s=q,M=null,v,m=this;this.$$setOptions=function(a){if((m.$options=a)&&a.getterSetter){var c=f(d.ngModel+"()"),g=f(d.ngModel+"($$$p)");u=function(a){var d=p(a);G(d)&&(d=c(a));return d};s=function(a,c){G(p(a))?g(a,{$$$p:m.$modelValue}):q(a,m.$modelValue)}}else if(!p.assign)throw Nb("nonassign",d.ngModel,wa(e)); -};this.$render=E;this.$isEmpty=function(a){return x(a)||""===a||null===a||a!==a};var F=e.inheritedData("$formController")||Kb,w=0;sd({ctrl:this,$element:e,set:function(a,c){a[c]=!0},unset:function(a,c){delete a[c]},parentForm:F,$animate:g});this.$setPristine=function(){m.$dirty=!1;m.$pristine=!0;g.removeClass(e,Lb);g.addClass(e,Ra)};this.$setDirty=function(){m.$dirty=!0;m.$pristine=!1;g.removeClass(e,Ra);g.addClass(e,Lb);F.$setDirty()};this.$setUntouched=function(){m.$touched=!1;m.$untouched=!0;g.setClass(e, -"ng-untouched","ng-touched")};this.$setTouched=function(){m.$touched=!0;m.$untouched=!1;g.setClass(e,"ng-touched","ng-untouched")};this.$rollbackViewValue=function(){h.cancel(M);m.$viewValue=m.$$lastCommittedViewValue;m.$render()};this.$validate=function(){if(!Y(m.$modelValue)||!isNaN(m.$modelValue)){var a=m.$$rawModelValue,c=m.$valid,d=m.$modelValue,e=m.$options&&m.$options.allowInvalid;m.$$runValidators(a,m.$$lastCommittedViewValue,function(f){e||c===f||(m.$modelValue=f?a:t,m.$modelValue!==d&&m.$$writeModelToScope())})}}; -this.$$runValidators=function(a,c,d){function e(){var d=!0;r(m.$validators,function(e,f){var h=e(a,c);d=d&&h;g(f,h)});return d?!0:(r(m.$asyncValidators,function(a,c){g(c,null)}),!1)}function f(){var d=[],e=!0;r(m.$asyncValidators,function(f,h){var k=f(a,c);if(!k||!G(k.then))throw Nb("$asyncValidators",k);g(h,t);d.push(k.then(function(){g(h,!0)},function(a){e=!1;g(h,!1)}))});d.length?k.all(d).then(function(){h(e)},E):h(!0)}function g(a,c){l===w&&m.$setValidity(a,c)}function h(a){l===w&&d(a)}w++;var l= -w;(function(){var a=m.$$parserName||"parse";if(v===t)g(a,null);else return v||(r(m.$validators,function(a,c){g(c,null)}),r(m.$asyncValidators,function(a,c){g(c,null)})),g(a,v),v;return!0})()?e()?f():h(!1):h(!1)};this.$commitViewValue=function(){var a=m.$viewValue;h.cancel(M);if(m.$$lastCommittedViewValue!==a||""===a&&m.$$hasNativeValidators)m.$$lastCommittedViewValue=a,m.$pristine&&this.$setDirty(),this.$$parseAndValidate()};this.$$parseAndValidate=function(){var c=m.$$lastCommittedViewValue;if(v= -x(c)?t:!0)for(var d=0;dz;)d=t.pop(),n(O,d.label,!1),d.element.remove()}for(;R.length> -x;){l=R.pop();for(z=1;za&&q.removeOption(c)})}var v;if(!(v=s.match(d)))throw gg("iexp",s,wa(f));var C=c(v[2]||v[1]),x=v[4]||v[6],A=/ as /.test(v[0])&&v[1],B=A?c(A):null,G=v[5],J=c(v[3]||""),z=c(v[2]?v[1]:x),L=c(v[7]),I=v[8]?c(v[8]):null,Q={},R=[[{element:f,label:""}]],T={};w&&(a(w)(e),w.removeClass("ng-scope"),w.remove());f.empty();f.on("change",function(){e.$apply(function(){var a=L(e)||[],c;if(u)c=[],r(f.val(), -function(d){d=I?Q[d]:d;c.push("?"===d?t:""===d?null:h(B?B:z,d,a[d]))});else{var d=I?Q[f.val()]:f.val();c="?"===d?t:""===d?null:h(B?B:z,d,a[d])}g.$setViewValue(c);p()})});g.$render=p;e.$watchCollection(L,l);e.$watchCollection(function(){var a=L(e),c;if(a&&H(a)){c=Array(a.length);for(var d=0,f=a.length;df||e.$isEmpty(c)||c.length<=f}}}}},Cc=function(){return{restrict:"A",require:"?ngModel",link:function(a,c,d,e){if(e){var f=0;d.$observe("minlength",function(a){f=aa(a)||0;e.$validate()});e.$validators.minlength=function(a,c){return e.$isEmpty(c)||c.length>=f}}}}};Q.angular.bootstrap?console.log("WARNING: Tried to load angular more than once."):(Nd(),Pd(ca),A(W).ready(function(){Jd(W,uc)}))})(window,document);!window.angular.$$csp()&&window.angular.element(document).find("head").prepend(''); diff --git a/apps/static/js/jquery-2.1.1.js b/apps/static/js/jquery-2.1.1.js deleted file mode 100644 index 01c360f4f..000000000 --- a/apps/static/js/jquery-2.1.1.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery v2.1.1 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ -!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.1",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+-new Date,v=a.document,w=0,x=0,y=gb(),z=gb(),A=gb(),B=function(a,b){return a===b&&(l=!0),0},C="undefined",D=1<<31,E={}.hasOwnProperty,F=[],G=F.pop,H=F.push,I=F.push,J=F.slice,K=F.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},L="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",N="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=N.replace("w","w#"),P="\\["+M+"*("+N+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+O+"))|)"+M+"*\\]",Q=":("+N+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+P+")*)|.*)\\)|)",R=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),S=new RegExp("^"+M+"*,"+M+"*"),T=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),V=new RegExp(Q),W=new RegExp("^"+O+"$"),X={ID:new RegExp("^#("+N+")"),CLASS:new RegExp("^\\.("+N+")"),TAG:new RegExp("^("+N.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+Q),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+L+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{I.apply(F=J.call(v.childNodes),v.childNodes),F[v.childNodes.length].nodeType}catch(eb){I={apply:F.length?function(a,b){H.apply(a,J.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],!a||"string"!=typeof a)return d;if(1!==(k=b.nodeType)&&9!==k)return[];if(p&&!e){if(f=_.exec(a))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return I.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return I.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=9===k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+qb(o[l]);w=ab.test(a)&&ob(b.parentNode)||b,x=o.join(",")}if(x)try{return I.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function gb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function hb(a){return a[u]=!0,a}function ib(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function jb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function kb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||D)-(~a.sourceIndex||D);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function lb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function nb(a){return hb(function(b){return b=+b,hb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function ob(a){return a&&typeof a.getElementsByTagName!==C&&a}c=fb.support={},f=fb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fb.setDocument=function(a){var b,e=a?a.ownerDocument||a:v,g=e.defaultView;return e!==n&&9===e.nodeType&&e.documentElement?(n=e,o=e.documentElement,p=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){m()},!1):g.attachEvent&&g.attachEvent("onunload",function(){m()})),c.attributes=ib(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ib(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(e.getElementsByClassName)&&ib(function(a){return a.innerHTML="
",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=ib(function(a){return o.appendChild(a).id=u,!e.getElementsByName||!e.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==C&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c=typeof a.getAttributeNode!==C&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==C?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==C&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(e.querySelectorAll))&&(ib(function(a){a.innerHTML="",a.querySelectorAll("[msallowclip^='']").length&&q.push("[*^$]="+M+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+M+"*(?:value|"+L+")"),a.querySelectorAll(":checked").length||q.push(":checked")}),ib(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+M+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ib(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",Q)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===v&&t(v,a)?-1:b===e||b.ownerDocument===v&&t(v,b)?1:k?K.call(k,a)-K.call(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],i=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:k?K.call(k,a)-K.call(k,b):0;if(f===g)return kb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?kb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},e):n},fb.matches=function(a,b){return fb(a,null,null,b)},fb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fb(b,n,null,[a]).length>0},fb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&E.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fb.selectors={cacheLength:50,createPseudo:hb,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+M+")"+a+"("+M+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==C&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?hb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=K.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:hb(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?hb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:hb(function(a){return function(b){return fb(a,b).length>0}}),contains:hb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:hb(function(a){return W.test(a||"")||fb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:nb(function(){return[0]}),last:nb(function(a,b){return[b-1]}),eq:nb(function(a,b,c){return[0>c?c+b:c]}),even:nb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:nb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:nb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:nb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function rb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function sb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function tb(a,b,c){for(var d=0,e=b.length;e>d;d++)fb(a,b[d],c);return c}function ub(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function vb(a,b,c,d,e,f){return d&&!d[u]&&(d=vb(d)),e&&!e[u]&&(e=vb(e,f)),hb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||tb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ub(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ub(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?K.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ub(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):I.apply(g,r)})}function wb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=rb(function(a){return a===b},h,!0),l=rb(function(a){return K.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>i;i++)if(c=d.relative[a[i].type])m=[rb(sb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return vb(i>1&&sb(m),i>1&&qb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&wb(a.slice(i,e)),f>e&&wb(a=a.slice(e)),f>e&&qb(a))}m.push(c)}return sb(m)}function xb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=G.call(i));s=ub(s)}I.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&fb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?hb(f):f}return h=fb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xb(e,d)),f.selector=a}return f},i=fb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&ob(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qb(j),!a)return I.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&ob(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ib(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ib(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||jb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ib(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||jb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ib(function(a){return null==a.getAttribute("disabled")})||jb(L,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fb}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+Math.random()}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b) -},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthx",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,bb=/<([\w:]+)/,cb=/<|&#?\w+;/,db=/<(?:script|style|link)/i,eb=/checked\s*(?:[^=]|=\s*.checked.)/i,fb=/^$|\/(?:java|ecma)script/i,gb=/^true\/(.*)/,hb=/^\s*\s*$/g,ib={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ib.optgroup=ib.option,ib.tbody=ib.tfoot=ib.colgroup=ib.caption=ib.thead,ib.th=ib.td;function jb(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function kb(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function lb(a){var b=gb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function mb(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function nb(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function ob(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pb(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=ob(h),f=ob(a),d=0,e=f.length;e>d;d++)pb(f[d],g[d]);if(b)if(c)for(f=f||ob(a),g=g||ob(h),d=0,e=f.length;e>d;d++)nb(f[d],g[d]);else nb(a,h);return g=ob(h,"script"),g.length>0&&mb(g,!i&&ob(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(cb.test(e)){f=f||k.appendChild(b.createElement("div")),g=(bb.exec(e)||["",""])[1].toLowerCase(),h=ib[g]||ib._default,f.innerHTML=h[1]+e.replace(ab,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=ob(k.appendChild(e),"script"),i&&mb(f),c)){j=0;while(e=f[j++])fb.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(ob(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&mb(ob(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(ob(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!db.test(a)&&!ib[(bb.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(ab,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(ob(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(ob(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&eb.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(ob(c,"script"),kb),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,ob(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,lb),j=0;g>j;j++)h=f[j],fb.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(hb,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qb,rb={};function sb(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function tb(a){var b=l,c=rb[a];return c||(c=sb(a,b),"none"!==c&&c||(qb=(qb||n("';break;case 3:b.title=!1,b.closeBtn=!1,-1===b.icon&&0===b.icon,f.closeAll("loading");break;case 4:j||(b.content=[b.content,"body"]),b.follow=b.content[1],b.content=b.content[0]+'',b.title=!1,b.tips="object"==typeof b.tips?b.tips:[b.tips,!0],b.tipsMore||f.closeAll("tips")}a.vessel(j,function(d,e){c("body").append(d[0]),j?function(){2==b.type||4==b.type?function(){c("body").append(d[1])}():function(){i.parents("."+h[0])[0]||(i.show().addClass("layui-layer-wrap").wrap(d[1]),c("#"+h[0]+g).find("."+h[5]).before(e))}()}():c("body").append(d[1]),a.layero=c("#"+h[0]+g),b.scrollbar||h.html.css("overflow","hidden").attr("layer-full",g)}).auto(g),2==b.type&&f.ie6&&a.layero.find("iframe").attr("src",i[0]),c(document).off("keydown",e.enter).on("keydown",e.enter),a.layero.on("keydown",function(a){c(document).off("keydown",e.enter)}),4==b.type?a.tips():a.offset(),b.fix&&d.on("resize",function(){a.offset(),(/^\d+%$/.test(b.area[0])||/^\d+%$/.test(b.area[1]))&&a.auto(g),4==b.type&&a.tips()}),b.time<=0||setTimeout(function(){f.close(a.index)},b.time),a.move().callback(),h.anim[b.shift]&&a.layero.addClass(h.anim[b.shift])}},g.pt.auto=function(a){function b(a){a=g.find(a),a.height(i[1]-j-k-2*(0|parseFloat(a.css("padding"))))}var e=this,f=e.config,g=c("#"+h[0]+a);""===f.area[0]&&f.maxWidth>0&&(/MSIE 7/.test(navigator.userAgent)&&f.btn&&g.width(g.innerWidth()),g.outerWidth()>f.maxWidth&&g.width(f.maxWidth));var i=[g.innerWidth(),g.innerHeight()],j=g.find(h[1]).outerHeight()||0,k=g.find("."+h[6]).outerHeight()||0;switch(f.type){case 2:b("iframe");break;default:""===f.area[1]?f.fix&&i[1]>=d.height()&&(i[1]=d.height(),b("."+h[5])):b("."+h[5])}return e},g.pt.offset=function(){var a=this,b=a.config,c=a.layero,e=[c.outerWidth(),c.outerHeight()],f="object"==typeof b.offset;a.offsetTop=(d.height()-e[1])/2,a.offsetLeft=(d.width()-e[0])/2,f?(a.offsetTop=b.offset[0],a.offsetLeft=b.offset[1]||a.offsetLeft):"auto"!==b.offset&&(a.offsetTop=b.offset,"rb"===b.offset&&(a.offsetTop=d.height()-e[1],a.offsetLeft=d.width()-e[0])),b.fix||(a.offsetTop=/%$/.test(a.offsetTop)?d.height()*parseFloat(a.offsetTop)/100:parseFloat(a.offsetTop),a.offsetLeft=/%$/.test(a.offsetLeft)?d.width()*parseFloat(a.offsetLeft)/100:parseFloat(a.offsetLeft),a.offsetTop+=d.scrollTop(),a.offsetLeft+=d.scrollLeft()),c.css({top:a.offsetTop,left:a.offsetLeft})},g.pt.tips=function(){var a=this,b=a.config,e=a.layero,f=[e.outerWidth(),e.outerHeight()],g=c(b.follow);g[0]||(g=c("body"));var i={width:g.outerWidth(),height:g.outerHeight(),top:g.offset().top,left:g.offset().left},j=e.find(".layui-layer-TipsG"),k=b.tips[0];b.tips[1]||j.remove(),i.autoLeft=function(){i.left+f[0]-d.width()>0?(i.tipLeft=i.left+i.width-f[0],j.css({right:12,left:"auto"})):i.tipLeft=i.left},i.where=[function(){i.autoLeft(),i.tipTop=i.top-f[1]-10,j.removeClass("layui-layer-TipsB").addClass("layui-layer-TipsT").css("border-right-color",b.tips[1])},function(){i.tipLeft=i.left+i.width+10,i.tipTop=i.top,j.removeClass("layui-layer-TipsL").addClass("layui-layer-TipsR").css("border-bottom-color",b.tips[1])},function(){i.autoLeft(),i.tipTop=i.top+i.height+10,j.removeClass("layui-layer-TipsT").addClass("layui-layer-TipsB").css("border-right-color",b.tips[1])},function(){i.tipLeft=i.left-f[0]-10,i.tipTop=i.top,j.removeClass("layui-layer-TipsR").addClass("layui-layer-TipsL").css("border-bottom-color",b.tips[1])}],i.where[k-1](),1===k?i.top-(d.scrollTop()+f[1]+16)<0&&i.where[2]():2===k?d.width()-(i.left+i.width+f[0]+16)>0||i.where[3]():3===k?i.top-d.scrollTop()+i.height+f[1]+16-d.height()>0&&i.where[0]():4===k&&f[0]+16-i.left>0&&i.where[1](),e.find("."+h[5]).css({"background-color":b.tips[1],"padding-right":b.closeBtn?"30px":""}),e.css({left:i.tipLeft-(b.fix?d.scrollLeft():0),top:i.tipTop-(b.fix?d.scrollTop():0)})},g.pt.move=function(){var a=this,b=a.config,e={setY:0,moveLayer:function(){var a=e.layero,b=parseInt(a.css("margin-left")),c=parseInt(e.move.css("left"));0===b||(c-=b),"fixed"!==a.css("position")&&(c-=a.parent().offset().left,e.setY=0),a.css({left:c,top:parseInt(e.move.css("top"))-e.setY})}},f=a.layero.find(b.move);return b.move&&f.attr("move","ok"),f.css({cursor:b.move?"move":"auto"}),c(b.move).on("mousedown",function(a){if(a.preventDefault(),"ok"===c(this).attr("move")){e.ismove=!0,e.layero=c(this).parents("."+h[0]);var f=e.layero.offset().left,g=e.layero.offset().top,i=e.layero.outerWidth()-6,j=e.layero.outerHeight()-6;c("#layui-layer-moves")[0]||c("body").append('
'),e.move=c("#layui-layer-moves"),b.moveType&&e.move.css({visibility:"hidden"}),e.moveX=a.pageX-e.move.position().left,e.moveY=a.pageY-e.move.position().top,"fixed"!==e.layero.css("position")||(e.setY=d.scrollTop())}}),c(document).mousemove(function(a){if(e.ismove){var c=a.pageX-e.moveX,f=a.pageY-e.moveY;if(a.preventDefault(),!b.moveOut){e.setY=d.scrollTop();var g=d.width()-e.move.outerWidth(),h=e.setY;0>c&&(c=0),c>g&&(c=g),h>f&&(f=h),f>d.height()-e.move.outerHeight()+e.setY&&(f=d.height()-e.move.outerHeight()+e.setY)}e.move.css({left:c,top:f}),b.moveType&&e.moveLayer(),c=f=g=h=null}}).mouseup(function(){try{e.ismove&&(e.moveLayer(),e.move.remove(),b.moveEnd&&b.moveEnd()),e.ismove=!1}catch(a){e.ismove=!1}}),a},g.pt.callback=function(){function a(){var a=g.cancel&&g.cancel(b.index,d);a===!1||f.close(b.index)}var b=this,d=b.layero,g=b.config;b.openLayer(),g.success&&(2==g.type?d.find("iframe").on("load",function(){g.success(d,b.index)}):g.success(d,b.index)),f.ie6&&b.IE6(d),d.find("."+h[6]).children("a").on("click",function(){var a=c(this).index();if(0===a)g.yes?g.yes(b.index,d):g.btn1?g.btn1(b.index,d):f.close(b.index);else{var e=g["btn"+(a+1)]&&g["btn"+(a+1)](b.index,d);e===!1||f.close(b.index)}}),d.find("."+h[7]).on("click",a),g.shadeClose&&c("#layui-layer-shade"+b.index).on("click",function(){f.close(b.index)}),d.find(".layui-layer-min").on("click",function(){var a=g.min&&g.min(d);a===!1||f.min(b.index,g)}),d.find(".layui-layer-max").on("click",function(){c(this).hasClass("layui-layer-maxmin")?(f.restore(b.index),g.restore&&g.restore(d)):(f.full(b.index,g),setTimeout(function(){g.full&&g.full(d)},100))}),g.end&&(e.end[b.index]=g.end)},e.reselect=function(){c.each(c("select"),function(a,b){var d=c(this);d.parents("."+h[0])[0]||1==d.attr("layer")&&c("."+h[0]).length<1&&d.removeAttr("layer").show(),d=null})},g.pt.IE6=function(a){function b(){a.css({top:f+(e.config.fix?d.scrollTop():0)})}var e=this,f=a.offset().top;b(),d.scroll(b),c("select").each(function(a,b){var d=c(this);d.parents("."+h[0])[0]||"none"===d.css("display")||d.attr({layer:"1"}).hide(),d=null})},g.pt.openLayer=function(){var a=this;f.zIndex=a.config.zIndex,f.setTop=function(a){var b=function(){f.zIndex++,a.css("z-index",f.zIndex+1)};return f.zIndex=parseInt(a[0].style.zIndex),a.on("mousedown",b),f.zIndex}},e.record=function(a){var b=[a.width(),a.height(),a.position().top,a.position().left+parseFloat(a.css("margin-left"))];a.find(".layui-layer-max").addClass("layui-layer-maxmin"),a.attr({area:b})},e.rescollbar=function(a){h.html.attr("layer-full")==a&&(h.html[0].style.removeProperty?h.html[0].style.removeProperty("overflow"):h.html[0].style.removeAttribute("overflow"),h.html.removeAttr("layer-full"))},a.layer=f,f.getChildFrame=function(a,b){return b=b||c("."+h[4]).attr("times"),c("#"+h[0]+b).find("iframe").contents().find(a)},f.getFrameIndex=function(a){return c("#"+a).parents("."+h[4]).attr("times")},f.iframeAuto=function(a){if(a){var b=f.getChildFrame("html",a).outerHeight(),d=c("#"+h[0]+a),e=d.find(h[1]).outerHeight()||0,g=d.find("."+h[6]).outerHeight()||0;d.css({height:b+e+g}),d.find("iframe").css({height:b})}},f.iframeSrc=function(a,b){c("#"+h[0]+a).find("iframe").attr("src",b)},f.style=function(a,b){var d=c("#"+h[0]+a),f=d.attr("type"),g=d.find(h[1]).outerHeight()||0,i=d.find("."+h[6]).outerHeight()||0;(f===e.type[1]||f===e.type[2])&&(d.css(b),f===e.type[2]&&d.find("iframe").css({height:parseFloat(b.height)-g-i}))},f.min=function(a,b){var d=c("#"+h[0]+a),g=d.find(h[1]).outerHeight()||0;e.record(d),f.style(a,{width:180,height:g,overflow:"hidden"}),d.find(".layui-layer-min").hide(),"page"===d.attr("type")&&d.find(h[4]).hide(),e.rescollbar(a)},f.restore=function(a){var b=c("#"+h[0]+a),d=b.attr("area").split(",");b.attr("type");f.style(a,{width:parseFloat(d[0]),height:parseFloat(d[1]),top:parseFloat(d[2]),left:parseFloat(d[3]),overflow:"visible"}),b.find(".layui-layer-max").removeClass("layui-layer-maxmin"),b.find(".layui-layer-min").show(),"page"===b.attr("type")&&b.find(h[4]).show(),e.rescollbar(a)},f.full=function(a){var b,g=c("#"+h[0]+a);e.record(g),h.html.attr("layer-full")||h.html.css("overflow","hidden").attr("layer-full",a),clearTimeout(b),b=setTimeout(function(){var b="fixed"===g.css("position");f.style(a,{top:b?0:d.scrollTop(),left:b?0:d.scrollLeft(),width:d.width(),height:d.height()}),g.find(".layui-layer-min").hide()},100)},f.title=function(a,b){var d=c("#"+h[0]+(b||f.index)).find(h[1]);d.html(a)},f.close=function(a){var b=c("#"+h[0]+a),d=b.attr("type");if(b[0]){if(d===e.type[1]&&"object"===b.attr("conType")){b.children(":not(."+h[5]+")").remove();for(var g=0;2>g;g++)b.find(".layui-layer-wrap").unwrap().hide()}else{if(d===e.type[2])try{var i=c("#"+h[4]+a)[0];i.contentWindow.document.write(""),i.contentWindow.close(),b.find("."+h[5])[0].removeChild(i)}catch(j){}b[0].innerHTML="",b.remove()}c("#layui-layer-moves, #layui-layer-shade"+a).remove(),f.ie6&&e.reselect(),e.rescollbar(a),c(document).off("keydown",e.enter),"function"==typeof e.end[a]&&e.end[a](),delete e.end[a]}},f.closeAll=function(a){c.each(c("."+h[0]),function(){var b=c(this),d=a?b.attr("type")===a:1;d&&f.close(b.attr("times")),d=null})};var i=f.cache||{},j=function(a){return i.skin?" "+i.skin+" "+i.skin+"-"+a:""};f.prompt=function(a,b){a=a||{},"function"==typeof a&&(b=a);var d,e=2==a.formType?'":function(){return''}();return f.open(c.extend({btn:["确定","取消"],content:e,skin:"layui-layer-prompt"+j("prompt"),success:function(a){d=a.find(".layui-layer-input"),d.focus()},yes:function(c){var e=d.val();""===e?d.focus():e.length>(a.maxlength||500)?f.tips("最多输入"+(a.maxlength||500)+"个字数",d,{tips:1}):b&&b(e,c,d)}},a))},f.tab=function(a){a=a||{};var b=a.tab||{};return f.open(c.extend({type:1,skin:"layui-layer-tab"+j("tab"),title:function(){var a=b.length,c=1,d="";if(a>0)for(d=''+b[0].title+"";a>c;c++)d+=""+b[c].title+"";return d}(),content:'
    '+function(){var a=b.length,c=1,d="";if(a>0)for(d='
  • '+(b[0].content||"no content")+"
  • ";a>c;c++)d+='
  • '+(b[c].content||"no content")+"
  • ";return d}()+"
",success:function(b){var d=b.find(".layui-layer-title").children(),e=b.find(".layui-layer-tabmain").children();d.on("mousedown",function(b){b.stopPropagation?b.stopPropagation():b.cancelBubble=!0;var d=c(this),f=d.index();d.addClass("layui-layer-tabnow").siblings().removeClass("layui-layer-tabnow"),e.eq(f).show().siblings().hide(),"function"==typeof a.change&&a.change(f)})}},a))},f.photos=function(b,d,e){function g(a,b,c){var d=new Image;return d.src=a,d.complete?b(d):(d.onload=function(){d.onload=null,b(d)},void(d.onerror=function(a){d.onerror=null,c(a)}))}var h={};if(b=b||{},b.photos){var i=b.photos.constructor===Object,k=i?b.photos:{},l=k.data||[],m=k.start||0;if(h.imgIndex=(0|m)+1,b.img=b.img||"img",i){if(0===l.length)return f.msg("没有图片")}else{var n=c(b.photos),o=function(){l=[],n.find(b.img).each(function(a){var b=c(this);b.attr("layer-index",a),l.push({alt:b.attr("alt"),pid:b.attr("layer-pid"),src:b.attr("layer-src")||b.attr("src"),thumb:b.attr("src")})})};if(o(),0===l.length)return;if(d||n.on("click",b.img,function(){var a=c(this),d=a.attr("layer-index");f.photos(c.extend(b,{photos:{start:d,data:l,tab:b.tab},full:b.full}),!0),o()}),!d)return}h.imgprev=function(a){h.imgIndex--,h.imgIndex<1&&(h.imgIndex=l.length),h.tabimg(a)},h.imgnext=function(a,b){h.imgIndex++,h.imgIndex>l.length&&(h.imgIndex=1,b)||h.tabimg(a)},h.keyup=function(a){if(!h.end){var b=a.keyCode;a.preventDefault(),37===b?h.imgprev(!0):39===b?h.imgnext(!0):27===b&&f.close(h.index)}},h.tabimg=function(a){l.length<=1||(k.start=h.imgIndex-1,f.close(h.index),f.photos(b,!0,a))},h.event=function(){h.bigimg.hover(function(){h.imgsee.show()},function(){h.imgsee.hide()}),h.bigimg.find(".layui-layer-imgprev").on("click",function(a){a.preventDefault(),h.imgprev()}),h.bigimg.find(".layui-layer-imgnext").on("click",function(a){a.preventDefault(),h.imgnext()}),c(document).on("keyup",h.keyup)},h.loadi=f.load(1,{shade:"shade"in b?!1:.9,scrollbar:!1}),g(l[m].src,function(d){f.close(h.loadi),h.index=f.open(c.extend({type:1,area:function(){var e=[d.width,d.height],f=[c(a).width()-50,c(a).height()-50];return!b.full&&e[0]>f[0]&&(e[0]=f[0],e[1]=e[0]*d.height/d.width),[e[0]+"px",e[1]+"px"]}(),title:!1,shade:.9,shadeClose:!0,closeBtn:!1,move:".layui-layer-phimg img",moveType:1,scrollbar:!1,moveOut:!0,shift:5*Math.random()|0,skin:"layui-layer-photos"+j("photos"),content:'
'+(l[m].alt||
'+(l.length>1?'':"")+'
'+(l[m].alt||"")+""+h.imgIndex+"/"+l.length+"
",success:function(a,c){h.bigimg=a.find(".layui-layer-phimg"),h.imgsee=a.find(".layui-layer-imguide,.layui-layer-imgbar"),h.event(a),b.tab&&b.tab(l[m],a)},end:function(){h.end=!0,c(document).off("keyup",h.keyup)}},b))},function(){f.close(h.loadi),f.msg("当前图片地址异常
是否继续查看下一张?",{time:3e4,btn:["下一张","不看了"],yes:function(){l.length>1&&h.imgnext(!0,!0)}})})}},e.run=function(){c=jQuery,d=c(a),h.html=c("html"),f.open=function(a){var b=new g(a);return b.index}},"function"==typeof define?define(function(){return e.run(),f}):function(){e.run(),f.use("skin/layer.css")}()}(window); \ No newline at end of file diff --git a/apps/static/js/plugins/layer/skin/default/icon-ext.png b/apps/static/js/plugins/layer/skin/default/icon-ext.png deleted file mode 100644 index bbbb669bb..000000000 Binary files a/apps/static/js/plugins/layer/skin/default/icon-ext.png and /dev/null differ diff --git a/apps/static/js/plugins/layer/skin/default/icon.png b/apps/static/js/plugins/layer/skin/default/icon.png deleted file mode 100644 index 3e17da8b1..000000000 Binary files a/apps/static/js/plugins/layer/skin/default/icon.png and /dev/null differ diff --git a/apps/static/js/plugins/layer/skin/default/loading-0.gif b/apps/static/js/plugins/layer/skin/default/loading-0.gif deleted file mode 100644 index 6f3c9539a..000000000 Binary files a/apps/static/js/plugins/layer/skin/default/loading-0.gif and /dev/null differ diff --git a/apps/static/js/plugins/layer/skin/default/loading-1.gif b/apps/static/js/plugins/layer/skin/default/loading-1.gif deleted file mode 100644 index db3a483e4..000000000 Binary files a/apps/static/js/plugins/layer/skin/default/loading-1.gif and /dev/null differ diff --git a/apps/static/js/plugins/layer/skin/default/loading-2.gif b/apps/static/js/plugins/layer/skin/default/loading-2.gif deleted file mode 100644 index 5bb90fd6a..000000000 Binary files a/apps/static/js/plugins/layer/skin/default/loading-2.gif and /dev/null differ diff --git a/apps/static/js/plugins/layer/skin/layer.css b/apps/static/js/plugins/layer/skin/layer.css deleted file mode 100644 index b4c094128..000000000 --- a/apps/static/js/plugins/layer/skin/layer.css +++ /dev/null @@ -1,7 +0,0 @@ -/*! - - @Name: layer's style - @Author: 贤心 - @Blog: sentsin.com - - */.layui-layer-imgbar,.layui-layer-imgtit a,.layui-layer-tab .layui-layer-title span,.layui-layer-title{text-overflow:ellipsis;white-space:nowrap}*html{background-image:url(about:blank);background-attachment:fixed}html #layui_layer_skinlayercss{display:none;position:absolute;width:1989px}.layui-layer,.layui-layer-shade{position:fixed;_position:absolute;pointer-events:auto}.layui-layer-shade{top:0;left:0;width:100%;height:100%;_height:expression(document.body.offsetHeight+"px")}.layui-layer{-webkit-overflow-scrolling:touch;top:150px;left:0;margin:0;padding:0;background-color:#fff;-webkit-background-clip:content;box-shadow:1px 1px 50px rgba(0,0,0,.3);border-radius:2px;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.3s;animation-duration:.3s}.layui-layer-close{position:absolute}.layui-layer-content{position:relative}.layui-layer-border{border:1px solid #B2B2B2;border:1px solid rgba(0,0,0,.3);box-shadow:1px 1px 5px rgba(0,0,0,.2)}.layui-layer-moves{position:absolute;border:3px solid #666;border:3px solid rgba(0,0,0,.5);cursor:move;background-color:#fff;background-color:rgba(255,255,255,.3);filter:alpha(opacity=50)}.layui-layer-load{background:url(default/loading-0.gif) center center no-repeat #fff}.layui-layer-ico{background:url(default/icon.png) no-repeat}.layui-layer-btn a,.layui-layer-dialog .layui-layer-ico,.layui-layer-setwin a{display:inline-block;*display:inline;*zoom:1;vertical-align:top}@-webkit-keyframes bounceIn{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes bounceIn{0%{opacity:0;-webkit-transform:scale(.5);-ms-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}.layer-anim{-webkit-animation-name:bounceIn;animation-name:bounceIn}@-webkit-keyframes bounceOut{100%{opacity:0;-webkit-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1.03);transform:scale(1.03)}0%{-webkit-transform:scale(1);transform:scale(1)}}@keyframes bounceOut{100%{opacity:0;-webkit-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1.03);-ms-transform:scale(1.03);transform:scale(1.03)}0%{-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}.layer-anim-close{-webkit-animation-name:bounceOut;animation-name:bounceOut;-webkit-animation-duration:.2s;animation-duration:.2s}@-webkit-keyframes zoomInDown{0%{opacity:0;-webkit-transform:scale(.1) translateY(-2000px);transform:scale(.1) translateY(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateY(60px);transform:scale(.475) translateY(60px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes zoomInDown{0%{opacity:0;-webkit-transform:scale(.1) translateY(-2000px);-ms-transform:scale(.1) translateY(-2000px);transform:scale(.1) translateY(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateY(60px);-ms-transform:scale(.475) translateY(60px);transform:scale(.475) translateY(60px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.layer-anim-01{-webkit-animation-name:zoomInDown;animation-name:zoomInDown}@-webkit-keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);-ms-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}.layer-anim-02{-webkit-animation-name:fadeInUpBig;animation-name:fadeInUpBig}@-webkit-keyframes zoomInLeft{0%{opacity:0;-webkit-transform:scale(.1) translateX(-2000px);transform:scale(.1) translateX(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateX(48px);transform:scale(.475) translateX(48px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes zoomInLeft{0%{opacity:0;-webkit-transform:scale(.1) translateX(-2000px);-ms-transform:scale(.1) translateX(-2000px);transform:scale(.1) translateX(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateX(48px);-ms-transform:scale(.475) translateX(48px);transform:scale(.475) translateX(48px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.layer-anim-03{-webkit-animation-name:zoomInLeft;animation-name:zoomInLeft}@-webkit-keyframes rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg);transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;-webkit-transform:translateX(0) rotate(0);transform:translateX(0) rotate(0)}}@keyframes rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg);-ms-transform:translateX(-100%) rotate(-120deg);transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;-webkit-transform:translateX(0) rotate(0);-ms-transform:translateX(0) rotate(0);transform:translateX(0) rotate(0)}}.layer-anim-04{-webkit-animation-name:rollIn;animation-name:rollIn}@keyframes fadeIn{0%{opacity:0}100%{opacity:1}}.layer-anim-05{-webkit-animation-name:fadeIn;animation-name:fadeIn}@-webkit-keyframes shake{0%,100%{-webkit-transform:translateX(0);transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);transform:translateX(10px)}}@keyframes shake{0%,100%{-webkit-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);-ms-transform:translateX(-10px);transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);-ms-transform:translateX(10px);transform:translateX(10px)}}.layer-anim-06{-webkit-animation-name:shake;animation-name:shake}@-webkit-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}.layui-layer-title{padding:0 80px 0 20px;height:42px;line-height:42px;border-bottom:1px solid #eee;font-size:14px;color:#333;overflow:hidden;background-color:#F8F8F8;border-radius:2px 2px 0 0}.layui-layer-setwin{position:absolute;right:15px;*right:0;top:15px;font-size:0;line-height:initial}.layui-layer-setwin a{position:relative;width:16px;height:16px;margin-left:10px;font-size:12px;_overflow:hidden}.layui-layer-setwin .layui-layer-min cite{position:absolute;width:14px;height:2px;left:0;top:50%;margin-top:-1px;background-color:#2E2D3C;cursor:pointer;_overflow:hidden}.layui-layer-setwin .layui-layer-min:hover cite{background-color:#2D93CA}.layui-layer-setwin .layui-layer-max{background-position:-32px -40px}.layui-layer-setwin .layui-layer-max:hover{background-position:-16px -40px}.layui-layer-setwin .layui-layer-maxmin{background-position:-65px -40px}.layui-layer-setwin .layui-layer-maxmin:hover{background-position:-49px -40px}.layui-layer-setwin .layui-layer-close1{background-position:0 -40px;cursor:pointer}.layui-layer-setwin .layui-layer-close1:hover{opacity:.7}.layui-layer-setwin .layui-layer-close2{position:absolute;right:-28px;top:-28px;width:30px;height:30px;margin-left:0;background-position:-149px -31px;*right:-18px;_display:none}.layui-layer-setwin .layui-layer-close2:hover{background-position:-180px -31px}.layui-layer-btn{text-align:right;padding:0 10px 12px;pointer-events:auto}.layui-layer-btn a{height:28px;line-height:28px;margin:0 6px;padding:0 15px;border:1px solid #dedede;background-color:#f1f1f1;color:#333;border-radius:2px;font-weight:400;cursor:pointer;text-decoration:none}.layui-layer-btn a:hover{opacity:.9;text-decoration:none}.layui-layer-btn a:active{opacity:.7}.layui-layer-btn .layui-layer-btn0{border-color:#4898d5;background-color:#2e8ded;color:#fff}.layui-layer-dialog{min-width:260px}.layui-layer-dialog .layui-layer-content{position:relative;padding:20px;line-height:24px;word-break:break-all;overflow:hidden;font-size:14px;overflow-x:hidden;overflow-y:auto}.layui-layer-dialog .layui-layer-content .layui-layer-ico{position:absolute;top:16px;left:15px;_left:-40px;width:30px;height:30px}.layui-layer-ico1{background-position:-30px 0}.layui-layer-ico2{background-position:-60px 0}.layui-layer-ico3{background-position:-90px 0}.layui-layer-ico4{background-position:-120px 0}.layui-layer-ico5{background-position:-150px 0}.layui-layer-ico6{background-position:-180px 0}.layui-layer-rim{border:6px solid #8D8D8D;border:6px solid rgba(0,0,0,.3);border-radius:5px;box-shadow:none}.layui-layer-msg{min-width:180px;border:1px solid #D3D4D3;box-shadow:none}.layui-layer-hui{min-width:100px;background-color:#000;filter:alpha(opacity=60);background-color:rgba(0,0,0,.6);color:#fff;border:none}.layui-layer-hui .layui-layer-content{padding:12px 25px;text-align:center}.layui-layer-dialog .layui-layer-padding{padding:20px 20px 20px 55px;text-align:left}.layui-layer-page .layui-layer-content{position:relative;overflow:auto}.layui-layer-iframe .layui-layer-btn,.layui-layer-page .layui-layer-btn{padding-top:10px}.layui-layer-nobg{background:0 0}.layui-layer-iframe .layui-layer-content{overflow:hidden}.layui-layer-iframe iframe{display:block;width:100%}.layui-layer-loading{border-radius:100%;background:0 0;box-shadow:none;border:none}.layui-layer-loading .layui-layer-content{width:60px;height:24px;background:url(default/loading-0.gif) no-repeat}.layui-layer-loading .layui-layer-loading1{width:37px;height:37px;background:url(default/loading-1.gif) no-repeat}.layui-layer-ico16,.layui-layer-loading .layui-layer-loading2{width:32px;height:32px;background:url(default/loading-2.gif) no-repeat}.layui-layer-tips{background:0 0;box-shadow:none;border:none}.layui-layer-tips .layui-layer-content{position:relative;line-height:22px;min-width:12px;padding:5px 10px;font-size:12px;_float:left;border-radius:3px;box-shadow:1px 1px 3px rgba(0,0,0,.3);background-color:#F90;color:#fff}.layui-layer-tips .layui-layer-close{right:-2px;top:-1px}.layui-layer-tips i.layui-layer-TipsG{position:absolute;width:0;height:0;border-width:8px;border-color:transparent;border-style:dashed;*overflow:hidden}.layui-layer-tips i.layui-layer-TipsB,.layui-layer-tips i.layui-layer-TipsT{left:5px;border-right-style:solid;border-right-color:#F90}.layui-layer-tips i.layui-layer-TipsT{bottom:-8px}.layui-layer-tips i.layui-layer-TipsB{top:-8px}.layui-layer-tips i.layui-layer-TipsL,.layui-layer-tips i.layui-layer-TipsR{top:1px;border-bottom-style:solid;border-bottom-color:#F90}.layui-layer-tips i.layui-layer-TipsR{left:-8px}.layui-layer-tips i.layui-layer-TipsL{right:-8px}.layui-layer-lan[type=dialog]{min-width:280px}.layui-layer-lan .layui-layer-title{background:#4476A7;color:#fff;border:none}.layui-layer-lan .layui-layer-btn{padding:10px;text-align:right;border-top:1px solid #E9E7E7}.layui-layer-lan .layui-layer-btn a{background:#BBB5B5;border:none}.layui-layer-lan .layui-layer-btn .layui-layer-btn1{background:#C9C5C5}.layui-layer-molv .layui-layer-title{background:#009f95;color:#fff;border:none}.layui-layer-molv .layui-layer-btn a{background:#009f95}.layui-layer-molv .layui-layer-btn .layui-layer-btn1{background:#92B8B1}.layui-layer-iconext{background:url(default/icon-ext.png) no-repeat}.layui-layer-prompt .layui-layer-input{display:block;width:220px;height:30px;margin:0 auto;line-height:30px;padding:0 5px;border:1px solid #ccc;box-shadow:1px 1px 5px rgba(0,0,0,.1) inset;color:#333}.layui-layer-prompt textarea.layui-layer-input{width:300px;height:100px;line-height:20px}.layui-layer-tab{box-shadow:1px 1px 50px rgba(0,0,0,.4)}.layui-layer-tab .layui-layer-title{padding-left:0;border-bottom:1px solid #ccc;background-color:#eee;overflow:visible}.layui-layer-tab .layui-layer-title span{position:relative;float:left;min-width:80px;max-width:260px;padding:0 20px;text-align:center;cursor:default;overflow:hidden}.layui-layer-tab .layui-layer-title span.layui-layer-tabnow{height:43px;border-left:1px solid #ccc;border-right:1px solid #ccc;background-color:#fff;z-index:10}.layui-layer-tab .layui-layer-title span:first-child{border-left:none}.layui-layer-tabmain{line-height:24px;clear:both}.layui-layer-tabmain .layui-layer-tabli{display:none}.layui-layer-tabmain .layui-layer-tabli.xubox_tab_layer{display:block}.xubox_tabclose{position:absolute;right:10px;top:5px;cursor:pointer}.layui-layer-photos{-webkit-animation-duration:1s;animation-duration:1s}.layui-layer-photos .layui-layer-content{overflow:hidden;text-align:center}.layui-layer-photos .layui-layer-phimg img{position:relative;width:100%;display:inline-block;*display:inline;*zoom:1;vertical-align:top}.layui-layer-imgbar,.layui-layer-imguide{display:none}.layui-layer-imgnext,.layui-layer-imgprev{position:absolute;top:50%;width:27px;_width:44px;height:44px;margin-top:-22px;outline:0;blr:expression(this.onFocus=this.blur())}.layui-layer-imgprev{left:10px;background-position:-5px -5px;_background-position:-70px -5px}.layui-layer-imgprev:hover{background-position:-33px -5px;_background-position:-120px -5px}.layui-layer-imgnext{right:10px;_right:8px;background-position:-5px -50px;_background-position:-70px -50px}.layui-layer-imgnext:hover{background-position:-33px -50px;_background-position:-120px -50px}.layui-layer-imgbar{position:absolute;left:0;bottom:0;width:100%;height:32px;line-height:32px;background-color:rgba(0,0,0,.8);background-color:#000\9;filter:Alpha(opacity=80);color:#fff;overflow:hidden;font-size:0}.layui-layer-imgtit *{display:inline-block;*display:inline;*zoom:1;vertical-align:top;font-size:12px}.layui-layer-imgtit a{max-width:65%;overflow:hidden;color:#fff}.layui-layer-imgtit a:hover{color:#fff;text-decoration:underline}.layui-layer-imgtit em{padding-left:10px;font-style:normal} \ No newline at end of file diff --git a/apps/static/js/plugins/magnific/jquery.magnific-popup.min.js b/apps/static/js/plugins/magnific/jquery.magnific-popup.min.js deleted file mode 100644 index ad353b97e..000000000 --- a/apps/static/js/plugins/magnific/jquery.magnific-popup.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! Magnific Popup - v1.0.0 - 2015-01-03 -* http://dimsemenov.com/plugins/magnific-popup/ -* Copyright (c) 2015 Dmitry Semenov; */ -!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports?require("jquery"):window.jQuery||window.Zepto)}(function(a){var b,c,d,e,f,g,h="Close",i="BeforeClose",j="AfterClose",k="BeforeAppend",l="MarkupParse",m="Open",n="Change",o="mfp",p="."+o,q="mfp-ready",r="mfp-removing",s="mfp-prevent-close",t=function(){},u=!!window.jQuery,v=a(window),w=function(a,c){b.ev.on(o+a+p,c)},x=function(b,c,d,e){var f=document.createElement("div");return f.className="mfp-"+b,d&&(f.innerHTML=d),e?c&&c.appendChild(f):(f=a(f),c&&f.appendTo(c)),f},y=function(c,d){b.ev.triggerHandler(o+c,d),b.st.callbacks&&(c=c.charAt(0).toLowerCase()+c.slice(1),b.st.callbacks[c]&&b.st.callbacks[c].apply(b,a.isArray(d)?d:[d]))},z=function(c){return c===g&&b.currTemplate.closeBtn||(b.currTemplate.closeBtn=a(b.st.closeMarkup.replace("%title%",b.st.tClose)),g=c),b.currTemplate.closeBtn},A=function(){a.magnificPopup.instance||(b=new t,b.init(),a.magnificPopup.instance=b)},B=function(){var a=document.createElement("p").style,b=["ms","O","Moz","Webkit"];if(void 0!==a.transition)return!0;for(;b.length;)if(b.pop()+"Transition"in a)return!0;return!1};t.prototype={constructor:t,init:function(){var c=navigator.appVersion;b.isIE7=-1!==c.indexOf("MSIE 7."),b.isIE8=-1!==c.indexOf("MSIE 8."),b.isLowIE=b.isIE7||b.isIE8,b.isAndroid=/android/gi.test(c),b.isIOS=/iphone|ipad|ipod/gi.test(c),b.supportsTransition=B(),b.probablyMobile=b.isAndroid||b.isIOS||/(Opera Mini)|Kindle|webOS|BlackBerry|(Opera Mobi)|(Windows Phone)|IEMobile/i.test(navigator.userAgent),d=a(document),b.popupsCache={}},open:function(c){var e;if(c.isObj===!1){b.items=c.items.toArray(),b.index=0;var g,h=c.items;for(e=0;e(a||v.height())},_setFocus:function(){(b.st.focus?b.content.find(b.st.focus).eq(0):b.wrap).focus()},_onFocusIn:function(c){return c.target===b.wrap[0]||a.contains(b.wrap[0],c.target)?void 0:(b._setFocus(),!1)},_parseMarkup:function(b,c,d){var e;d.data&&(c=a.extend(d.data,c)),y(l,[b,c,d]),a.each(c,function(a,c){if(void 0===c||c===!1)return!0;if(e=a.split("_"),e.length>1){var d=b.find(p+"-"+e[0]);if(d.length>0){var f=e[1];"replaceWith"===f?d[0]!==c[0]&&d.replaceWith(c):"img"===f?d.is("img")?d.attr("src",c):d.replaceWith(''):d.attr(e[1],c)}}else b.find(p+"-"+a).html(c)})},_getScrollbarSize:function(){if(void 0===b.scrollbarSize){var a=document.createElement("div");a.style.cssText="width: 99px; height: 99px; overflow: scroll; position: absolute; top: -9999px;",document.body.appendChild(a),b.scrollbarSize=a.offsetWidth-a.clientWidth,document.body.removeChild(a)}return b.scrollbarSize}},a.magnificPopup={instance:null,proto:t.prototype,modules:[],open:function(b,c){return A(),b=b?a.extend(!0,{},b):{},b.isObj=!0,b.index=c||0,this.instance.open(b)},close:function(){return a.magnificPopup.instance&&a.magnificPopup.instance.close()},registerModule:function(b,c){c.options&&(a.magnificPopup.defaults[b]=c.options),a.extend(this.proto,c.proto),this.modules.push(b)},defaults:{disableOn:0,key:null,midClick:!1,mainClass:"",preloader:!0,focus:"",closeOnContentClick:!1,closeOnBgClick:!0,closeBtnInside:!0,showCloseBtn:!0,enableEscapeKey:!0,modal:!1,alignTop:!1,removalDelay:0,prependTo:null,fixedContentPos:"auto",fixedBgPos:"auto",overflowY:"auto",closeMarkup:'',tClose:"Close (Esc)",tLoading:"Loading..."}},a.fn.magnificPopup=function(c){A();var d=a(this);if("string"==typeof c)if("open"===c){var e,f=u?d.data("magnificPopup"):d[0].magnificPopup,g=parseInt(arguments[1],10)||0;f.items?e=f.items[g]:(e=d,f.delegate&&(e=e.find(f.delegate)),e=e.eq(g)),b._openClick({mfpEl:e},d,f)}else b.isOpen&&b[c].apply(b,Array.prototype.slice.call(arguments,1));else c=a.extend(!0,{},c),u?d.data("magnificPopup",c):d[0].magnificPopup=c,b.addGroup(d,c);return d};var C,D,E,F="inline",G=function(){E&&(D.after(E.addClass(C)).detach(),E=null)};a.magnificPopup.registerModule(F,{options:{hiddenClass:"hide",markup:"",tNotFound:"Content not found"},proto:{initInline:function(){b.types.push(F),w(h+"."+F,function(){G()})},getInline:function(c,d){if(G(),c.src){var e=b.st.inline,f=a(c.src);if(f.length){var g=f[0].parentNode;g&&g.tagName&&(D||(C=e.hiddenClass,D=x(C),C="mfp-"+C),E=f.after(D).detach().removeClass(C)),b.updateStatus("ready")}else b.updateStatus("error",e.tNotFound),f=a("
");return c.inlineElement=f,f}return b.updateStatus("ready"),b._parseMarkup(d,{},c),d}}});var H,I="ajax",J=function(){H&&a(document.body).removeClass(H)},K=function(){J(),b.req&&b.req.abort()};a.magnificPopup.registerModule(I,{options:{settings:null,cursor:"mfp-ajax-cur",tError:'The content could not be loaded.'},proto:{initAjax:function(){b.types.push(I),H=b.st.ajax.cursor,w(h+"."+I,K),w("BeforeChange."+I,K)},getAjax:function(c){H&&a(document.body).addClass(H),b.updateStatus("loading");var d=a.extend({url:c.src,success:function(d,e,f){var g={data:d,xhr:f};y("ParseAjax",g),b.appendContent(a(g.data),I),c.finished=!0,J(),b._setFocus(),setTimeout(function(){b.wrap.addClass(q)},16),b.updateStatus("ready"),y("AjaxContentAdded")},error:function(){J(),c.finished=c.loadError=!0,b.updateStatus("error",b.st.ajax.tError.replace("%url%",c.src))}},b.st.ajax.settings);return b.req=a.ajax(d),""}}});var L,M=function(c){if(c.data&&void 0!==c.data.title)return c.data.title;var d=b.st.image.titleSrc;if(d){if(a.isFunction(d))return d.call(b,c);if(c.el)return c.el.attr(d)||""}return""};a.magnificPopup.registerModule("image",{options:{markup:'
',cursor:"mfp-zoom-out-cur",titleSrc:"title",verticalFit:!0,tError:'The image could not be loaded.'},proto:{initImage:function(){var c=b.st.image,d=".image";b.types.push("image"),w(m+d,function(){"image"===b.currItem.type&&c.cursor&&a(document.body).addClass(c.cursor)}),w(h+d,function(){c.cursor&&a(document.body).removeClass(c.cursor),v.off("resize"+p)}),w("Resize"+d,b.resizeImage),b.isLowIE&&w("AfterChange",b.resizeImage)},resizeImage:function(){var a=b.currItem;if(a&&a.img&&b.st.image.verticalFit){var c=0;b.isLowIE&&(c=parseInt(a.img.css("padding-top"),10)+parseInt(a.img.css("padding-bottom"),10)),a.img.css("max-height",b.wH-c)}},_onImageHasSize:function(a){a.img&&(a.hasSize=!0,L&&clearInterval(L),a.isCheckingImgSize=!1,y("ImageHasSize",a),a.imgHidden&&(b.content&&b.content.removeClass("mfp-loading"),a.imgHidden=!1))},findImageSize:function(a){var c=0,d=a.img[0],e=function(f){L&&clearInterval(L),L=setInterval(function(){return d.naturalWidth>0?void b._onImageHasSize(a):(c>200&&clearInterval(L),c++,void(3===c?e(10):40===c?e(50):100===c&&e(500)))},f)};e(1)},getImage:function(c,d){var e=0,f=function(){c&&(c.img[0].complete?(c.img.off(".mfploader"),c===b.currItem&&(b._onImageHasSize(c),b.updateStatus("ready")),c.hasSize=!0,c.loaded=!0,y("ImageLoadComplete")):(e++,200>e?setTimeout(f,100):g()))},g=function(){c&&(c.img.off(".mfploader"),c===b.currItem&&(b._onImageHasSize(c),b.updateStatus("error",h.tError.replace("%url%",c.src))),c.hasSize=!0,c.loaded=!0,c.loadError=!0)},h=b.st.image,i=d.find(".mfp-img");if(i.length){var j=document.createElement("img");j.className="mfp-img",c.el&&c.el.find("img").length&&(j.alt=c.el.find("img").attr("alt")),c.img=a(j).on("load.mfploader",f).on("error.mfploader",g),j.src=c.src,i.is("img")&&(c.img=c.img.clone()),j=c.img[0],j.naturalWidth>0?c.hasSize=!0:j.width||(c.hasSize=!1)}return b._parseMarkup(d,{title:M(c),img_replaceWith:c.img},c),b.resizeImage(),c.hasSize?(L&&clearInterval(L),c.loadError?(d.addClass("mfp-loading"),b.updateStatus("error",h.tError.replace("%url%",c.src))):(d.removeClass("mfp-loading"),b.updateStatus("ready")),d):(b.updateStatus("loading"),c.loading=!0,c.hasSize||(c.imgHidden=!0,d.addClass("mfp-loading"),b.findImageSize(c)),d)}}});var N,O=function(){return void 0===N&&(N=void 0!==document.createElement("p").style.MozTransform),N};a.magnificPopup.registerModule("zoom",{options:{enabled:!1,easing:"ease-in-out",duration:300,opener:function(a){return a.is("img")?a:a.find("img")}},proto:{initZoom:function(){var a,c=b.st.zoom,d=".zoom";if(c.enabled&&b.supportsTransition){var e,f,g=c.duration,j=function(a){var b=a.clone().removeAttr("style").removeAttr("class").addClass("mfp-animated-image"),d="all "+c.duration/1e3+"s "+c.easing,e={position:"fixed",zIndex:9999,left:0,top:0,"-webkit-backface-visibility":"hidden"},f="transition";return e["-webkit-"+f]=e["-moz-"+f]=e["-o-"+f]=e[f]=d,b.css(e),b},k=function(){b.content.css("visibility","visible")};w("BuildControls"+d,function(){if(b._allowZoom()){if(clearTimeout(e),b.content.css("visibility","hidden"),a=b._getItemToZoom(),!a)return void k();f=j(a),f.css(b._getOffset()),b.wrap.append(f),e=setTimeout(function(){f.css(b._getOffset(!0)),e=setTimeout(function(){k(),setTimeout(function(){f.remove(),a=f=null,y("ZoomAnimationEnded")},16)},g)},16)}}),w(i+d,function(){if(b._allowZoom()){if(clearTimeout(e),b.st.removalDelay=g,!a){if(a=b._getItemToZoom(),!a)return;f=j(a)}f.css(b._getOffset(!0)),b.wrap.append(f),b.content.css("visibility","hidden"),setTimeout(function(){f.css(b._getOffset())},16)}}),w(h+d,function(){b._allowZoom()&&(k(),f&&f.remove(),a=null)})}},_allowZoom:function(){return"image"===b.currItem.type},_getItemToZoom:function(){return b.currItem.hasSize?b.currItem.img:!1},_getOffset:function(c){var d;d=c?b.currItem.img:b.st.zoom.opener(b.currItem.el||b.currItem);var e=d.offset(),f=parseInt(d.css("padding-top"),10),g=parseInt(d.css("padding-bottom"),10);e.top-=a(window).scrollTop()-f;var h={width:d.width(),height:(u?d.innerHeight():d[0].offsetHeight)-g-f};return O()?h["-moz-transform"]=h.transform="translate("+e.left+"px,"+e.top+"px)":(h.left=e.left,h.top=e.top),h}}});var P="iframe",Q="//about:blank",R=function(a){if(b.currTemplate[P]){var c=b.currTemplate[P].find("iframe");c.length&&(a||(c[0].src=Q),b.isIE8&&c.css("display",a?"block":"none"))}};a.magnificPopup.registerModule(P,{options:{markup:'
',srcAction:"iframe_src",patterns:{youtube:{index:"youtube.com",id:"v=",src:"//www.youtube.com/embed/%id%?autoplay=1"},vimeo:{index:"vimeo.com/",id:"/",src:"//player.vimeo.com/video/%id%?autoplay=1"},gmaps:{index:"//maps.google.",src:"%id%&output=embed"}}},proto:{initIframe:function(){b.types.push(P),w("BeforeChange",function(a,b,c){b!==c&&(b===P?R():c===P&&R(!0))}),w(h+"."+P,function(){R()})},getIframe:function(c,d){var e=c.src,f=b.st.iframe;a.each(f.patterns,function(){return e.indexOf(this.index)>-1?(this.id&&(e="string"==typeof this.id?e.substr(e.lastIndexOf(this.id)+this.id.length,e.length):this.id.call(this,e)),e=this.src.replace("%id%",e),!1):void 0});var g={};return f.srcAction&&(g[f.srcAction]=e),b._parseMarkup(d,g,c),b.updateStatus("ready"),d}}});var S=function(a){var c=b.items.length;return a>c-1?a-c:0>a?c+a:a},T=function(a,b,c){return a.replace(/%curr%/gi,b+1).replace(/%total%/gi,c)};a.magnificPopup.registerModule("gallery",{options:{enabled:!1,arrowMarkup:'',preload:[0,2],navigateByImgClick:!0,arrows:!0,tPrev:"Previous (Left arrow key)",tNext:"Next (Right arrow key)",tCounter:"%curr% of %total%"},proto:{initGallery:function(){var c=b.st.gallery,e=".mfp-gallery",g=Boolean(a.fn.mfpFastClick);return b.direction=!0,c&&c.enabled?(f+=" mfp-gallery",w(m+e,function(){c.navigateByImgClick&&b.wrap.on("click"+e,".mfp-img",function(){return b.items.length>1?(b.next(),!1):void 0}),d.on("keydown"+e,function(a){37===a.keyCode?b.prev():39===a.keyCode&&b.next()})}),w("UpdateStatus"+e,function(a,c){c.text&&(c.text=T(c.text,b.currItem.index,b.items.length))}),w(l+e,function(a,d,e,f){var g=b.items.length;e.counter=g>1?T(c.tCounter,f.index,g):""}),w("BuildControls"+e,function(){if(b.items.length>1&&c.arrows&&!b.arrowLeft){var d=c.arrowMarkup,e=b.arrowLeft=a(d.replace(/%title%/gi,c.tPrev).replace(/%dir%/gi,"left")).addClass(s),f=b.arrowRight=a(d.replace(/%title%/gi,c.tNext).replace(/%dir%/gi,"right")).addClass(s),h=g?"mfpFastClick":"click";e[h](function(){b.prev()}),f[h](function(){b.next()}),b.isIE7&&(x("b",e[0],!1,!0),x("a",e[0],!1,!0),x("b",f[0],!1,!0),x("a",f[0],!1,!0)),b.container.append(e.add(f))}}),w(n+e,function(){b._preloadTimeout&&clearTimeout(b._preloadTimeout),b._preloadTimeout=setTimeout(function(){b.preloadNearbyImages(),b._preloadTimeout=null},16)}),void w(h+e,function(){d.off(e),b.wrap.off("click"+e),b.arrowLeft&&g&&b.arrowLeft.add(b.arrowRight).destroyMfpFastClick(),b.arrowRight=b.arrowLeft=null})):!1},next:function(){b.direction=!0,b.index=S(b.index+1),b.updateItemHTML()},prev:function(){b.direction=!1,b.index=S(b.index-1),b.updateItemHTML()},goTo:function(a){b.direction=a>=b.index,b.index=a,b.updateItemHTML()},preloadNearbyImages:function(){var a,c=b.st.gallery.preload,d=Math.min(c[0],b.items.length),e=Math.min(c[1],b.items.length);for(a=1;a<=(b.direction?e:d);a++)b._preloadItem(b.index+a);for(a=1;a<=(b.direction?d:e);a++)b._preloadItem(b.index-a)},_preloadItem:function(c){if(c=S(c),!b.items[c].preloaded){var d=b.items[c];d.parsed||(d=b.parseEl(c)),y("LazyLoad",d),"image"===d.type&&(d.img=a('').on("load.mfploader",function(){d.hasSize=!0}).on("error.mfploader",function(){d.hasSize=!0,d.loadError=!0,y("LazyLoadError",d)}).attr("src",d.src)),d.preloaded=!0}}}});var U="retina";a.magnificPopup.registerModule(U,{options:{replaceSrc:function(a){return a.src.replace(/\.\w+$/,function(a){return"@2x"+a})},ratio:1},proto:{initRetina:function(){if(window.devicePixelRatio>1){var a=b.st.retina,c=a.ratio;c=isNaN(c)?c():c,c>1&&(w("ImageHasSize."+U,function(a,b){b.img.css({"max-width":b.img[0].naturalWidth/c,width:"100%"})}),w("ElementParse."+U,function(b,d){d.src=a.replaceSrc(d,c)}))}}}}),function(){var b=1e3,c="ontouchstart"in window,d=function(){v.off("touchmove"+f+" touchend"+f)},e="mfpFastClick",f="."+e;a.fn.mfpFastClick=function(e){return a(this).each(function(){var g,h=a(this);if(c){var i,j,k,l,m,n;h.on("touchstart"+f,function(a){l=!1,n=1,m=a.originalEvent?a.originalEvent.touches[0]:a.touches[0],j=m.clientX,k=m.clientY,v.on("touchmove"+f,function(a){m=a.originalEvent?a.originalEvent.touches:a.touches,n=m.length,m=m[0],(Math.abs(m.clientX-j)>10||Math.abs(m.clientY-k)>10)&&(l=!0,d())}).on("touchend"+f,function(a){d(),l||n>1||(g=!0,a.preventDefault(),clearTimeout(i),i=setTimeout(function(){g=!1},b),e())})})}h.on("click"+f,function(){g||e()})})},a.fn.destroyMfpFastClick=function(){a(this).off("touchstart"+f+" click"+f),c&&v.off("touchmove"+f+" touchend"+f)}}(),A()}); \ No newline at end of file diff --git a/apps/static/js/plugins/moment/moment.min.js b/apps/static/js/plugins/moment/moment.min.js index 8e6866af0..5787a4085 100644 --- a/apps/static/js/plugins/moment/moment.min.js +++ b/apps/static/js/plugins/moment/moment.min.js @@ -1,7 +1 @@ -//! moment.js -//! version : 2.10.6 -//! authors : Tim Wood, Iskren Chernev, Moment.js contributors -//! license : MIT -//! momentjs.com -!function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.moment=b()}(this,function(){"use strict";function a(){return Hc.apply(null,arguments)}function b(a){Hc=a}function c(a){return"[object Array]"===Object.prototype.toString.call(a)}function d(a){return a instanceof Date||"[object Date]"===Object.prototype.toString.call(a)}function e(a,b){var c,d=[];for(c=0;c0)for(c in Jc)d=Jc[c],e=b[d],"undefined"!=typeof e&&(a[d]=e);return a}function n(b){m(this,b),this._d=new Date(null!=b._d?b._d.getTime():NaN),Kc===!1&&(Kc=!0,a.updateOffset(this),Kc=!1)}function o(a){return a instanceof n||null!=a&&null!=a._isAMomentObject}function p(a){return 0>a?Math.ceil(a):Math.floor(a)}function q(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=p(b)),c}function r(a,b,c){var d,e=Math.min(a.length,b.length),f=Math.abs(a.length-b.length),g=0;for(d=0;e>d;d++)(c&&a[d]!==b[d]||!c&&q(a[d])!==q(b[d]))&&g++;return g+f}function s(){}function t(a){return a?a.toLowerCase().replace("_","-"):a}function u(a){for(var b,c,d,e,f=0;f0;){if(d=v(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&r(e,c,!0)>=b-1)break;b--}f++}return null}function v(a){var b=null;if(!Lc[a]&&"undefined"!=typeof module&&module&&module.exports)try{b=Ic._abbr,require("./locale/"+a),w(b)}catch(c){}return Lc[a]}function w(a,b){var c;return a&&(c="undefined"==typeof b?y(a):x(a,b),c&&(Ic=c)),Ic._abbr}function x(a,b){return null!==b?(b.abbr=a,Lc[a]=Lc[a]||new s,Lc[a].set(b),w(a),Lc[a]):(delete Lc[a],null)}function y(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return Ic;if(!c(a)){if(b=v(a))return b;a=[a]}return u(a)}function z(a,b){var c=a.toLowerCase();Mc[c]=Mc[c+"s"]=Mc[b]=a}function A(a){return"string"==typeof a?Mc[a]||Mc[a.toLowerCase()]:void 0}function B(a){var b,c,d={};for(c in a)f(a,c)&&(b=A(c),b&&(d[b]=a[c]));return d}function C(b,c){return function(d){return null!=d?(E(this,b,d),a.updateOffset(this,c),this):D(this,b)}}function D(a,b){return a._d["get"+(a._isUTC?"UTC":"")+b]()}function E(a,b,c){return a._d["set"+(a._isUTC?"UTC":"")+b](c)}function F(a,b){var c;if("object"==typeof a)for(c in a)this.set(c,a[c]);else if(a=A(a),"function"==typeof this[a])return this[a](b);return this}function G(a,b,c){var d=""+Math.abs(a),e=b-d.length,f=a>=0;return(f?c?"+":"":"-")+Math.pow(10,Math.max(0,e)).toString().substr(1)+d}function H(a,b,c,d){var e=d;"string"==typeof d&&(e=function(){return this[d]()}),a&&(Qc[a]=e),b&&(Qc[b[0]]=function(){return G(e.apply(this,arguments),b[1],b[2])}),c&&(Qc[c]=function(){return this.localeData().ordinal(e.apply(this,arguments),a)})}function I(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function J(a){var b,c,d=a.match(Nc);for(b=0,c=d.length;c>b;b++)Qc[d[b]]?d[b]=Qc[d[b]]:d[b]=I(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function K(a,b){return a.isValid()?(b=L(b,a.localeData()),Pc[b]=Pc[b]||J(b),Pc[b](a)):a.localeData().invalidDate()}function L(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(Oc.lastIndex=0;d>=0&&Oc.test(a);)a=a.replace(Oc,c),Oc.lastIndex=0,d-=1;return a}function M(a){return"function"==typeof a&&"[object Function]"===Object.prototype.toString.call(a)}function N(a,b,c){dd[a]=M(b)?b:function(a){return a&&c?c:b}}function O(a,b){return f(dd,a)?dd[a](b._strict,b._locale):new RegExp(P(a))}function P(a){return a.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e}).replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function Q(a,b){var c,d=b;for("string"==typeof a&&(a=[a]),"number"==typeof b&&(d=function(a,c){c[b]=q(a)}),c=0;cd;d++){if(e=h([2e3,d]),c&&!this._longMonthsParse[d]&&(this._longMonthsParse[d]=new RegExp("^"+this.months(e,"").replace(".","")+"$","i"),this._shortMonthsParse[d]=new RegExp("^"+this.monthsShort(e,"").replace(".","")+"$","i")),c||this._monthsParse[d]||(f="^"+this.months(e,"")+"|^"+this.monthsShort(e,""),this._monthsParse[d]=new RegExp(f.replace(".",""),"i")),c&&"MMMM"===b&&this._longMonthsParse[d].test(a))return d;if(c&&"MMM"===b&&this._shortMonthsParse[d].test(a))return d;if(!c&&this._monthsParse[d].test(a))return d}}function X(a,b){var c;return"string"==typeof b&&(b=a.localeData().monthsParse(b),"number"!=typeof b)?a:(c=Math.min(a.date(),T(a.year(),b)),a._d["set"+(a._isUTC?"UTC":"")+"Month"](b,c),a)}function Y(b){return null!=b?(X(this,b),a.updateOffset(this,!0),this):D(this,"Month")}function Z(){return T(this.year(),this.month())}function $(a){var b,c=a._a;return c&&-2===j(a).overflow&&(b=c[gd]<0||c[gd]>11?gd:c[hd]<1||c[hd]>T(c[fd],c[gd])?hd:c[id]<0||c[id]>24||24===c[id]&&(0!==c[jd]||0!==c[kd]||0!==c[ld])?id:c[jd]<0||c[jd]>59?jd:c[kd]<0||c[kd]>59?kd:c[ld]<0||c[ld]>999?ld:-1,j(a)._overflowDayOfYear&&(fd>b||b>hd)&&(b=hd),j(a).overflow=b),a}function _(b){a.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+b)}function aa(a,b){var c=!0;return g(function(){return c&&(_(a+"\n"+(new Error).stack),c=!1),b.apply(this,arguments)},b)}function ba(a,b){od[a]||(_(b),od[a]=!0)}function ca(a){var b,c,d=a._i,e=pd.exec(d);if(e){for(j(a).iso=!0,b=0,c=qd.length;c>b;b++)if(qd[b][1].exec(d)){a._f=qd[b][0];break}for(b=0,c=rd.length;c>b;b++)if(rd[b][1].exec(d)){a._f+=(e[6]||" ")+rd[b][0];break}d.match(ad)&&(a._f+="Z"),va(a)}else a._isValid=!1}function da(b){var c=sd.exec(b._i);return null!==c?void(b._d=new Date(+c[1])):(ca(b),void(b._isValid===!1&&(delete b._isValid,a.createFromInputFallback(b))))}function ea(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 1970>a&&h.setFullYear(a),h}function fa(a){var b=new Date(Date.UTC.apply(null,arguments));return 1970>a&&b.setUTCFullYear(a),b}function ga(a){return ha(a)?366:365}function ha(a){return a%4===0&&a%100!==0||a%400===0}function ia(){return ha(this.year())}function ja(a,b,c){var d,e=c-b,f=c-a.day();return f>e&&(f-=7),e-7>f&&(f+=7),d=Da(a).add(f,"d"),{week:Math.ceil(d.dayOfYear()/7),year:d.year()}}function ka(a){return ja(a,this._week.dow,this._week.doy).week}function la(){return this._week.dow}function ma(){return this._week.doy}function na(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")}function oa(a){var b=ja(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")}function pa(a,b,c,d,e){var f,g=6+e-d,h=fa(a,0,1+g),i=h.getUTCDay();return e>i&&(i+=7),c=null!=c?1*c:e,f=1+g+7*(b-1)-i+c,{year:f>0?a:a-1,dayOfYear:f>0?f:ga(a-1)+f}}function qa(a){var b=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")}function ra(a,b,c){return null!=a?a:null!=b?b:c}function sa(a){var b=new Date;return a._useUTC?[b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDate()]:[b.getFullYear(),b.getMonth(),b.getDate()]}function ta(a){var b,c,d,e,f=[];if(!a._d){for(d=sa(a),a._w&&null==a._a[hd]&&null==a._a[gd]&&ua(a),a._dayOfYear&&(e=ra(a._a[fd],d[fd]),a._dayOfYear>ga(e)&&(j(a)._overflowDayOfYear=!0),c=fa(e,0,a._dayOfYear),a._a[gd]=c.getUTCMonth(),a._a[hd]=c.getUTCDate()),b=0;3>b&&null==a._a[b];++b)a._a[b]=f[b]=d[b];for(;7>b;b++)a._a[b]=f[b]=null==a._a[b]?2===b?1:0:a._a[b];24===a._a[id]&&0===a._a[jd]&&0===a._a[kd]&&0===a._a[ld]&&(a._nextDay=!0,a._a[id]=0),a._d=(a._useUTC?fa:ea).apply(null,f),null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[id]=24)}}function ua(a){var b,c,d,e,f,g,h;b=a._w,null!=b.GG||null!=b.W||null!=b.E?(f=1,g=4,c=ra(b.GG,a._a[fd],ja(Da(),1,4).year),d=ra(b.W,1),e=ra(b.E,1)):(f=a._locale._week.dow,g=a._locale._week.doy,c=ra(b.gg,a._a[fd],ja(Da(),f,g).year),d=ra(b.w,1),null!=b.d?(e=b.d,f>e&&++d):e=null!=b.e?b.e+f:f),h=pa(c,d,e,g,f),a._a[fd]=h.year,a._dayOfYear=h.dayOfYear}function va(b){if(b._f===a.ISO_8601)return void ca(b);b._a=[],j(b).empty=!0;var c,d,e,f,g,h=""+b._i,i=h.length,k=0;for(e=L(b._f,b._locale).match(Nc)||[],c=0;c0&&j(b).unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),k+=d.length),Qc[f]?(d?j(b).empty=!1:j(b).unusedTokens.push(f),S(f,d,b)):b._strict&&!d&&j(b).unusedTokens.push(f);j(b).charsLeftOver=i-k,h.length>0&&j(b).unusedInput.push(h),j(b).bigHour===!0&&b._a[id]<=12&&b._a[id]>0&&(j(b).bigHour=void 0),b._a[id]=wa(b._locale,b._a[id],b._meridiem),ta(b),$(b)}function wa(a,b,c){var d;return null==c?b:null!=a.meridiemHour?a.meridiemHour(b,c):null!=a.isPM?(d=a.isPM(c),d&&12>b&&(b+=12),d||12!==b||(b=0),b):b}function xa(a){var b,c,d,e,f;if(0===a._f.length)return j(a).invalidFormat=!0,void(a._d=new Date(NaN));for(e=0;ef)&&(d=f,c=b));g(a,c||b)}function ya(a){if(!a._d){var b=B(a._i);a._a=[b.year,b.month,b.day||b.date,b.hour,b.minute,b.second,b.millisecond],ta(a)}}function za(a){var b=new n($(Aa(a)));return b._nextDay&&(b.add(1,"d"),b._nextDay=void 0),b}function Aa(a){var b=a._i,e=a._f;return a._locale=a._locale||y(a._l),null===b||void 0===e&&""===b?l({nullInput:!0}):("string"==typeof b&&(a._i=b=a._locale.preparse(b)),o(b)?new n($(b)):(c(e)?xa(a):e?va(a):d(b)?a._d=b:Ba(a),a))}function Ba(b){var f=b._i;void 0===f?b._d=new Date:d(f)?b._d=new Date(+f):"string"==typeof f?da(b):c(f)?(b._a=e(f.slice(0),function(a){return parseInt(a,10)}),ta(b)):"object"==typeof f?ya(b):"number"==typeof f?b._d=new Date(f):a.createFromInputFallback(b)}function Ca(a,b,c,d,e){var f={};return"boolean"==typeof c&&(d=c,c=void 0),f._isAMomentObject=!0,f._useUTC=f._isUTC=e,f._l=c,f._i=a,f._f=b,f._strict=d,za(f)}function Da(a,b,c,d){return Ca(a,b,c,d,!1)}function Ea(a,b){var d,e;if(1===b.length&&c(b[0])&&(b=b[0]),!b.length)return Da();for(d=b[0],e=1;ea&&(a=-a,c="-"),c+G(~~(a/60),2)+b+G(~~a%60,2)})}function Ka(a){var b=(a||"").match(ad)||[],c=b[b.length-1]||[],d=(c+"").match(xd)||["-",0,0],e=+(60*d[1])+q(d[2]);return"+"===d[0]?e:-e}function La(b,c){var e,f;return c._isUTC?(e=c.clone(),f=(o(b)||d(b)?+b:+Da(b))-+e,e._d.setTime(+e._d+f),a.updateOffset(e,!1),e):Da(b).local()}function Ma(a){return 15*-Math.round(a._d.getTimezoneOffset()/15)}function Na(b,c){var d,e=this._offset||0;return null!=b?("string"==typeof b&&(b=Ka(b)),Math.abs(b)<16&&(b=60*b),!this._isUTC&&c&&(d=Ma(this)),this._offset=b,this._isUTC=!0,null!=d&&this.add(d,"m"),e!==b&&(!c||this._changeInProgress?bb(this,Ya(b-e,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,a.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?e:Ma(this)}function Oa(a,b){return null!=a?("string"!=typeof a&&(a=-a),this.utcOffset(a,b),this):-this.utcOffset()}function Pa(a){return this.utcOffset(0,a)}function Qa(a){return this._isUTC&&(this.utcOffset(0,a),this._isUTC=!1,a&&this.subtract(Ma(this),"m")),this}function Ra(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(Ka(this._i)),this}function Sa(a){return a=a?Da(a).utcOffset():0,(this.utcOffset()-a)%60===0}function Ta(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Ua(){if("undefined"!=typeof this._isDSTShifted)return this._isDSTShifted;var a={};if(m(a,this),a=Aa(a),a._a){var b=a._isUTC?h(a._a):Da(a._a);this._isDSTShifted=this.isValid()&&r(a._a,b.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function Va(){return!this._isUTC}function Wa(){return this._isUTC}function Xa(){return this._isUTC&&0===this._offset}function Ya(a,b){var c,d,e,g=a,h=null;return Ia(a)?g={ms:a._milliseconds,d:a._days,M:a._months}:"number"==typeof a?(g={},b?g[b]=a:g.milliseconds=a):(h=yd.exec(a))?(c="-"===h[1]?-1:1,g={y:0,d:q(h[hd])*c,h:q(h[id])*c,m:q(h[jd])*c,s:q(h[kd])*c,ms:q(h[ld])*c}):(h=zd.exec(a))?(c="-"===h[1]?-1:1,g={y:Za(h[2],c),M:Za(h[3],c),d:Za(h[4],c),h:Za(h[5],c),m:Za(h[6],c),s:Za(h[7],c),w:Za(h[8],c)}):null==g?g={}:"object"==typeof g&&("from"in g||"to"in g)&&(e=_a(Da(g.from),Da(g.to)),g={},g.ms=e.milliseconds,g.M=e.months),d=new Ha(g),Ia(a)&&f(a,"_locale")&&(d._locale=a._locale),d}function Za(a,b){var c=a&&parseFloat(a.replace(",","."));return(isNaN(c)?0:c)*b}function $a(a,b){var c={milliseconds:0,months:0};return c.months=b.month()-a.month()+12*(b.year()-a.year()),a.clone().add(c.months,"M").isAfter(b)&&--c.months,c.milliseconds=+b-+a.clone().add(c.months,"M"),c}function _a(a,b){var c;return b=La(b,a),a.isBefore(b)?c=$a(a,b):(c=$a(b,a),c.milliseconds=-c.milliseconds,c.months=-c.months),c}function ab(a,b){return function(c,d){var e,f;return null===d||isNaN(+d)||(ba(b,"moment()."+b+"(period, number) is deprecated. Please use moment()."+b+"(number, period)."),f=c,c=d,d=f),c="string"==typeof c?+c:c,e=Ya(c,d),bb(this,e,a),this}}function bb(b,c,d,e){var f=c._milliseconds,g=c._days,h=c._months;e=null==e?!0:e,f&&b._d.setTime(+b._d+f*d),g&&E(b,"Date",D(b,"Date")+g*d),h&&X(b,D(b,"Month")+h*d),e&&a.updateOffset(b,g||h)}function cb(a,b){var c=a||Da(),d=La(c,this).startOf("day"),e=this.diff(d,"days",!0),f=-6>e?"sameElse":-1>e?"lastWeek":0>e?"lastDay":1>e?"sameDay":2>e?"nextDay":7>e?"nextWeek":"sameElse";return this.format(b&&b[f]||this.localeData().calendar(f,this,Da(c)))}function db(){return new n(this)}function eb(a,b){var c;return b=A("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=o(a)?a:Da(a),+this>+a):(c=o(a)?+a:+Da(a),c<+this.clone().startOf(b))}function fb(a,b){var c;return b=A("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=o(a)?a:Da(a),+a>+this):(c=o(a)?+a:+Da(a),+this.clone().endOf(b)b-f?(c=a.clone().add(e-1,"months"),d=(b-f)/(f-c)):(c=a.clone().add(e+1,"months"),d=(b-f)/(c-f)),-(e+d)}function kb(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function lb(){var a=this.clone().utc();return 0b;b++)if(this._weekdaysParse[b]||(c=Da([2e3,1]).day(b),d="^"+this.weekdays(c,"")+"|^"+this.weekdaysShort(c,"")+"|^"+this.weekdaysMin(c,""),this._weekdaysParse[b]=new RegExp(d.replace(".",""),"i")),this._weekdaysParse[b].test(a))return b}function Pb(a){var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=Kb(a,this.localeData()),this.add(a-b,"d")):b}function Qb(a){var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")}function Rb(a){return null==a?this.day()||7:this.day(this.day()%7?a:a-7)}function Sb(a,b){H(a,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),b)})}function Tb(a,b){return b._meridiemParse}function Ub(a){return"p"===(a+"").toLowerCase().charAt(0)}function Vb(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"}function Wb(a,b){b[ld]=q(1e3*("0."+a))}function Xb(){return this._isUTC?"UTC":""}function Yb(){return this._isUTC?"Coordinated Universal Time":""}function Zb(a){return Da(1e3*a)}function $b(){return Da.apply(null,arguments).parseZone()}function _b(a,b,c){var d=this._calendar[a];return"function"==typeof d?d.call(b,c):d}function ac(a){var b=this._longDateFormat[a],c=this._longDateFormat[a.toUpperCase()];return b||!c?b:(this._longDateFormat[a]=c.replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a])}function bc(){return this._invalidDate}function cc(a){return this._ordinal.replace("%d",a)}function dc(a){return a}function ec(a,b,c,d){var e=this._relativeTime[c];return"function"==typeof e?e(a,b,c,d):e.replace(/%d/i,a)}function fc(a,b){var c=this._relativeTime[a>0?"future":"past"];return"function"==typeof c?c(b):c.replace(/%s/i,b)}function gc(a){var b,c;for(c in a)b=a[c],"function"==typeof b?this[c]=b:this["_"+c]=b;this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function hc(a,b,c,d){var e=y(),f=h().set(d,b);return e[c](f,a)}function ic(a,b,c,d,e){if("number"==typeof a&&(b=a,a=void 0),a=a||"",null!=b)return hc(a,b,c,e);var f,g=[];for(f=0;d>f;f++)g[f]=hc(a,f,c,e);return g}function jc(a,b){return ic(a,b,"months",12,"month")}function kc(a,b){return ic(a,b,"monthsShort",12,"month")}function lc(a,b){return ic(a,b,"weekdays",7,"day")}function mc(a,b){return ic(a,b,"weekdaysShort",7,"day")}function nc(a,b){return ic(a,b,"weekdaysMin",7,"day")}function oc(){var a=this._data;return this._milliseconds=Wd(this._milliseconds),this._days=Wd(this._days),this._months=Wd(this._months),a.milliseconds=Wd(a.milliseconds),a.seconds=Wd(a.seconds),a.minutes=Wd(a.minutes),a.hours=Wd(a.hours),a.months=Wd(a.months),a.years=Wd(a.years),this}function pc(a,b,c,d){var e=Ya(b,c);return a._milliseconds+=d*e._milliseconds,a._days+=d*e._days,a._months+=d*e._months,a._bubble()}function qc(a,b){return pc(this,a,b,1)}function rc(a,b){return pc(this,a,b,-1)}function sc(a){return 0>a?Math.floor(a):Math.ceil(a)}function tc(){var a,b,c,d,e,f=this._milliseconds,g=this._days,h=this._months,i=this._data;return f>=0&&g>=0&&h>=0||0>=f&&0>=g&&0>=h||(f+=864e5*sc(vc(h)+g),g=0,h=0),i.milliseconds=f%1e3,a=p(f/1e3),i.seconds=a%60,b=p(a/60),i.minutes=b%60,c=p(b/60),i.hours=c%24,g+=p(c/24),e=p(uc(g)),h+=e,g-=sc(vc(e)),d=p(h/12),h%=12,i.days=g,i.months=h,i.years=d,this}function uc(a){return 4800*a/146097}function vc(a){return 146097*a/4800}function wc(a){var b,c,d=this._milliseconds;if(a=A(a),"month"===a||"year"===a)return b=this._days+d/864e5,c=this._months+uc(b),"month"===a?c:c/12;switch(b=this._days+Math.round(vc(this._months)),a){case"week":return b/7+d/6048e5;case"day":return b+d/864e5;case"hour":return 24*b+d/36e5;case"minute":return 1440*b+d/6e4;case"second":return 86400*b+d/1e3;case"millisecond":return Math.floor(864e5*b)+d;default:throw new Error("Unknown unit "+a)}}function xc(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*q(this._months/12)}function yc(a){return function(){return this.as(a)}}function zc(a){return a=A(a),this[a+"s"]()}function Ac(a){return function(){return this._data[a]}}function Bc(){return p(this.days()/7)}function Cc(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function Dc(a,b,c){var d=Ya(a).abs(),e=ke(d.as("s")),f=ke(d.as("m")),g=ke(d.as("h")),h=ke(d.as("d")),i=ke(d.as("M")),j=ke(d.as("y")),k=e0,k[4]=c,Cc.apply(null,k)}function Ec(a,b){return void 0===le[a]?!1:void 0===b?le[a]:(le[a]=b,!0)}function Fc(a){var b=this.localeData(),c=Dc(this,!a,b);return a&&(c=b.pastFuture(+this,c)),b.postformat(c)}function Gc(){var a,b,c,d=me(this._milliseconds)/1e3,e=me(this._days),f=me(this._months);a=p(d/60),b=p(a/60),d%=60,a%=60,c=p(f/12),f%=12;var g=c,h=f,i=e,j=b,k=a,l=d,m=this.asSeconds();return m?(0>m?"-":"")+"P"+(g?g+"Y":"")+(h?h+"M":"")+(i?i+"D":"")+(j||k||l?"T":"")+(j?j+"H":"")+(k?k+"M":"")+(l?l+"S":""):"P0D"}var Hc,Ic,Jc=a.momentProperties=[],Kc=!1,Lc={},Mc={},Nc=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,Oc=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,Pc={},Qc={},Rc=/\d/,Sc=/\d\d/,Tc=/\d{3}/,Uc=/\d{4}/,Vc=/[+-]?\d{6}/,Wc=/\d\d?/,Xc=/\d{1,3}/,Yc=/\d{1,4}/,Zc=/[+-]?\d{1,6}/,$c=/\d+/,_c=/[+-]?\d+/,ad=/Z|[+-]\d\d:?\d\d/gi,bd=/[+-]?\d+(\.\d{1,3})?/,cd=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,dd={},ed={},fd=0,gd=1,hd=2,id=3,jd=4,kd=5,ld=6;H("M",["MM",2],"Mo",function(){return this.month()+1}),H("MMM",0,0,function(a){return this.localeData().monthsShort(this,a)}),H("MMMM",0,0,function(a){return this.localeData().months(this,a)}),z("month","M"),N("M",Wc),N("MM",Wc,Sc),N("MMM",cd),N("MMMM",cd),Q(["M","MM"],function(a,b){b[gd]=q(a)-1}),Q(["MMM","MMMM"],function(a,b,c,d){var e=c._locale.monthsParse(a,d,c._strict);null!=e?b[gd]=e:j(c).invalidMonth=a});var md="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),nd="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),od={};a.suppressDeprecationWarnings=!1;var pd=/^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,qd=[["YYYYYY-MM-DD",/[+-]\d{6}-\d{2}-\d{2}/],["YYYY-MM-DD",/\d{4}-\d{2}-\d{2}/],["GGGG-[W]WW-E",/\d{4}-W\d{2}-\d/],["GGGG-[W]WW",/\d{4}-W\d{2}/],["YYYY-DDD",/\d{4}-\d{3}/]],rd=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],sd=/^\/?Date\((\-?\d+)/i;a.createFromInputFallback=aa("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(a){a._d=new Date(a._i+(a._useUTC?" UTC":""))}),H(0,["YY",2],0,function(){return this.year()%100}),H(0,["YYYY",4],0,"year"),H(0,["YYYYY",5],0,"year"),H(0,["YYYYYY",6,!0],0,"year"),z("year","y"),N("Y",_c),N("YY",Wc,Sc),N("YYYY",Yc,Uc),N("YYYYY",Zc,Vc),N("YYYYYY",Zc,Vc),Q(["YYYYY","YYYYYY"],fd),Q("YYYY",function(b,c){c[fd]=2===b.length?a.parseTwoDigitYear(b):q(b)}),Q("YY",function(b,c){c[fd]=a.parseTwoDigitYear(b)}),a.parseTwoDigitYear=function(a){return q(a)+(q(a)>68?1900:2e3)};var td=C("FullYear",!1);H("w",["ww",2],"wo","week"),H("W",["WW",2],"Wo","isoWeek"),z("week","w"),z("isoWeek","W"),N("w",Wc),N("ww",Wc,Sc),N("W",Wc),N("WW",Wc,Sc),R(["w","ww","W","WW"],function(a,b,c,d){b[d.substr(0,1)]=q(a)});var ud={dow:0,doy:6};H("DDD",["DDDD",3],"DDDo","dayOfYear"),z("dayOfYear","DDD"),N("DDD",Xc),N("DDDD",Tc),Q(["DDD","DDDD"],function(a,b,c){c._dayOfYear=q(a)}),a.ISO_8601=function(){};var vd=aa("moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var a=Da.apply(null,arguments);return this>a?this:a}),wd=aa("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var a=Da.apply(null,arguments);return a>this?this:a});Ja("Z",":"),Ja("ZZ",""),N("Z",ad),N("ZZ",ad),Q(["Z","ZZ"],function(a,b,c){c._useUTC=!0,c._tzm=Ka(a)});var xd=/([\+\-]|\d\d)/gi;a.updateOffset=function(){};var yd=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,zd=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/;Ya.fn=Ha.prototype;var Ad=ab(1,"add"),Bd=ab(-1,"subtract");a.defaultFormat="YYYY-MM-DDTHH:mm:ssZ";var Cd=aa("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(a){return void 0===a?this.localeData():this.locale(a)});H(0,["gg",2],0,function(){return this.weekYear()%100}),H(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Db("gggg","weekYear"),Db("ggggg","weekYear"),Db("GGGG","isoWeekYear"),Db("GGGGG","isoWeekYear"),z("weekYear","gg"),z("isoWeekYear","GG"),N("G",_c),N("g",_c),N("GG",Wc,Sc),N("gg",Wc,Sc),N("GGGG",Yc,Uc),N("gggg",Yc,Uc),N("GGGGG",Zc,Vc),N("ggggg",Zc,Vc),R(["gggg","ggggg","GGGG","GGGGG"],function(a,b,c,d){b[d.substr(0,2)]=q(a)}),R(["gg","GG"],function(b,c,d,e){c[e]=a.parseTwoDigitYear(b)}),H("Q",0,0,"quarter"),z("quarter","Q"),N("Q",Rc),Q("Q",function(a,b){b[gd]=3*(q(a)-1)}),H("D",["DD",2],"Do","date"),z("date","D"),N("D",Wc),N("DD",Wc,Sc),N("Do",function(a,b){return a?b._ordinalParse:b._ordinalParseLenient}),Q(["D","DD"],hd),Q("Do",function(a,b){b[hd]=q(a.match(Wc)[0],10)});var Dd=C("Date",!0);H("d",0,"do","day"),H("dd",0,0,function(a){return this.localeData().weekdaysMin(this,a)}),H("ddd",0,0,function(a){return this.localeData().weekdaysShort(this,a)}),H("dddd",0,0,function(a){return this.localeData().weekdays(this,a)}),H("e",0,0,"weekday"),H("E",0,0,"isoWeekday"),z("day","d"),z("weekday","e"),z("isoWeekday","E"),N("d",Wc),N("e",Wc),N("E",Wc),N("dd",cd),N("ddd",cd),N("dddd",cd),R(["dd","ddd","dddd"],function(a,b,c){var d=c._locale.weekdaysParse(a);null!=d?b.d=d:j(c).invalidWeekday=a}),R(["d","e","E"],function(a,b,c,d){b[d]=q(a)});var Ed="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Fd="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Gd="Su_Mo_Tu_We_Th_Fr_Sa".split("_");H("H",["HH",2],0,"hour"),H("h",["hh",2],0,function(){return this.hours()%12||12}),Sb("a",!0),Sb("A",!1),z("hour","h"),N("a",Tb),N("A",Tb),N("H",Wc),N("h",Wc),N("HH",Wc,Sc),N("hh",Wc,Sc),Q(["H","HH"],id),Q(["a","A"],function(a,b,c){c._isPm=c._locale.isPM(a),c._meridiem=a}),Q(["h","hh"],function(a,b,c){b[id]=q(a),j(c).bigHour=!0});var Hd=/[ap]\.?m?\.?/i,Id=C("Hours",!0);H("m",["mm",2],0,"minute"),z("minute","m"),N("m",Wc),N("mm",Wc,Sc),Q(["m","mm"],jd);var Jd=C("Minutes",!1);H("s",["ss",2],0,"second"),z("second","s"),N("s",Wc),N("ss",Wc,Sc),Q(["s","ss"],kd);var Kd=C("Seconds",!1);H("S",0,0,function(){return~~(this.millisecond()/100)}),H(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),H(0,["SSS",3],0,"millisecond"),H(0,["SSSS",4],0,function(){return 10*this.millisecond()}),H(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),H(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),H(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),H(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),H(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),z("millisecond","ms"),N("S",Xc,Rc),N("SS",Xc,Sc),N("SSS",Xc,Tc);var Ld;for(Ld="SSSS";Ld.length<=9;Ld+="S")N(Ld,$c);for(Ld="S";Ld.length<=9;Ld+="S")Q(Ld,Wb);var Md=C("Milliseconds",!1);H("z",0,0,"zoneAbbr"),H("zz",0,0,"zoneName");var Nd=n.prototype;Nd.add=Ad,Nd.calendar=cb,Nd.clone=db,Nd.diff=ib,Nd.endOf=ub,Nd.format=mb,Nd.from=nb,Nd.fromNow=ob,Nd.to=pb,Nd.toNow=qb,Nd.get=F,Nd.invalidAt=Cb,Nd.isAfter=eb,Nd.isBefore=fb,Nd.isBetween=gb,Nd.isSame=hb,Nd.isValid=Ab,Nd.lang=Cd,Nd.locale=rb,Nd.localeData=sb,Nd.max=wd,Nd.min=vd,Nd.parsingFlags=Bb,Nd.set=F,Nd.startOf=tb,Nd.subtract=Bd,Nd.toArray=yb,Nd.toObject=zb,Nd.toDate=xb,Nd.toISOString=lb,Nd.toJSON=lb,Nd.toString=kb,Nd.unix=wb,Nd.valueOf=vb,Nd.year=td,Nd.isLeapYear=ia,Nd.weekYear=Fb,Nd.isoWeekYear=Gb,Nd.quarter=Nd.quarters=Jb,Nd.month=Y,Nd.daysInMonth=Z,Nd.week=Nd.weeks=na,Nd.isoWeek=Nd.isoWeeks=oa,Nd.weeksInYear=Ib,Nd.isoWeeksInYear=Hb,Nd.date=Dd,Nd.day=Nd.days=Pb,Nd.weekday=Qb,Nd.isoWeekday=Rb,Nd.dayOfYear=qa,Nd.hour=Nd.hours=Id,Nd.minute=Nd.minutes=Jd,Nd.second=Nd.seconds=Kd, -Nd.millisecond=Nd.milliseconds=Md,Nd.utcOffset=Na,Nd.utc=Pa,Nd.local=Qa,Nd.parseZone=Ra,Nd.hasAlignedHourOffset=Sa,Nd.isDST=Ta,Nd.isDSTShifted=Ua,Nd.isLocal=Va,Nd.isUtcOffset=Wa,Nd.isUtc=Xa,Nd.isUTC=Xa,Nd.zoneAbbr=Xb,Nd.zoneName=Yb,Nd.dates=aa("dates accessor is deprecated. Use date instead.",Dd),Nd.months=aa("months accessor is deprecated. Use month instead",Y),Nd.years=aa("years accessor is deprecated. Use year instead",td),Nd.zone=aa("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",Oa);var Od=Nd,Pd={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},Qd={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},Rd="Invalid date",Sd="%d",Td=/\d{1,2}/,Ud={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},Vd=s.prototype;Vd._calendar=Pd,Vd.calendar=_b,Vd._longDateFormat=Qd,Vd.longDateFormat=ac,Vd._invalidDate=Rd,Vd.invalidDate=bc,Vd._ordinal=Sd,Vd.ordinal=cc,Vd._ordinalParse=Td,Vd.preparse=dc,Vd.postformat=dc,Vd._relativeTime=Ud,Vd.relativeTime=ec,Vd.pastFuture=fc,Vd.set=gc,Vd.months=U,Vd._months=md,Vd.monthsShort=V,Vd._monthsShort=nd,Vd.monthsParse=W,Vd.week=ka,Vd._week=ud,Vd.firstDayOfYear=ma,Vd.firstDayOfWeek=la,Vd.weekdays=Lb,Vd._weekdays=Ed,Vd.weekdaysMin=Nb,Vd._weekdaysMin=Gd,Vd.weekdaysShort=Mb,Vd._weekdaysShort=Fd,Vd.weekdaysParse=Ob,Vd.isPM=Ub,Vd._meridiemParse=Hd,Vd.meridiem=Vb,w("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===q(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}),a.lang=aa("moment.lang is deprecated. Use moment.locale instead.",w),a.langData=aa("moment.langData is deprecated. Use moment.localeData instead.",y);var Wd=Math.abs,Xd=yc("ms"),Yd=yc("s"),Zd=yc("m"),$d=yc("h"),_d=yc("d"),ae=yc("w"),be=yc("M"),ce=yc("y"),de=Ac("milliseconds"),ee=Ac("seconds"),fe=Ac("minutes"),ge=Ac("hours"),he=Ac("days"),ie=Ac("months"),je=Ac("years"),ke=Math.round,le={s:45,m:45,h:22,d:26,M:11},me=Math.abs,ne=Ha.prototype;ne.abs=oc,ne.add=qc,ne.subtract=rc,ne.as=wc,ne.asMilliseconds=Xd,ne.asSeconds=Yd,ne.asMinutes=Zd,ne.asHours=$d,ne.asDays=_d,ne.asWeeks=ae,ne.asMonths=be,ne.asYears=ce,ne.valueOf=xc,ne._bubble=tc,ne.get=zc,ne.milliseconds=de,ne.seconds=ee,ne.minutes=fe,ne.hours=ge,ne.days=he,ne.weeks=Bc,ne.months=ie,ne.years=je,ne.humanize=Fc,ne.toISOString=Gc,ne.toString=Gc,ne.toJSON=Gc,ne.locale=rb,ne.localeData=sb,ne.toIsoString=aa("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",Gc),ne.lang=Cd,H("X",0,0,"unix"),H("x",0,0,"valueOf"),N("x",_c),N("X",bd),Q("X",function(a,b,c){c._d=new Date(1e3*parseFloat(a,10))}),Q("x",function(a,b,c){c._d=new Date(q(a))}),a.version="2.10.6",b(Da),a.fn=Od,a.min=Fa,a.max=Ga,a.utc=h,a.unix=Zb,a.months=jc,a.isDate=d,a.locale=w,a.invalid=l,a.duration=Ya,a.isMoment=o,a.weekdays=lc,a.parseZone=$b,a.localeData=y,a.isDuration=Ia,a.monthsShort=kc,a.weekdaysMin=nc,a.defineLocale=x,a.weekdaysShort=mc,a.normalizeUnits=A,a.relativeTimeThreshold=Ec;var oe=a;return oe}); \ No newline at end of file +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.moment=t()}(this,function(){"use strict";var e,i;function c(){return e.apply(null,arguments)}function o(e){return e instanceof Array||"[object Array]"===Object.prototype.toString.call(e)}function u(e){return null!=e&&"[object Object]"===Object.prototype.toString.call(e)}function l(e){return void 0===e}function h(e){return"number"==typeof e||"[object Number]"===Object.prototype.toString.call(e)}function d(e){return e instanceof Date||"[object Date]"===Object.prototype.toString.call(e)}function f(e,t){var n,s=[];for(n=0;n>>0,s=0;sSe(e)?(r=e+1,o-Se(e)):(r=e,o),{year:r,dayOfYear:a}}function Ie(e,t,n){var s,i,r=Ve(e.year(),t,n),a=Math.floor((e.dayOfYear()-r-1)/7)+1;return a<1?s=a+Ae(i=e.year()-1,t,n):a>Ae(e.year(),t,n)?(s=a-Ae(e.year(),t,n),i=e.year()+1):(i=e.year(),s=a),{week:s,year:i}}function Ae(e,t,n){var s=Ve(e,t,n),i=Ve(e+1,t,n);return(Se(e)-s+i)/7}I("w",["ww",2],"wo","week"),I("W",["WW",2],"Wo","isoWeek"),C("week","w"),C("isoWeek","W"),F("week",5),F("isoWeek",5),ue("w",B),ue("ww",B,z),ue("W",B),ue("WW",B,z),fe(["w","ww","W","WW"],function(e,t,n,s){t[s.substr(0,1)]=D(e)});function je(e,t){return e.slice(t,7).concat(e.slice(0,t))}I("d",0,"do","day"),I("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),I("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),I("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),I("e",0,0,"weekday"),I("E",0,0,"isoWeekday"),C("day","d"),C("weekday","e"),C("isoWeekday","E"),F("day",11),F("weekday",11),F("isoWeekday",11),ue("d",B),ue("e",B),ue("E",B),ue("dd",function(e,t){return t.weekdaysMinRegex(e)}),ue("ddd",function(e,t){return t.weekdaysShortRegex(e)}),ue("dddd",function(e,t){return t.weekdaysRegex(e)}),fe(["dd","ddd","dddd"],function(e,t,n,s){var i=n._locale.weekdaysParse(e,s,n._strict);null!=i?t.d=i:g(n).invalidWeekday=e}),fe(["d","e","E"],function(e,t,n,s){t[s]=D(e)});var Ze="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_");var ze="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_");var $e="Su_Mo_Tu_We_Th_Fr_Sa".split("_");var qe=ae;var Je=ae;var Be=ae;function Qe(){function e(e,t){return t.length-e.length}var t,n,s,i,r,a=[],o=[],u=[],l=[];for(t=0;t<7;t++)n=y([2e3,1]).day(t),s=this.weekdaysMin(n,""),i=this.weekdaysShort(n,""),r=this.weekdays(n,""),a.push(s),o.push(i),u.push(r),l.push(s),l.push(i),l.push(r);for(a.sort(e),o.sort(e),u.sort(e),l.sort(e),t=0;t<7;t++)o[t]=he(o[t]),u[t]=he(u[t]),l[t]=he(l[t]);this._weekdaysRegex=new RegExp("^("+l.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+u.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+o.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+a.join("|")+")","i")}function Xe(){return this.hours()%12||12}function Ke(e,t){I(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function et(e,t){return t._meridiemParse}I("H",["HH",2],0,"hour"),I("h",["hh",2],0,Xe),I("k",["kk",2],0,function(){return this.hours()||24}),I("hmm",0,0,function(){return""+Xe.apply(this)+L(this.minutes(),2)}),I("hmmss",0,0,function(){return""+Xe.apply(this)+L(this.minutes(),2)+L(this.seconds(),2)}),I("Hmm",0,0,function(){return""+this.hours()+L(this.minutes(),2)}),I("Hmmss",0,0,function(){return""+this.hours()+L(this.minutes(),2)+L(this.seconds(),2)}),Ke("a",!0),Ke("A",!1),C("hour","h"),F("hour",13),ue("a",et),ue("A",et),ue("H",B),ue("h",B),ue("k",B),ue("HH",B,z),ue("hh",B,z),ue("kk",B,z),ue("hmm",Q),ue("hmmss",X),ue("Hmm",Q),ue("Hmmss",X),ce(["H","HH"],ge),ce(["k","kk"],function(e,t,n){var s=D(e);t[ge]=24===s?0:s}),ce(["a","A"],function(e,t,n){n._isPm=n._locale.isPM(e),n._meridiem=e}),ce(["h","hh"],function(e,t,n){t[ge]=D(e),g(n).bigHour=!0}),ce("hmm",function(e,t,n){var s=e.length-2;t[ge]=D(e.substr(0,s)),t[ve]=D(e.substr(s)),g(n).bigHour=!0}),ce("hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[ge]=D(e.substr(0,s)),t[ve]=D(e.substr(s,2)),t[pe]=D(e.substr(i)),g(n).bigHour=!0}),ce("Hmm",function(e,t,n){var s=e.length-2;t[ge]=D(e.substr(0,s)),t[ve]=D(e.substr(s))}),ce("Hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[ge]=D(e.substr(0,s)),t[ve]=D(e.substr(s,2)),t[pe]=D(e.substr(i))});var tt,nt=Te("Hours",!0),st={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Ce,monthsShort:He,week:{dow:0,doy:6},weekdays:Ze,weekdaysMin:$e,weekdaysShort:ze,meridiemParse:/[ap]\.?m?\.?/i},it={},rt={};function at(e){return e?e.toLowerCase().replace("_","-"):e}function ot(e){var t=null;if(!it[e]&&"undefined"!=typeof module&&module&&module.exports)try{t=tt._abbr,require("./locale/"+e),ut(t)}catch(e){}return it[e]}function ut(e,t){var n;return e&&((n=l(t)?ht(e):lt(e,t))?tt=n:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+e+" not found. Did you forget to load it?")),tt._abbr}function lt(e,t){if(null===t)return delete it[e],null;var n,s=st;if(t.abbr=e,null!=it[e])T("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),s=it[e]._config;else if(null!=t.parentLocale)if(null!=it[t.parentLocale])s=it[t.parentLocale]._config;else{if(null==(n=ot(t.parentLocale)))return rt[t.parentLocale]||(rt[t.parentLocale]=[]),rt[t.parentLocale].push({name:e,config:t}),null;s=n._config}return it[e]=new P(x(s,t)),rt[e]&&rt[e].forEach(function(e){lt(e.name,e.config)}),ut(e),it[e]}function ht(e){var t;if(e&&e._locale&&e._locale._abbr&&(e=e._locale._abbr),!e)return tt;if(!o(e)){if(t=ot(e))return t;e=[e]}return function(e){for(var t,n,s,i,r=0;r=t&&a(i,n,!0)>=t-1)break;t--}r++}return tt}(e)}function dt(e){var t,n=e._a;return n&&-2===g(e).overflow&&(t=n[_e]<0||11Pe(n[me],n[_e])?ye:n[ge]<0||24Ae(n,r,a)?g(e)._overflowWeeks=!0:null!=u?g(e)._overflowWeekday=!0:(o=Ee(n,s,i,r,a),e._a[me]=o.year,e._dayOfYear=o.dayOfYear)}(e),null!=e._dayOfYear&&(r=ct(e._a[me],s[me]),(e._dayOfYear>Se(r)||0===e._dayOfYear)&&(g(e)._overflowDayOfYear=!0),n=Ge(r,0,e._dayOfYear),e._a[_e]=n.getUTCMonth(),e._a[ye]=n.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=a[t]=s[t];for(;t<7;t++)e._a[t]=a[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[ge]&&0===e._a[ve]&&0===e._a[pe]&&0===e._a[we]&&(e._nextDay=!0,e._a[ge]=0),e._d=(e._useUTC?Ge:function(e,t,n,s,i,r,a){var o;return e<100&&0<=e?(o=new Date(e+400,t,n,s,i,r,a),isFinite(o.getFullYear())&&o.setFullYear(e)):o=new Date(e,t,n,s,i,r,a),o}).apply(null,a),i=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[ge]=24),e._w&&void 0!==e._w.d&&e._w.d!==i&&(g(e).weekdayMismatch=!0)}}var mt=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,_t=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,yt=/Z|[+-]\d\d(?::?\d\d)?/,gt=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],vt=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],pt=/^\/?Date\((\-?\d+)/i;function wt(e){var t,n,s,i,r,a,o=e._i,u=mt.exec(o)||_t.exec(o);if(u){for(g(e).iso=!0,t=0,n=gt.length;tn.valueOf():n.valueOf()this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},mn.isLocal=function(){return!!this.isValid()&&!this._isUTC},mn.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},mn.isUtc=Et,mn.isUTC=Et,mn.zoneAbbr=function(){return this._isUTC?"UTC":""},mn.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},mn.dates=n("dates accessor is deprecated. Use date instead.",un),mn.months=n("months accessor is deprecated. Use month instead",Ue),mn.years=n("years accessor is deprecated. Use year instead",Oe),mn.zone=n("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(e,t){return null!=e?("string"!=typeof e&&(e=-e),this.utcOffset(e,t),this):-this.utcOffset()}),mn.isDSTShifted=n("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!l(this._isDSTShifted))return this._isDSTShifted;var e={};if(w(e,this),(e=Ot(e))._a){var t=e._isUTC?y(e._a):bt(e._a);this._isDSTShifted=this.isValid()&&0b;b++)if(b in this&&this[b]===a)return b;return-1};for(t={catchupTime:500,initialRate:.03,minTime:500,ghostTime:500,maxProgressPerFrame:10,easeFactor:1.25,startOnPageLoad:!0,restartOnPushState:!0,restartOnRequestAfter:500,target:"body",elements:{checkInterval:100,selectors:["body"]},eventLag:{minSamples:10,sampleCount:3,lagThreshold:3},ajax:{trackMethods:["GET"],trackWebSockets:!0,ignoreURLs:[]}},B=function(){var a;return null!=(a="undefined"!=typeof performance&&null!==performance?"function"==typeof performance.now?performance.now():void 0:void 0)?a:+new Date},D=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame,s=window.cancelAnimationFrame||window.mozCancelAnimationFrame,null==D&&(D=function(a){return setTimeout(a,50)},s=function(a){return clearTimeout(a)}),F=function(a){var b,c;return b=B(),(c=function(){var d;return d=B()-b,d>=33?(b=B(),a(d,function(){return D(c)})):setTimeout(c,33-d)})()},E=function(){var a,b,c;return c=arguments[0],b=arguments[1],a=3<=arguments.length?W.call(arguments,2):[],"function"==typeof c[b]?c[b].apply(c,a):c[b]},u=function(){var a,b,c,d,e,f,g;for(b=arguments[0],d=2<=arguments.length?W.call(arguments,1):[],f=0,g=d.length;g>f;f++)if(c=d[f])for(a in c)X.call(c,a)&&(e=c[a],null!=b[a]&&"object"==typeof b[a]&&null!=e&&"object"==typeof e?u(b[a],e):b[a]=e);return b},p=function(a){var b,c,d,e,f;for(c=b=0,e=0,f=a.length;f>e;e++)d=a[e],c+=Math.abs(d),b++;return c/b},w=function(a,b){var c,d,e;if(null==a&&(a="options"),null==b&&(b=!0),e=document.querySelector("[data-pace-"+a+"]")){if(c=e.getAttribute("data-pace-"+a),!b)return c;try{return JSON.parse(c)}catch(f){return d=f,"undefined"!=typeof console&&null!==console?console.error("Error parsing inline pace options",d):void 0}}},g=function(){function a(){}return a.prototype.on=function(a,b,c,d){var e;return null==d&&(d=!1),null==this.bindings&&(this.bindings={}),null==(e=this.bindings)[a]&&(e[a]=[]),this.bindings[a].push({handler:b,ctx:c,once:d})},a.prototype.once=function(a,b,c){return this.on(a,b,c,!0)},a.prototype.off=function(a,b){var c,d,e;if(null!=(null!=(d=this.bindings)?d[a]:void 0)){if(null==b)return delete this.bindings[a];for(c=0,e=[];cP;P++)J=T[P],C[J]===!0&&(C[J]=t[J]);i=function(a){function b(){return U=b.__super__.constructor.apply(this,arguments)}return Y(b,a),b}(Error),b=function(){function a(){this.progress=0}return a.prototype.getElement=function(){var a;if(null==this.el){if(a=document.querySelector(C.target),!a)throw new i;this.el=document.createElement("div"),this.el.className="pace pace-active",document.body.className=document.body.className.replace(/pace-done/g,""),document.body.className+=" pace-running",this.el.innerHTML='
\n
\n
\n
',null!=a.firstChild?a.insertBefore(this.el,a.firstChild):a.appendChild(this.el)}return this.el},a.prototype.finish=function(){var a;return a=this.getElement(),a.className=a.className.replace("pace-active",""),a.className+=" pace-inactive",document.body.className=document.body.className.replace("pace-running",""),document.body.className+=" pace-done"},a.prototype.update=function(a){return this.progress=a,this.render()},a.prototype.destroy=function(){try{this.getElement().parentNode.removeChild(this.getElement())}catch(a){i=a}return this.el=void 0},a.prototype.render=function(){var a,b;return null==document.querySelector(C.target)?!1:(a=this.getElement(),a.children[0].style.width=""+this.progress+"%",(!this.lastRenderedProgress||this.lastRenderedProgress|0!==this.progress|0)&&(a.children[0].setAttribute("data-progress-text",""+(0|this.progress)+"%"),this.progress>=100?b="99":(b=this.progress<10?"0":"",b+=0|this.progress),a.children[0].setAttribute("data-progress",""+b)),this.lastRenderedProgress=this.progress)},a.prototype.done=function(){return this.progress>=100},a}(),h=function(){function a(){this.bindings={}}return a.prototype.trigger=function(a,b){var c,d,e,f,g;if(null!=this.bindings[a]){for(f=this.bindings[a],g=[],d=0,e=f.length;e>d;d++)c=f[d],g.push(c.call(this,b));return g}},a.prototype.on=function(a,b){var c;return null==(c=this.bindings)[a]&&(c[a]=[]),this.bindings[a].push(b)},a}(),O=window.XMLHttpRequest,N=window.XDomainRequest,M=window.WebSocket,v=function(a,b){var c,d,e,f;f=[];for(d in b.prototype)try{e=b.prototype[d],null==a[d]&&"function"!=typeof e?f.push(a[d]=e):f.push(void 0)}catch(g){c=g}return f},z=[],Pace.ignore=function(){var a,b,c;return b=arguments[0],a=2<=arguments.length?W.call(arguments,1):[],z.unshift("ignore"),c=b.apply(null,a),z.shift(),c},Pace.track=function(){var a,b,c;return b=arguments[0],a=2<=arguments.length?W.call(arguments,1):[],z.unshift("track"),c=b.apply(null,a),z.shift(),c},I=function(a){var b;if(null==a&&(a="GET"),"track"===z[0])return"force";if(!z.length&&C.ajax){if("socket"===a&&C.ajax.trackWebSockets)return!0;if(b=a.toUpperCase(),Z.call(C.ajax.trackMethods,b)>=0)return!0}return!1},j=function(a){function b(){var a,c=this;b.__super__.constructor.apply(this,arguments),a=function(a){var b;return b=a.open,a.open=function(d,e){return I(d)&&c.trigger("request",{type:d,url:e,request:a}),b.apply(a,arguments)}},window.XMLHttpRequest=function(b){var c;return c=new O(b),a(c),c},v(window.XMLHttpRequest,O),null!=N&&(window.XDomainRequest=function(){var b;return b=new N,a(b),b},v(window.XDomainRequest,N)),null!=M&&C.ajax.trackWebSockets&&(window.WebSocket=function(a,b){var d;return d=null!=b?new M(a,b):new M(a),I("socket")&&c.trigger("request",{type:"socket",url:a,protocols:b,request:d}),d},v(window.WebSocket,M))}return Y(b,a),b}(h),Q=null,x=function(){return null==Q&&(Q=new j),Q},H=function(a){var b,c,d,e;for(e=C.ajax.ignoreURLs,c=0,d=e.length;d>c;c++)if(b=e[c],"string"==typeof b){if(-1!==a.indexOf(b))return!0}else if(b.test(a))return!0;return!1},x().on("request",function(b){var c,d,e,f,g;return f=b.type,e=b.request,g=b.url,H(g)?void 0:Pace.running||C.restartOnRequestAfter===!1&&"force"!==I(f)?void 0:(d=arguments,c=C.restartOnRequestAfter||0,"boolean"==typeof c&&(c=0),setTimeout(function(){var b,c,g,h,i,j;if(b="socket"===f?e.readyState<2:0<(h=e.readyState)&&4>h){for(Pace.restart(),i=Pace.sources,j=[],c=0,g=i.length;g>c;c++){if(J=i[c],J instanceof a){J.watch.apply(J,d);break}j.push(void 0)}return j}},c))}),a=function(){function a(){var a=this;this.elements=[],x().on("request",function(){return a.watch.apply(a,arguments)})}return a.prototype.watch=function(a){var b,c,d,e;return d=a.type,b=a.request,e=a.url,H(e)?void 0:(c="socket"===d?new m(b):new n(b),this.elements.push(c))},a}(),n=function(){function a(a){var b,c,d,e,f,g,h=this;if(this.progress=0,null!=window.ProgressEvent)for(c=null,a.addEventListener("progress",function(a){return h.progress=a.lengthComputable?100*a.loaded/a.total:h.progress+(100-h.progress)/2}),g=["load","abort","timeout","error"],d=0,e=g.length;e>d;d++)b=g[d],a.addEventListener(b,function(){return h.progress=100});else f=a.onreadystatechange,a.onreadystatechange=function(){var b;return 0===(b=a.readyState)||4===b?h.progress=100:3===a.readyState&&(h.progress=50),"function"==typeof f?f.apply(null,arguments):void 0}}return a}(),m=function(){function a(a){var b,c,d,e,f=this;for(this.progress=0,e=["error","open"],c=0,d=e.length;d>c;c++)b=e[c],a.addEventListener(b,function(){return f.progress=100})}return a}(),d=function(){function a(a){var b,c,d,f;for(null==a&&(a={}),this.elements=[],null==a.selectors&&(a.selectors=[]),f=a.selectors,c=0,d=f.length;d>c;c++)b=f[c],this.elements.push(new e(b))}return a}(),e=function(){function a(a){this.selector=a,this.progress=0,this.check()}return a.prototype.check=function(){var a=this;return document.querySelector(this.selector)?this.done():setTimeout(function(){return a.check()},C.elements.checkInterval)},a.prototype.done=function(){return this.progress=100},a}(),c=function(){function a(){var a,b,c=this;this.progress=null!=(b=this.states[document.readyState])?b:100,a=document.onreadystatechange,document.onreadystatechange=function(){return null!=c.states[document.readyState]&&(c.progress=c.states[document.readyState]),"function"==typeof a?a.apply(null,arguments):void 0}}return a.prototype.states={loading:0,interactive:50,complete:100},a}(),f=function(){function a(){var a,b,c,d,e,f=this;this.progress=0,a=0,e=[],d=0,c=B(),b=setInterval(function(){var g;return g=B()-c-50,c=B(),e.push(g),e.length>C.eventLag.sampleCount&&e.shift(),a=p(e),++d>=C.eventLag.minSamples&&a=100&&(this.done=!0),b===this.last?this.sinceLastUpdate+=a:(this.sinceLastUpdate&&(this.rate=(b-this.last)/this.sinceLastUpdate),this.catchup=(b-this.progress)/C.catchupTime,this.sinceLastUpdate=0,this.last=b),b>this.progress&&(this.progress+=this.catchup*a),c=1-Math.pow(this.progress/100,C.easeFactor),this.progress+=c*this.rate*a,this.progress=Math.min(this.lastProgress+C.maxProgressPerFrame,this.progress),this.progress=Math.max(0,this.progress),this.progress=Math.min(100,this.progress),this.lastProgress=this.progress,this.progress},a}(),K=null,G=null,q=null,L=null,o=null,r=null,Pace.running=!1,y=function(){return C.restartOnPushState?Pace.restart():void 0},null!=window.history.pushState&&(S=window.history.pushState,window.history.pushState=function(){return y(),S.apply(window.history,arguments)}),null!=window.history.replaceState&&(V=window.history.replaceState,window.history.replaceState=function(){return y(),V.apply(window.history,arguments)}),k={ajax:a,elements:d,document:c,eventLag:f},(A=function(){var a,c,d,e,f,g,h,i;for(Pace.sources=K=[],g=["ajax","elements","document","eventLag"],c=0,e=g.length;e>c;c++)a=g[c],C[a]!==!1&&K.push(new k[a](C[a]));for(i=null!=(h=C.extraSources)?h:[],d=0,f=i.length;f>d;d++)J=i[d],K.push(new J(C));return Pace.bar=q=new b,G=[],L=new l})(),Pace.stop=function(){return Pace.trigger("stop"),Pace.running=!1,q.destroy(),r=!0,null!=o&&("function"==typeof s&&s(o),o=null),A()},Pace.restart=function(){return Pace.trigger("restart"),Pace.stop(),Pace.start()},Pace.go=function(){var a;return Pace.running=!0,q.render(),a=B(),r=!1,o=F(function(b,c){var d,e,f,g,h,i,j,k,m,n,o,p,s,t,u,v;for(k=100-q.progress,e=o=0,f=!0,i=p=0,t=K.length;t>p;i=++p)for(J=K[i],n=null!=G[i]?G[i]:G[i]=[],h=null!=(v=J.elements)?v:[J],j=s=0,u=h.length;u>s;j=++s)g=h[j],m=null!=n[j]?n[j]:n[j]=new l(g),f&=m.done,m.done||(e++,o+=m.tick(b));return d=o/e,q.update(L.tick(b,d)),q.done()||f||r?(q.update(100),Pace.trigger("done"),setTimeout(function(){return q.finish(),Pace.running=!1,Pace.trigger("hide")},Math.max(C.ghostTime,Math.max(C.minTime-(B()-a),0)))):c()})},Pace.start=function(a){u(C,a),Pace.running=!0;try{q.render()}catch(b){i=b}return document.querySelector(".pace")?(Pace.trigger("start"),Pace.go()):setTimeout(Pace.start,50)},"function"==typeof define&&define.amd?define(function(){return Pace}):"object"==typeof exports?module.exports=Pace:C.startOnPageLoad&&Pace.start()}).call(this); \ No newline at end of file diff --git a/apps/static/js/plugins/peity/jquery.peity.min.js b/apps/static/js/plugins/peity/jquery.peity.min.js deleted file mode 100644 index 085b72507..000000000 --- a/apps/static/js/plugins/peity/jquery.peity.min.js +++ /dev/null @@ -1,13 +0,0 @@ -// Peity jQuery plugin version 2.0.3 -// (c) 2014 Ben Pickles -// -// http://benpickles.github.io/peity -// -// Released under MIT license. -(function(e,q,h){var o=function(a,b){var c=q.createElementNS("http://www.w3.org/2000/svg",a);e.each(b,function(a,b){c.setAttribute(a,b)});return c},t="createElementNS"in q&&o("svg",{}).createSVGRect,r=1/(window.devicePixelRatio||1),j=e.fn.peity=function(a,b){t&&this.each(function(){var c=e(this),d=c.data("peity");if(d)a&&(d.type=a),e.extend(d.opts,b);else{var f=j.defaults[a],g={};e.each(c.data(),function(a,b){a in f&&(g[a]=b)});var h=e.extend({},f,g,b),d=new s(c,a,h);c.change(function(){d.draw()}).data("peity", - d)}d.draw()});return this},s=function(a,b,c){this.$el=a;this.type=b;this.opts=c},m=s.prototype;m.draw=function(){j.graphers[this.type].call(this,this.opts)};m.fill=function(){var a=this.opts.fill,b=a;e.isFunction(b)||(b=function(b,d){return a[d%a.length]});return b};m.prepare=function(a,b){var c;this.svg?c=e(this.svg).empty():(this.svg=o("svg",{"class":"peity"}),this.$el.hide().after(this.svg),c=e(this.svg).data("peity",this));this.svg.setAttribute("height",b);this.svg.setAttribute("width",a);return c}; - m.values=function(){return e.map(this.$el.text().split(this.opts.delimiter),function(a){return parseFloat(a)})};j.defaults={};j.graphers={};j.register=function(a,b,c){this.defaults[a]=b;this.graphers[a]=c};j.register("pie",{delimiter:null,diameter:16,fill:["#ff9900","#fff4dd","#ffc66e"]},function(a){if(!a.delimiter){var b=this.$el.text().match(/[^0-9\.]/);a.delimiter=b?b[0]:","}b=this.values();if("/"==a.delimiter)var c=b[0],b=[c,h.max(0,b[1]-c)];for(var d=0,c=b.length,f=0;de?1:0,1,q,r,"Z"].join(" ")});i=l}k.setAttribute("fill",j.call(this,n,d,b));this.svg.appendChild(k)}}});j.register("line",{delimiter:",",fill:"#c6d9fd",height:16,max:null, - min:0,stroke:"#4d89f9",strokeWidth:1,width:32},function(a){var b=this.values();1==b.length&&b.push(b[0]);for(var c=h.max.apply(h,b.concat([a.max])),d=h.min.apply(h,b.concat([a.min])),f=this.prepare(a.width,a.height),g=f.width(),f=f.height()-a.strokeWidth,e=g/(b.length-1),c=c-d,j=0==c?f:f/c,m=f+d*j,c=[0,m],i=0;i=d&&0k&&(l+=k,k=-k);n=o("rect",{fill:m.call(this,n,i,b),x:i*g,y:l,width:g-a,height:k});this.svg.appendChild(n)}})})(jQuery,document,Math); diff --git a/apps/static/js/plugins/slimscroll/jquery.slimscroll.js b/apps/static/js/plugins/slimscroll/jquery.slimscroll.js deleted file mode 100644 index 2ea5b0801..000000000 --- a/apps/static/js/plugins/slimscroll/jquery.slimscroll.js +++ /dev/null @@ -1,464 +0,0 @@ -/*! Copyright (c) 2011 Piotr Rochala (http://rocha.la) - * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) - * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. - * - * Version: 1.3.0 - * - */ -(function($) { - - jQuery.fn.extend({ - slimScroll: function(options) { - - var defaults = { - - // width in pixels of the visible scroll area - width : 'auto', - - // height in pixels of the visible scroll area - height : '250px', - - // width in pixels of the scrollbar and rail - size : '7px', - - // scrollbar color, accepts any hex/color value - color: '#000', - - // scrollbar position - left/right - position : 'right', - - // distance in pixels between the side edge and the scrollbar - distance : '1px', - - // default scroll position on load - top / bottom / $('selector') - start : 'top', - - // sets scrollbar opacity - opacity : .4, - - // enables always-on mode for the scrollbar - alwaysVisible : false, - - // check if we should hide the scrollbar when user is hovering over - disableFadeOut : false, - - // sets visibility of the rail - railVisible : false, - - // sets rail color - railColor : '#333', - - // sets rail opacity - railOpacity : .2, - - // whether we should use jQuery UI Draggable to enable bar dragging - railDraggable : true, - - // defautlt CSS class of the slimscroll rail - railClass : 'slimScrollRail', - - // defautlt CSS class of the slimscroll bar - barClass : 'slimScrollBar', - - // defautlt CSS class of the slimscroll wrapper - wrapperClass : 'slimScrollDiv', - - // check if mousewheel should scroll the window if we reach top/bottom - allowPageScroll : false, - - // scroll amount applied to each mouse wheel step - wheelStep : 20, - - // scroll amount applied when user is using gestures - touchScrollStep : 200, - - // sets border radius - borderRadius: '7px', - - // sets border radius of the rail - railBorderRadius : '7px' - }; - - var o = $.extend(defaults, options); - - // do it for every element that matches selector - this.each(function(){ - - var isOverPanel, isOverBar, isDragg, queueHide, touchDif, - barHeight, percentScroll, lastScroll, - divS = '
', - minBarHeight = 30, - releaseScroll = false; - - // used in event handlers and for better minification - var me = $(this); - - // ensure we are not binding it again - if (me.parent().hasClass(o.wrapperClass)) - { - // start from last bar position - var offset = me.scrollTop(); - - // find bar and rail - bar = me.parent().find('.' + o.barClass); - rail = me.parent().find('.' + o.railClass); - - getBarHeight(); - - // check if we should scroll existing instance - if ($.isPlainObject(options)) - { - // Pass height: auto to an existing slimscroll object to force a resize after contents have changed - if ( 'height' in options && options.height == 'auto' ) { - me.parent().css('height', 'auto'); - me.css('height', 'auto'); - var height = me.parent().parent().height(); - me.parent().css('height', height); - me.css('height', height); - } - - if ('scrollTo' in options) - { - // jump to a static point - offset = parseInt(o.scrollTo); - } - else if ('scrollBy' in options) - { - // jump by value pixels - offset += parseInt(o.scrollBy); - } - else if ('destroy' in options) - { - // remove slimscroll elements - bar.remove(); - rail.remove(); - me.unwrap(); - return; - } - - // scroll content by the given offset - scrollContent(offset, false, true); - } - - return; - } - - // optionally set height to the parent's height - o.height = (o.height == 'auto') ? me.parent().height() : o.height; - - // wrap content - var wrapper = $(divS) - .addClass(o.wrapperClass) - .css({ - position: 'relative', - overflow: 'hidden', - width: o.width, - height: o.height - }); - - // update style for the div - me.css({ - overflow: 'hidden', - width: o.width, - height: o.height - }); - - // create scrollbar rail - var rail = $(divS) - .addClass(o.railClass) - .css({ - width: o.size, - height: '100%', - position: 'absolute', - top: 0, - display: (o.alwaysVisible && o.railVisible) ? 'block' : 'none', - 'border-radius': o.railBorderRadius, - background: o.railColor, - opacity: o.railOpacity, - zIndex: 90 - }); - - // create scrollbar - var bar = $(divS) - .addClass(o.barClass) - .css({ - background: o.color, - width: o.size, - position: 'absolute', - top: 0, - opacity: o.opacity, - display: o.alwaysVisible ? 'block' : 'none', - 'border-radius' : o.borderRadius, - BorderRadius: o.borderRadius, - MozBorderRadius: o.borderRadius, - WebkitBorderRadius: o.borderRadius, - zIndex: 99 - }); - - // set position - var posCss = (o.position == 'right') ? { right: o.distance } : { left: o.distance }; - rail.css(posCss); - bar.css(posCss); - - // wrap it - me.wrap(wrapper); - - // append to parent div - me.parent().append(bar); - me.parent().append(rail); - - // make it draggable and no longer dependent on the jqueryUI - if (o.railDraggable){ - bar.bind("mousedown", function(e) { - var $doc = $(document); - isDragg = true; - t = parseFloat(bar.css('top')); - pageY = e.pageY; - - $doc.bind("mousemove.slimscroll", function(e){ - currTop = t + e.pageY - pageY; - bar.css('top', currTop); - scrollContent(0, bar.position().top, false);// scroll content - }); - - $doc.bind("mouseup.slimscroll", function(e) { - isDragg = false;hideBar(); - $doc.unbind('.slimscroll'); - }); - return false; - }).bind("selectstart.slimscroll", function(e){ - e.stopPropagation(); - e.preventDefault(); - return false; - }); - } - - // on rail over - rail.hover(function(){ - showBar(); - }, function(){ - hideBar(); - }); - - // on bar over - bar.hover(function(){ - isOverBar = true; - }, function(){ - isOverBar = false; - }); - - // show on parent mouseover - me.hover(function(){ - isOverPanel = true; - showBar(); - hideBar(); - }, function(){ - isOverPanel = false; - hideBar(); - }); - - // support for mobile - me.bind('touchstart', function(e,b){ - if (e.originalEvent.touches.length) - { - // record where touch started - touchDif = e.originalEvent.touches[0].pageY; - } - }); - - me.bind('touchmove', function(e){ - // prevent scrolling the page if necessary - if(!releaseScroll) - { - e.originalEvent.preventDefault(); - } - if (e.originalEvent.touches.length) - { - // see how far user swiped - var diff = (touchDif - e.originalEvent.touches[0].pageY) / o.touchScrollStep; - // scroll content - scrollContent(diff, true); - touchDif = e.originalEvent.touches[0].pageY; - } - }); - - // set up initial height - getBarHeight(); - - // check start position - if (o.start === 'bottom') - { - // scroll content to bottom - bar.css({ top: me.outerHeight() - bar.outerHeight() }); - scrollContent(0, true); - } - else if (o.start !== 'top') - { - // assume jQuery selector - scrollContent($(o.start).position().top, null, true); - - // make sure bar stays hidden - if (!o.alwaysVisible) { bar.hide(); } - } - - // attach scroll events - attachWheel(); - - function _onWheel(e) - { - // use mouse wheel only when mouse is over - if (!isOverPanel) { return; } - - var e = e || window.event; - - var delta = 0; - if (e.wheelDelta) { delta = -e.wheelDelta/120; } - if (e.detail) { delta = e.detail / 3; } - - var target = e.target || e.srcTarget || e.srcElement; - if ($(target).closest('.' + o.wrapperClass).is(me.parent())) { - // scroll content - scrollContent(delta, true); - } - - // stop window scroll - if (e.preventDefault && !releaseScroll) { e.preventDefault(); } - if (!releaseScroll) { e.returnValue = false; } - } - - function scrollContent(y, isWheel, isJump) - { - releaseScroll = false; - var delta = y; - var maxTop = me.outerHeight() - bar.outerHeight(); - - if (isWheel) - { - // move bar with mouse wheel - delta = parseInt(bar.css('top')) + y * parseInt(o.wheelStep) / 100 * bar.outerHeight(); - - // move bar, make sure it doesn't go out - delta = Math.min(Math.max(delta, 0), maxTop); - - // if scrolling down, make sure a fractional change to the - // scroll position isn't rounded away when the scrollbar's CSS is set - // this flooring of delta would happened automatically when - // bar.css is set below, but we floor here for clarity - delta = (y > 0) ? Math.ceil(delta) : Math.floor(delta); - - // scroll the scrollbar - bar.css({ top: delta + 'px' }); - } - - // calculate actual scroll amount - percentScroll = parseInt(bar.css('top')) / (me.outerHeight() - bar.outerHeight()); - delta = percentScroll * (me[0].scrollHeight - me.outerHeight()); - - if (isJump) - { - delta = y; - var offsetTop = delta / me[0].scrollHeight * me.outerHeight(); - offsetTop = Math.min(Math.max(offsetTop, 0), maxTop); - bar.css({ top: offsetTop + 'px' }); - } - - // scroll content - me.scrollTop(delta); - - // fire scrolling event - me.trigger('slimscrolling', ~~delta); - - // ensure bar is visible - showBar(); - - // trigger hide when scroll is stopped - hideBar(); - } - - function attachWheel() - { - if (window.addEventListener) - { - this.addEventListener('DOMMouseScroll', _onWheel, false ); - this.addEventListener('mousewheel', _onWheel, false ); - this.addEventListener('MozMousePixelScroll', _onWheel, false ); - } - else - { - document.attachEvent("onmousewheel", _onWheel) - } - } - - function getBarHeight() - { - // calculate scrollbar height and make sure it is not too small - barHeight = Math.max((me.outerHeight() / me[0].scrollHeight) * me.outerHeight(), minBarHeight); - bar.css({ height: barHeight + 'px' }); - - // hide scrollbar if content is not long enough - var display = barHeight == me.outerHeight() ? 'none' : 'block'; - bar.css({ display: display }); - } - - function showBar() - { - // recalculate bar height - getBarHeight(); - clearTimeout(queueHide); - - // when bar reached top or bottom - if (percentScroll == ~~percentScroll) - { - //release wheel - releaseScroll = o.allowPageScroll; - - // publish approporiate event - if (lastScroll != percentScroll) - { - var msg = (~~percentScroll == 0) ? 'top' : 'bottom'; - me.trigger('slimscroll', msg); - } - } - else - { - releaseScroll = false; - } - lastScroll = percentScroll; - - // show only when required - if(barHeight >= me.outerHeight()) { - //allow window scroll - releaseScroll = true; - return; - } - bar.stop(true,true).fadeIn('fast'); - if (o.railVisible) { rail.stop(true,true).fadeIn('fast'); } - } - - function hideBar() - { - // only hide when options allow it - if (!o.alwaysVisible) - { - queueHide = setTimeout(function(){ - if (!(o.disableFadeOut && isOverPanel) && !isOverBar && !isDragg) - { - bar.fadeOut('slow'); - rail.fadeOut('slow'); - } - }, 1000); - } - } - - }); - - // maintain chainability - return this; - } - }); - - jQuery.fn.extend({ - slimscroll: jQuery.fn.slimScroll - }); - -})(jQuery); diff --git a/apps/static/js/plugins/slimscroll/jquery.slimscroll.min.js b/apps/static/js/plugins/slimscroll/jquery.slimscroll.min.js deleted file mode 100644 index 26220d6b2..000000000 --- a/apps/static/js/plugins/slimscroll/jquery.slimscroll.min.js +++ /dev/null @@ -1,16 +0,0 @@ -/*! Copyright (c) 2011 Piotr Rochala (http://rocha.la) - * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) - * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. - * - * Version: 1.3.0 - * - */ -(function(f){jQuery.fn.extend({slimScroll:function(h){var a=f.extend({width:"auto",height:"250px",size:"7px",color:"#000",position:"right",distance:"1px",start:"top",opacity:0.4,alwaysVisible:!1,disableFadeOut:!1,railVisible:!1,railColor:"#333",railOpacity:0.2,railDraggable:!0,railClass:"slimScrollRail",barClass:"slimScrollBar",wrapperClass:"slimScrollDiv",allowPageScroll:!1,wheelStep:20,touchScrollStep:200,borderRadius:"7px",railBorderRadius:"7px"},h);this.each(function(){function r(d){if(s){d=d|| -window.event;var c=0;d.wheelDelta&&(c=-d.wheelDelta/120);d.detail&&(c=d.detail/3);f(d.target||d.srcTarget||d.srcElement).closest("."+a.wrapperClass).is(b.parent())&&m(c,!0);d.preventDefault&&!k&&d.preventDefault();k||(d.returnValue=!1)}}function m(d,f,h){k=!1;var e=d,g=b.outerHeight()-c.outerHeight();f&&(e=parseInt(c.css("top"))+d*parseInt(a.wheelStep)/100*c.outerHeight(),e=Math.min(Math.max(e,0),g),e=0=b.outerHeight()?k=!0:(c.stop(!0,!0).fadeIn("fast"),a.railVisible&&g.stop(!0,!0).fadeIn("fast"))}function p(){a.alwaysVisible||(A=setTimeout(function(){a.disableFadeOut&&s||(x||y)||(c.fadeOut("slow"),g.fadeOut("slow"))},1E3))}var s,x,y,A,z,u,l,B,D=30,k=!1,b=f(this);if(b.parent().hasClass(a.wrapperClass)){var n=b.scrollTop(), -c=b.parent().find("."+a.barClass),g=b.parent().find("."+a.railClass);w();if(f.isPlainObject(h)){if("height"in h&&"auto"==h.height){b.parent().css("height","auto");b.css("height","auto");var q=b.parent().parent().height();b.parent().css("height",q);b.css("height",q)}if("scrollTo"in h)n=parseInt(a.scrollTo);else if("scrollBy"in h)n+=parseInt(a.scrollBy);else if("destroy"in h){c.remove();g.remove();b.unwrap();return}m(n,!1,!0)}}else{a.height="auto"==a.height?b.parent().height():a.height;n=f("
").addClass(a.wrapperClass).css({position:"relative", -overflow:"hidden",width:a.width,height:a.height});b.css({overflow:"hidden",width:a.width,height:a.height});var g=f("
").addClass(a.railClass).css({width:a.size,height:"100%",position:"absolute",top:0,display:a.alwaysVisible&&a.railVisible?"block":"none","border-radius":a.railBorderRadius,background:a.railColor,opacity:a.railOpacity,zIndex:90}),c=f("
").addClass(a.barClass).css({background:a.color,width:a.size,position:"absolute",top:0,opacity:a.opacity,display:a.alwaysVisible? -"block":"none","border-radius":a.borderRadius,BorderRadius:a.borderRadius,MozBorderRadius:a.borderRadius,WebkitBorderRadius:a.borderRadius,zIndex:99}),q="right"==a.position?{right:a.distance}:{left:a.distance};g.css(q);c.css(q);b.wrap(n);b.parent().append(c);b.parent().append(g);a.railDraggable&&c.bind("mousedown",function(a){var b=f(document);y=!0;t=parseFloat(c.css("top"));pageY=a.pageY;b.bind("mousemove.slimscroll",function(a){currTop=t+a.pageY-pageY;c.css("top",currTop);m(0,c.position().top,!1)}); -b.bind("mouseup.slimscroll",function(a){y=!1;p();b.unbind(".slimscroll")});return!1}).bind("selectstart.slimscroll",function(a){a.stopPropagation();a.preventDefault();return!1});g.hover(function(){v()},function(){p()});c.hover(function(){x=!0},function(){x=!1});b.hover(function(){s=!0;v();p()},function(){s=!1;p()});b.bind("touchstart",function(a,b){a.originalEvent.touches.length&&(z=a.originalEvent.touches[0].pageY)});b.bind("touchmove",function(b){k||b.originalEvent.preventDefault();b.originalEvent.touches.length&& -(m((z-b.originalEvent.touches[0].pageY)/a.touchScrollStep,!0),z=b.originalEvent.touches[0].pageY)});w();"bottom"===a.start?(c.css({top:b.outerHeight()-c.outerHeight()}),m(0,!0)):"top"!==a.start&&(m(f(a.start).position().top,null,!0),a.alwaysVisible||c.hide());C()}});return this}});jQuery.fn.extend({slimscroll:jQuery.fn.slimScroll})})(jQuery); \ No newline at end of file diff --git a/apps/templates/_left_side_bar.html b/apps/templates/_left_side_bar.html deleted file mode 100644 index 572375ed8..000000000 --- a/apps/templates/_left_side_bar.html +++ /dev/null @@ -1,12 +0,0 @@ - diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index 12c59d209..e69de29bb 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -1,195 +0,0 @@ -{% load i18n %} - -{# Index #} -{% if request.user.can_admin_or_audit_current_org %} -
  • - - {% trans 'Dashboard' %} - - -
  • -{% endif %} - -{# Users #} -{% if request.user.can_admin_current_org %} -
  • - - {% trans 'Users' %} - - -
  • -{% endif %} - -{# User info #} -{% if not request.user.can_admin_current_org and request.user.can_audit_current_org %} -
  • - - {% trans 'Profile' %} - -
  • -{% endif %} - -{# Assets #} -{% if request.user.can_admin_current_org %} -
  • - - {% trans 'Assets' %} - - -
  • -{% endif %} - - -{# Applications #} -{% if request.user.can_admin_current_org %} -
  • - - {% trans 'Applications' %} - - -
  • -{% endif %} - - -{# Perms #} -{% if request.user.can_admin_current_org %} -
  • - {% trans 'Perms' %} - -
  • -{% endif %} - - -{# Terminal #} -{% if request.user.can_admin_or_audit_current_org %} -
  • - - {% trans 'Sessions' %} - - -
  • -{% endif %} - - -{# Ops #} -{% if request.user.can_admin_current_org %} -
  • - - {% trans 'Job Center' %} - - -
  • -{% endif %} - -{% if request.user.can_admin_current_org and LICENSE_VALID %} -
  • - - - {% trans 'Tickets' %} - -
  • -{% endif %} - -{# Audits #} -{% if request.user.can_admin_or_audit_current_org %} -
  • - - {% trans 'Audits' %} - - -
  • -{% endif %} - - -{# X-Pack #} -{% if request.user.can_admin_current_org and XPACK_PLUGINS %} -
  • - - {% trans 'XPack' %} - - -
  • -{% endif %} - -{# Settings #} -{% if request.user.is_superuser %} -
  • - - {% trans 'Settings' %} - -
  • -{% endif %} - - diff --git a/apps/templates/_nav_user.html b/apps/templates/_nav_user.html index 7e36f3dcc..e69de29bb 100644 --- a/apps/templates/_nav_user.html +++ b/apps/templates/_nav_user.html @@ -1,49 +0,0 @@ -{% load i18n %} -
  • - - {% trans 'My assets' %} - -
  • - -
  • - - {% trans 'My applications' %} - - -
  • - -{% if SECURITY_COMMAND_EXECUTION %} -
  • - - {% trans 'Command execution' %} - -
  • -{% endif %} -
  • - - {% trans 'Profile' %} - -
  • -
  • - - {% trans 'Web terminal' %} - -
  • -
  • - - {% trans 'File manager' %} - -
  • diff --git a/apps/templates/_user_profile.html b/apps/templates/_user_profile.html deleted file mode 100644 index 3e72f5b8e..000000000 --- a/apps/templates/_user_profile.html +++ /dev/null @@ -1,63 +0,0 @@ -{% load static %} -{% load i18n %} - - diff --git a/apps/templates/base.html b/apps/templates/base.html index e400a71f8..ce3e453b1 100644 --- a/apps/templates/base.html +++ b/apps/templates/base.html @@ -13,7 +13,6 @@
    - {% include '_left_side_bar.html' %}
    {% include '_header_bar.html' %}