diff --git a/apps/assets/api/label.py b/apps/assets/api/label.py
index eb8594e4a..d3537b20c 100644
--- a/apps/assets/api/label.py
+++ b/apps/assets/api/label.py
@@ -13,11 +13,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from rest_framework_bulk import BulkModelViewSet
from rest_framework.pagination import LimitOffsetPagination
from django.db.models import Count
from common.utils import get_logger
+from orgs.mixins import OrgBulkModelViewSet
from ..hands import IsOrgAdmin
from ..models import Label
from .. import serializers
@@ -27,7 +27,7 @@ logger = get_logger(__file__)
__all__ = ['LabelViewSet']
-class LabelViewSet(BulkModelViewSet):
+class LabelViewSet(OrgBulkModelViewSet):
filter_fields = ("name", "value")
search_fields = filter_fields
permission_classes = (IsOrgAdmin,)
diff --git a/apps/assets/migrations/0035_auto_20190711_2018.py b/apps/assets/migrations/0035_auto_20190711_2018.py
new file mode 100644
index 000000000..9dcbad1db
--- /dev/null
+++ b/apps/assets/migrations/0035_auto_20190711_2018.py
@@ -0,0 +1,34 @@
+# Generated by Django 2.1.7 on 2019-07-11 12:18
+
+import common.fields.model
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('assets', '0034_auto_20190705_1348'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='adminuser',
+ name='private_key',
+ field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
+ ),
+ migrations.AlterField(
+ model_name='authbook',
+ name='private_key',
+ field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
+ ),
+ migrations.AlterField(
+ model_name='gateway',
+ name='private_key',
+ field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
+ ),
+ migrations.AlterField(
+ model_name='systemuser',
+ name='private_key',
+ field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
+ ),
+ ]
diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py
index b372bc9fd..75f81917f 100644
--- a/apps/assets/models/base.py
+++ b/apps/assets/models/base.py
@@ -28,7 +28,7 @@ class AssetUser(OrgModelMixin):
name = models.CharField(max_length=128, verbose_name=_('Name'))
username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric])
password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
- private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])
+ private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'))
public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
diff --git a/apps/assets/serializers/admin_user.py b/apps/assets/serializers/admin_user.py
index b8295457f..0efc09801 100644
--- a/apps/assets/serializers/admin_user.py
+++ b/apps/assets/serializers/admin_user.py
@@ -21,17 +21,14 @@ class AdminUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
model = AdminUser
fields = [
'id', 'name', 'username', 'password', 'private_key', 'public_key',
- 'comment', 'assets_amount',
- 'date_created', 'date_updated', 'created_by',
+ 'comment', 'assets_amount', 'date_created', 'date_updated', 'created_by',
]
+ read_only_fields = ['date_created', 'date_updated', 'created_by', 'assets_amount']
extra_kwargs = {
'password': {"write_only": True},
'private_key': {"write_only": True},
'public_key': {"write_only": True},
- 'date_created': {'read_only': True},
- 'date_updated': {'read_only': True},
- 'created_by': {'read_only': True},
'assets_amount': {'label': _('Asset')},
}
diff --git a/apps/assets/templates/assets/_asset_user_auth_view_modal.html b/apps/assets/templates/assets/_asset_user_auth_view_modal.html
index 6fbd48fcd..417e1021d 100644
--- a/apps/assets/templates/assets/_asset_user_auth_view_modal.html
+++ b/apps/assets/templates/assets/_asset_user_auth_view_modal.html
@@ -70,7 +70,7 @@ function showAuth() {
var msg = "{% trans 'Get auth info error' %}";
toastr.error(msg)
};
- APIUpdateAttr({
+ requestApi({
url: url,
method: "GET",
success: success,
diff --git a/apps/assets/templates/assets/_asset_user_list.html b/apps/assets/templates/assets/_asset_user_list.html
index 381aec13d..f76754391 100644
--- a/apps/assets/templates/assets/_asset_user_list.html
+++ b/apps/assets/templates/assets/_asset_user_list.html
@@ -141,7 +141,7 @@ $(document).ready(function(){
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
method: 'GET',
success: success,
diff --git a/apps/assets/templates/assets/_node_tree.html b/apps/assets/templates/assets/_node_tree.html
index 61737184c..9a4004060 100644
--- a/apps/assets/templates/assets/_node_tree.html
+++ b/apps/assets/templates/assets/_node_tree.html
@@ -235,7 +235,7 @@ function onRename(event, treeId, treeNode, isCancel){
if (isCancel){
return
}
- APIUpdateAttr({
+ requestApi({
url: url,
body: JSON.stringify(data),
method: "PATCH",
@@ -274,7 +274,7 @@ function onDrop(event, treeId, treeNodes, targetNode, moveType) {
var the_url = "{% url 'api-assets:node-add-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", targetNode.meta.node.id);
var body = {nodes: treeNodesIds};
- APIUpdateAttr({
+ requestApi({
url: the_url,
method: "PUT",
body: JSON.stringify(body)
diff --git a/apps/assets/templates/assets/admin_user_assets.html b/apps/assets/templates/assets/admin_user_assets.html
index 7c97259ab..9ca433930 100644
--- a/apps/assets/templates/assets/admin_user_assets.html
+++ b/apps/assets/templates/assets/admin_user_assets.html
@@ -88,7 +88,7 @@ $(document).ready(function () {
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
method: 'GET',
success: success,
diff --git a/apps/assets/templates/assets/admin_user_detail.html b/apps/assets/templates/assets/admin_user_detail.html
index f00e2352a..9e3365509 100644
--- a/apps/assets/templates/assets/admin_user_detail.html
+++ b/apps/assets/templates/assets/admin_user_detail.html
@@ -131,7 +131,7 @@ function replaceNodeAssetsAdminUser(nodes) {
// clear jumpserver.groups_selected
jumpserver.nodes_selected = {};
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
diff --git a/apps/assets/templates/assets/asset_asset_user_list.html b/apps/assets/templates/assets/asset_asset_user_list.html
index 39e4816b9..bf3cba583 100644
--- a/apps/assets/templates/assets/asset_asset_user_list.html
+++ b/apps/assets/templates/assets/asset_asset_user_list.html
@@ -84,7 +84,7 @@ $(document).ready(function () {
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
method: 'GET',
success: success,
diff --git a/apps/assets/templates/assets/asset_detail.html b/apps/assets/templates/assets/asset_detail.html
index ad635ca5b..760cc7e6d 100644
--- a/apps/assets/templates/assets/asset_detail.html
+++ b/apps/assets/templates/assets/asset_detail.html
@@ -70,7 +70,7 @@
{% trans 'Protocol' %} |
- {{ asset.protocols }} |
+ {{ asset.protocols }} |
{% trans 'Admin user' %}: |
@@ -267,7 +267,7 @@ function updateAssetNodes(nodes) {
// clear jumpserver.groups_selected
jumpserver.nodes_selected = {};
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
@@ -282,7 +282,7 @@ function refreshAssetHardware() {
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600')
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
success: success,
method: 'GET'
@@ -306,7 +306,7 @@ $(document).ready(function () {
};
var success = '{% trans "Update successfully!" %}';
var status = $(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").text();
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(body),
success_message: success
@@ -360,7 +360,7 @@ $(document).ready(function () {
window.open(url, '', 'width=800,height=600')
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
method: 'GET',
success: success
diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html
index 8f889feb7..0cdcb176a 100644
--- a/apps/assets/templates/assets/asset_list.html
+++ b/apps/assets/templates/assets/asset_list.html
@@ -360,7 +360,7 @@ $(document).ready(function(){
setTimeout( function () {
window.location.reload();}, 500);
}
- APIUpdateAttr({
+ requestApi({
url: the_url,
method: 'PATCH',
body: JSON.stringify(data),
@@ -377,7 +377,7 @@ $(document).ready(function(){
setTimeout( function () {
window.location.reload();}, 300);
}
- APIUpdateAttr({
+ requestApi({
url: the_url,
method: 'PATCH',
body: JSON.stringify(data),
@@ -397,7 +397,7 @@ $(document).ready(function(){
},function () {
function success(data) {
url = setUrlParam(the_url, 'spm', data.spm);
- APIUpdateAttr({
+ requestApi({
url:url,
method:'DELETE',
success:refreshTag,
@@ -410,7 +410,7 @@ $(document).ready(function(){
var msg = "{% trans 'Asset Deleting failed.' %}";
swal("{% trans 'Asset Delete' %}", msg, "error");
}
- APIUpdateAttr({
+ requestApi({
url: "{% url 'api-common:resources-cache' %}",
method:'POST',
body:JSON.stringify(data),
@@ -428,7 +428,7 @@ $(document).ready(function(){
var url = "{% url 'assets:asset-bulk-update' %}";
location.href= setUrlParam(url, 'spm', data.spm);
}
- APIUpdateAttr({
+ requestApi({
url: "{% url 'api-common:resources-cache' %}",
method:'POST',
body:JSON.stringify(data),
@@ -452,7 +452,7 @@ $(document).ready(function(){
asset_table.ajax.reload()
};
- APIUpdateAttr({
+ requestApi({
'url': '/api/assets/v1/nodes/' + current_node_id + '/assets/remove/',
'method': 'PUT',
'body': JSON.stringify(data),
@@ -500,7 +500,7 @@ $(document).ready(function(){
url = "{% url 'api-assets:node-add-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node_id);
}
- APIUpdateAttr({
+ requestApi({
'url': url,
'method': 'PUT',
'body': JSON.stringify(data),
@@ -524,7 +524,7 @@ $(document).ready(function(){
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600')
}
- APIUpdateAttr({
+ requestApi({
url: the_url,
method: "GET",
success: success,
@@ -539,7 +539,7 @@ $(document).ready(function(){
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600')
}
- APIUpdateAttr({
+ requestApi({
url: the_url,
method: "GET",
success: success,
diff --git a/apps/assets/templates/assets/cmd_filter_detail.html b/apps/assets/templates/assets/cmd_filter_detail.html
index ee68ff2f6..b98828f4e 100644
--- a/apps/assets/templates/assets/cmd_filter_detail.html
+++ b/apps/assets/templates/assets/cmd_filter_detail.html
@@ -136,7 +136,7 @@ function updateCMDFilterSystemUsers(system_users) {
var success = function(data) {
location.reload();
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(body),
method: 'PATCH',
diff --git a/apps/assets/templates/assets/domain_gateway_list.html b/apps/assets/templates/assets/domain_gateway_list.html
index d621fb0ec..eb348141e 100644
--- a/apps/assets/templates/assets/domain_gateway_list.html
+++ b/apps/assets/templates/assets/domain_gateway_list.html
@@ -134,7 +134,7 @@ $(document).ready(function(){
var data = $("#test_gateway_form").serializeObject();
var uid = data.gateway_id;
var the_url = '{% url "api-assets:test-gateway-connective" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
- APIUpdateAttr({
+ requestApi({
url: the_url,
method: "POST",
body: JSON.stringify({'port': parseInt(data.port)}),
diff --git a/apps/assets/templates/assets/system_user_assets.html b/apps/assets/templates/assets/system_user_assets.html
index 546111130..5818e4ce6 100644
--- a/apps/assets/templates/assets/system_user_assets.html
+++ b/apps/assets/templates/assets/system_user_assets.html
@@ -146,7 +146,7 @@ function updateSystemUserNode(nodes) {
// clear jumpserver.nodes_selected
jumpserver.nodes_selected = {};
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
@@ -206,7 +206,7 @@ $(document).ready(function () {
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
error: error,
method: 'GET',
@@ -226,7 +226,7 @@ $(document).ready(function () {
var error = function (data) {
alert(data)
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
method: 'GET',
success: success,
@@ -243,7 +243,7 @@ $(document).ready(function () {
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
error: error,
method: 'GET',
diff --git a/apps/assets/templates/assets/system_user_detail.html b/apps/assets/templates/assets/system_user_detail.html
index 55f625d81..d9ff1a641 100644
--- a/apps/assets/templates/assets/system_user_detail.html
+++ b/apps/assets/templates/assets/system_user_detail.html
@@ -212,7 +212,7 @@ function updateCommandFilters(command_filters) {
var success = function(data) {
location.reload();
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
@@ -235,7 +235,7 @@ $(document).ready(function () {
var body = {
'auto_push': checked
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(body)
});
@@ -254,7 +254,7 @@ $(document).ready(function () {
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
method: 'GET',
success: success,
@@ -268,7 +268,7 @@ $(document).ready(function () {
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600')
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
method: 'GET',
success: success,
diff --git a/apps/assets/templates/assets/system_user_list.html b/apps/assets/templates/assets/system_user_list.html
index 6c5ff3339..621e18201 100644
--- a/apps/assets/templates/assets/system_user_list.html
+++ b/apps/assets/templates/assets/system_user_list.html
@@ -182,7 +182,7 @@ $(document).ready(function(){
swal("{% trans 'System Users Delete' %}", msg, "error");
};
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
- APIUpdateAttr({url: url_delete, method: 'DELETE', success: success, error: fail});
+ requestApi({url: url_delete, method: 'DELETE', success: success, error: fail});
$data_table.ajax.reload();
jumpserver.checked = false;
});
diff --git a/apps/assets/templates/assets/user_asset_list.html b/apps/assets/templates/assets/user_asset_list.html
index f441baa39..d021df0d7 100644
--- a/apps/assets/templates/assets/user_asset_list.html
+++ b/apps/assets/templates/assets/user_asset_list.html
@@ -11,47 +11,7 @@
{% block content %}
-
-
+ {% include 'users/_granted_assets.html' %}
@@ -62,121 +22,51 @@
{% block custom_foot_js %}
-
{% endblock %}
\ No newline at end of file
diff --git a/apps/assets/utils.py b/apps/assets/utils.py
index a0de3b481..be8a80351 100644
--- a/apps/assets/utils.py
+++ b/apps/assets/utils.py
@@ -1,7 +1,8 @@
# ~*~ coding: utf-8 ~*~
#
import time
-from django.db.models import Prefetch
+from functools import reduce
+from django.db.models import Prefetch, Q
from common.utils import get_object_or_none, get_logger
from common.struct import Stack
@@ -21,24 +22,34 @@ def get_system_user_by_id(id):
return system_user
-class LabelFilter:
- def filter_queryset(self, queryset):
- queryset = super().filter_queryset(queryset)
- query_keys = self.request.query_params.keys()
+class LabelFilterMixin:
+ def get_filter_labels_ids(self):
+ query_params = self.request.query_params
+ query_keys = query_params.keys()
all_label_keys = Label.objects.values_list('name', flat=True)
valid_keys = set(all_label_keys) & set(query_keys)
- labels_query = {}
- for key in valid_keys:
- labels_query[key] = self.request.query_params.get(key)
- conditions = []
- for k, v in labels_query.items():
- query = {'labels__name': k, 'labels__value': v}
- conditions.append(query)
+ if not valid_keys:
+ return []
- if conditions:
- for kwargs in conditions:
- queryset = queryset.filter(**kwargs)
+ labels_query = [
+ {"name": key, "value": query_params[key]}
+ for key in valid_keys
+ ]
+ args = [Q(**kwargs) for kwargs in labels_query]
+ args = reduce(lambda x, y: x | y, args)
+ labels_id = Label.objects.filter(args).values_list('id', flat=True)
+ return labels_id
+
+
+class LabelFilter(LabelFilterMixin):
+ def filter_queryset(self, queryset):
+ queryset = super().filter_queryset(queryset)
+ labels_ids = self.get_filter_labels_ids()
+ if not labels_ids:
+ return queryset
+ for labels_id in labels_ids:
+ queryset = queryset.filter(labels=labels_id)
return queryset
diff --git a/apps/assets/views/asset.py b/apps/assets/views/asset.py
index ce701b129..40601d27f 100644
--- a/apps/assets/views/asset.py
+++ b/apps/assets/views/asset.py
@@ -69,7 +69,7 @@ class UserAssetListView(PermissionsMixin, TemplateView):
context = {
'action': _('My assets'),
'labels': Label.objects.all().order_by('name'),
- 'system_users': SystemUser.objects.all(),
+ 'show_actions': True
}
kwargs.update(context)
return super().get_context_data(**kwargs)
diff --git a/apps/authentication/templates/authentication/_mfa_confirm_modal.html b/apps/authentication/templates/authentication/_mfa_confirm_modal.html
index 0d7b794bb..60512d7de 100644
--- a/apps/authentication/templates/authentication/_mfa_confirm_modal.html
+++ b/apps/authentication/templates/authentication/_mfa_confirm_modal.html
@@ -38,7 +38,7 @@ $(document).ready(function () {
var error = function () {
$("#mfa_error").addClass("text-danger").html(codeError);
};
- APIUpdateAttr({
+ requestApi({
url: url,
method: "POST",
body: JSON.stringify(data),
diff --git a/apps/common/permissions.py b/apps/common/permissions.py
index edb5ee4d0..bdc25fe21 100644
--- a/apps/common/permissions.py
+++ b/apps/common/permissions.py
@@ -145,13 +145,13 @@ class NeedMFAVerify(permissions.BasePermission):
return False
-class CanUpdateSuperUser(permissions.BasePermission):
+class CanUpdateDeleteSuperUser(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in ['GET', 'OPTIONS']:
return True
- if str(request.user.id) == str(obj.id):
+ elif request.method == 'DELETE' and str(request.user.id) == str(obj.id):
return False
- if request.user.is_superuser:
+ elif request.user.is_superuser:
return True
if hasattr(obj, 'is_superuser') and obj.is_superuser:
return False
diff --git a/apps/ops/templates/ops/command_execution_create.html b/apps/ops/templates/ops/command_execution_create.html
index 3e4436e41..17d2c044a 100644
--- a/apps/ops/templates/ops/command_execution_create.html
+++ b/apps/ops/templates/ops/command_execution_create.html
@@ -255,7 +255,7 @@ function execute() {
}
}
- APIUpdateAttr({
+ requestApi({
url: url,
body: JSON.stringify(data),
method: 'POST',
diff --git a/apps/ops/templates/ops/task_list.html b/apps/ops/templates/ops/task_list.html
index 0426e059d..ae8c45043 100644
--- a/apps/ops/templates/ops/task_list.html
+++ b/apps/ops/templates/ops/task_list.html
@@ -109,7 +109,7 @@ $(document).ready(function() {
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
error: error,
method: 'GET',
diff --git a/apps/perms/api/mixin.py b/apps/perms/api/mixin.py
new file mode 100644
index 000000000..24bd9abd2
--- /dev/null
+++ b/apps/perms/api/mixin.py
@@ -0,0 +1,225 @@
+# -*- coding: utf-8 -*-
+#
+from functools import reduce
+from hashlib import md5
+from django.core.cache import cache
+from django.db.models import Q
+from django.conf import settings
+from rest_framework.views import Response
+
+from django.utils.translation import ugettext as _
+from common.utils import get_logger
+from assets.utils import LabelFilterMixin
+from ..utils import (
+ AssetPermissionUtil
+)
+from .. import const
+from ..hands import Asset, Node, SystemUser, Label
+from .. import serializers
+
+logger = get_logger(__name__)
+
+__all__ = ['UserPermissionCacheMixin', 'GrantAssetsMixin', 'NodesWithUngroupMixin']
+
+
+class UserPermissionCacheMixin:
+ cache_policy = '0'
+ RESP_CACHE_KEY = '_PERMISSION_RESPONSE_CACHE_V2_{}'
+ CACHE_TIME = settings.ASSETS_PERM_CACHE_TIME
+ _object = None
+
+ def get_object(self):
+ return None
+
+ # 内部使用可控制缓存
+ def _get_object(self):
+ if not self._object:
+ self._object = self.get_object()
+ return self._object
+
+ def get_object_id(self):
+ obj = self._get_object()
+ if obj:
+ return str(obj.id)
+ return None
+
+ def get_request_md5(self):
+ path = self.request.path
+ query = {k: v for k, v in self.request.GET.items()}
+ query.pop("_", None)
+ query = "&".join(["{}={}".format(k, v) for k, v in query.items()])
+ full_path = "{}?{}".format(path, query)
+ return md5(full_path.encode()).hexdigest()
+
+ def get_meta_cache_id(self):
+ obj = self._get_object()
+ util = AssetPermissionUtil(obj, cache_policy=self.cache_policy)
+ meta_cache_id = util.cache_meta.get('id')
+ return meta_cache_id
+
+ def get_response_cache_id(self):
+ obj_id = self.get_object_id()
+ request_md5 = self.get_request_md5()
+ meta_cache_id = self.get_meta_cache_id()
+ resp_cache_id = '{}_{}_{}'.format(obj_id, request_md5, meta_cache_id)
+ return resp_cache_id
+
+ def get_response_from_cache(self):
+ # 没有数据缓冲
+ meta_cache_id = self.get_meta_cache_id()
+ if not meta_cache_id:
+ logger.debug("Not get meta id: {}".format(meta_cache_id))
+ return None
+ # 从响应缓冲里获取响应
+ key = self.get_response_key()
+ data = cache.get(key)
+ if not data:
+ logger.debug("Not get response from cache: {}".format(key))
+ return None
+ logger.debug("Get user permission from cache: {}".format(self.get_object()))
+ response = Response(data)
+ return response
+
+ def expire_response_cache(self):
+ obj_id = self.get_object_id()
+ expire_cache_id = '{}_{}'.format(obj_id, '*')
+ key = self.RESP_CACHE_KEY.format(expire_cache_id)
+ cache.delete_pattern(key)
+
+ def get_response_key(self):
+ resp_cache_id = self.get_response_cache_id()
+ key = self.RESP_CACHE_KEY.format(resp_cache_id)
+ return key
+
+ def set_response_to_cache(self, response):
+ key = self.get_response_key()
+ cache.set(key, response.data, self.CACHE_TIME)
+ logger.debug("Set response to cache: {}".format(key))
+
+ def get(self, request, *args, **kwargs):
+ self.cache_policy = request.GET.get('cache_policy', '0')
+
+ obj = self._get_object()
+ if obj is None:
+ logger.debug("Not get response from cache: obj is none")
+ return super().get(request, *args, **kwargs)
+
+ if AssetPermissionUtil.is_not_using_cache(self.cache_policy):
+ logger.debug("Not get resp from cache: {}".format(self.cache_policy))
+ return super().get(request, *args, **kwargs)
+ elif AssetPermissionUtil.is_refresh_cache(self.cache_policy):
+ logger.debug("Not get resp from cache: {}".format(self.cache_policy))
+ self.expire_response_cache()
+
+ logger.debug("Try get response from cache")
+ resp = self.get_response_from_cache()
+ if not resp:
+ resp = super().get(request, *args, **kwargs)
+ self.set_response_to_cache(resp)
+ return resp
+
+
+class NodesWithUngroupMixin:
+ util = None
+
+ @staticmethod
+ def get_ungrouped_node(ungroup_key):
+ return Node(key=ungroup_key, id=const.UNGROUPED_NODE_ID,
+ value=_("ungrouped"))
+
+ @staticmethod
+ def get_empty_node():
+ return Node(key=const.EMPTY_NODE_KEY, id=const.EMPTY_NODE_ID,
+ value=_("empty"))
+
+ def add_ungrouped_nodes(self, node_map, node_keys):
+ ungroup_key = '1:-1'
+ for key in node_keys:
+ if key.endswith('-1'):
+ ungroup_key = key
+ break
+ ungroup_node = self.get_ungrouped_node(ungroup_key)
+ empty_node = self.get_empty_node()
+ node_map[ungroup_key] = ungroup_node
+ node_map[const.EMPTY_NODE_KEY] = empty_node
+
+
+class GrantAssetsMixin(LabelFilterMixin):
+ serializer_class = serializers.AssetGrantedSerializer
+
+ def get_serializer_queryset(self, queryset):
+ assets_ids = []
+ system_users_ids = set()
+ for asset in queryset:
+ assets_ids.append(asset["id"])
+ system_users_ids.update(set(asset["system_users"]))
+ assets = Asset.objects.filter(id__in=assets_ids).only(
+ *self.serializer_class.Meta.only_fields
+ )
+ assets_map = {asset.id: asset for asset in assets}
+ system_users = SystemUser.objects.filter(id__in=system_users_ids).only(
+ *self.serializer_class.system_users_only_fields
+ )
+ system_users_map = {s.id: s for s in system_users}
+ data = []
+ for item in queryset:
+ i = item["id"]
+ asset = assets_map.get(i)
+ if not asset:
+ continue
+
+ _system_users = item["system_users"]
+ system_users_granted = []
+ for sid, action in _system_users.items():
+ system_user = system_users_map.get(sid)
+ if not system_user:
+ continue
+ system_user.actions = action
+ system_users_granted.append(system_user)
+ asset.system_users_granted = system_users_granted
+ data.append(asset)
+ return data
+
+ def get_serializer(self, queryset_list, many=True):
+ data = self.get_serializer_queryset(queryset_list)
+ return super().get_serializer(data, many=True)
+
+ def search_queryset(self, assets_items):
+ search = self.request.query_params.get("search")
+ if not search:
+ return assets_items
+ assets_map = {asset['id']: asset for asset in assets_items}
+ assets_ids = set(assets_map.keys())
+ assets_ids_search = Asset.objects.filter(id__in=assets_ids).filter(
+ Q(hostname__icontains=search) | Q(ip__icontains=search)
+ ).values_list('id', flat=True)
+ return [assets_map.get(asset_id) for asset_id in assets_ids_search]
+
+ def filter_queryset_by_label(self, assets_items):
+ labels_id = self.get_filter_labels_ids()
+ if not labels_id:
+ return assets_items
+
+ assets_map = {asset['id']: asset for asset in assets_items}
+ assets_matched = Asset.objects.filter(id__in=assets_map.keys())
+ for label_id in labels_id:
+ assets_matched = assets_matched.filter(labels=label_id)
+ assets_ids_matched = assets_matched.values_list('id', flat=True)
+ return [assets_map.get(asset_id) for asset_id in assets_ids_matched]
+
+ def sort_queryset(self, assets_items):
+ order_by = self.request.query_params.get('order', 'hostname')
+
+ if order_by not in ['hostname', '-hostname', 'ip', '-ip']:
+ order_by = 'hostname'
+ assets_map = {asset['id']: asset for asset in assets_items}
+ assets_ids_search = Asset.objects.filter(id__in=assets_map.keys())\
+ .order_by(order_by)\
+ .values_list('id', flat=True)
+ return [assets_map.get(asset_id) for asset_id in assets_ids_search]
+
+ def filter_queryset(self, assets_items):
+ assets_items = self.search_queryset(assets_items)
+ assets_items = self.filter_queryset_by_label(assets_items)
+ assets_items = self.sort_queryset(assets_items)
+ return assets_items
\ No newline at end of file
diff --git a/apps/perms/api/user_group_permission.py b/apps/perms/api/user_group_permission.py
index 9e32055d4..f83330c58 100644
--- a/apps/perms/api/user_group_permission.py
+++ b/apps/perms/api/user_group_permission.py
@@ -2,23 +2,21 @@
#
from django.shortcuts import get_object_or_404
-from rest_framework.generics import (
- ListAPIView, get_object_or_404,
-)
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from ..hands import UserGroup
-from .. import serializers, const
+from .. import serializers
from .user_permission import (
UserGrantedAssetsApi, UserGrantedNodesApi, UserGrantedNodesWithAssetsApi,
UserGrantedNodesWithAssetsAsTreeApi, UserGrantedNodeAssetsApi,
+ UserGrantedNodesAsTreeApi,
)
__all__ = [
'UserGroupGrantedAssetsApi', 'UserGroupGrantedNodesApi',
'UserGroupGrantedNodesWithAssetsApi', 'UserGroupGrantedNodeAssetsApi',
- 'UserGroupGrantedNodesWithAssetsAsTreeApi',
+ 'UserGroupGrantedNodesWithAssetsAsTreeApi', 'UserGroupGrantedNodesAsTreeApi',
]
@@ -36,6 +34,13 @@ class UserGroupGrantedNodesApi(UserGrantedNodesApi):
return user_group
+class UserGroupGrantedNodesAsTreeApi(UserGrantedNodesAsTreeApi):
+ def get_object(self):
+ user_group_id = self.kwargs.get('pk', '')
+ user_group = get_object_or_404(UserGroup, id=user_group_id)
+ return user_group
+
+
class UserGroupGrantedNodesWithAssetsApi(UserGrantedNodesWithAssetsApi):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeGrantedSerializer
diff --git a/apps/perms/api/user_permission.py b/apps/perms/api/user_permission.py
index 8d55ee49c..0e67e2747 100644
--- a/apps/perms/api/user_permission.py
+++ b/apps/perms/api/user_permission.py
@@ -2,25 +2,23 @@
#
import time
import traceback
+from functools import reduce
import uuid
-from hashlib import md5
-from django.core.cache import cache
-from django.conf import settings
from django.db.models import Q
from django.shortcuts import get_object_or_404
from rest_framework.views import APIView, Response
from rest_framework.generics import (
ListAPIView, get_object_or_404, RetrieveAPIView
)
-from django.utils.translation import ugettext as _
from rest_framework.pagination import LimitOffsetPagination
from common.permissions import IsValidUser, IsOrgAdminOrAppUser
from common.tree import TreeNodeSerializer
-from common.utils import get_logger, get_object_or_none
+from common.utils import get_logger
from ..utils import (
AssetPermissionUtil, ParserNode,
)
+from .mixin import UserPermissionCacheMixin, GrantAssetsMixin, NodesWithUngroupMixin
from .. import const
from ..hands import User, Asset, Node, SystemUser, NodeSerializer
from .. import serializers
@@ -37,153 +35,6 @@ __all__ = [
]
-class UserPermissionCacheMixin:
- cache_policy = '0'
- RESP_CACHE_KEY = '_PERMISSION_RESPONSE_CACHE_V2_{}'
- CACHE_TIME = settings.ASSETS_PERM_CACHE_TIME
- _object = None
-
- def get_object(self):
- return None
-
- # 内部使用可控制缓存
- def _get_object(self):
- if not self._object:
- self._object = self.get_object()
- return self._object
-
- def get_object_id(self):
- obj = self._get_object()
- if obj:
- return str(obj.id)
- return None
-
- def get_request_md5(self):
- path = self.request.path
- query = {k: v for k, v in self.request.GET.items()}
- query.pop("_", None)
- query = "&".join(["{}={}".format(k, v) for k, v in query.items()])
- full_path = "{}?{}".format(path, query)
- return md5(full_path.encode()).hexdigest()
-
- def get_meta_cache_id(self):
- obj = self._get_object()
- util = AssetPermissionUtil(obj, cache_policy=self.cache_policy)
- meta_cache_id = util.cache_meta.get('id')
- return meta_cache_id
-
- def get_response_cache_id(self):
- obj_id = self.get_object_id()
- request_md5 = self.get_request_md5()
- meta_cache_id = self.get_meta_cache_id()
- resp_cache_id = '{}_{}_{}'.format(obj_id, request_md5, meta_cache_id)
- return resp_cache_id
-
- def get_response_from_cache(self):
- # 没有数据缓冲
- meta_cache_id = self.get_meta_cache_id()
- if not meta_cache_id:
- logger.debug("Not get meta id: {}".format(meta_cache_id))
- return None
- # 从响应缓冲里获取响应
- key = self.get_response_key()
- data = cache.get(key)
- if not data:
- logger.debug("Not get response from cache: {}".format(key))
- return None
- logger.debug("Get user permission from cache: {}".format(self.get_object()))
- response = Response(data)
- return response
-
- def expire_response_cache(self):
- obj_id = self.get_object_id()
- expire_cache_id = '{}_{}'.format(obj_id, '*')
- key = self.RESP_CACHE_KEY.format(expire_cache_id)
- cache.delete_pattern(key)
-
- def get_response_key(self):
- resp_cache_id = self.get_response_cache_id()
- key = self.RESP_CACHE_KEY.format(resp_cache_id)
- return key
-
- def set_response_to_cache(self, response):
- key = self.get_response_key()
- cache.set(key, response.data, self.CACHE_TIME)
- logger.debug("Set response to cache: {}".format(key))
-
- def get(self, request, *args, **kwargs):
- self.cache_policy = request.GET.get('cache_policy', '0')
-
- obj = self._get_object()
- if obj is None:
- logger.debug("Not get response from cache: obj is none")
- return super().get(request, *args, **kwargs)
-
- if AssetPermissionUtil.is_not_using_cache(self.cache_policy):
- logger.debug("Not get resp from cache: {}".format(self.cache_policy))
- return super().get(request, *args, **kwargs)
- elif AssetPermissionUtil.is_refresh_cache(self.cache_policy):
- logger.debug("Not get resp from cache: {}".format(self.cache_policy))
- self.expire_response_cache()
-
- logger.debug("Try get response from cache")
- resp = self.get_response_from_cache()
- if not resp:
- resp = super().get(request, *args, **kwargs)
- self.set_response_to_cache(resp)
- return resp
-
-
-class GrantAssetsMixin:
- serializer_class = serializers.AssetGrantedSerializer
-
- def get_serializer(self, queryset, many=True):
- assets_ids = []
- system_users_ids = set()
- for asset in queryset:
- assets_ids.append(asset["id"])
- system_users_ids.update(set(asset["system_users"]))
- assets = Asset.objects.filter(id__in=assets_ids).only(
- *self.serializer_class.Meta.only_fields
- )
- assets_map = {asset.id: asset for asset in assets}
- system_users = SystemUser.objects.filter(id__in=system_users_ids).only(
- *self.serializer_class.system_users_only_fields
- )
- system_users_map = {s.id: s for s in system_users}
- data = []
- for item in queryset:
- i = item["id"]
- asset = assets_map.get(i)
- if not asset:
- continue
-
- _system_users = item["system_users"]
- system_users_granted = []
- for sid, action in _system_users.items():
- system_user = system_users_map.get(sid)
- if not system_user:
- continue
- system_user.actions = action
- system_users_granted.append(system_user)
- asset.system_users_granted = system_users_granted
- data.append(asset)
- return super().get_serializer(data, many=True)
-
- def search_queryset(self, assets):
- search = self.request.query_params.get("search")
- if not search:
- return assets
-
- assets_map = {asset['id']: asset for asset in assets}
- assets_ids = set(assets_map.keys())
- assets_ids_search = Asset.objects.filter(id__in=assets_ids).filter(
- Q(hostname__icontains=search) | Q(ip__icontains=search)
- ).values_list('id', flat=True)
- assets_ids &= set(assets_ids_search)
- return [assets_map.get(asset_id) for asset_id in assets_ids]
-
-
class UserGrantedAssetsApi(UserPermissionCacheMixin, GrantAssetsMixin, ListAPIView):
"""
用户授权的所有资产
@@ -203,7 +54,6 @@ class UserGrantedAssetsApi(UserPermissionCacheMixin, GrantAssetsMixin, ListAPIVi
user = self.get_object()
util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
queryset = util.get_assets()
- queryset = self.search_queryset(queryset)
return queryset
def get_permissions(self):
@@ -212,29 +62,52 @@ class UserGrantedAssetsApi(UserPermissionCacheMixin, GrantAssetsMixin, ListAPIVi
return super().get_permissions()
-class NodesWithUngroupMixin:
- util = None
+class UserGrantedNodeAssetsApi(UserPermissionCacheMixin, GrantAssetsMixin, ListAPIView):
+ """
+ 查询用户授权的节点下的资产的api, 与上面api不同的是,只返回某个节点下的资产
+ """
+ permission_classes = (IsOrgAdminOrAppUser,)
+ pagination_class = LimitOffsetPagination
- @staticmethod
- def get_ungrouped_node(ungroup_key):
- return Node(key=ungroup_key, id=const.UNGROUPED_NODE_ID,
- value=_("ungrouped"))
+ def get_object(self):
+ user_id = self.kwargs.get('pk', '')
- @staticmethod
- def get_empty_node():
- return Node(key=const.EMPTY_NODE_KEY, id=const.EMPTY_NODE_ID,
- value=_("empty"))
+ if user_id:
+ user = get_object_or_404(User, id=user_id)
+ else:
+ user = self.request.user
+ return user
- def add_ungrouped_nodes(self, node_map, node_keys):
- ungroup_key = '1:-1'
- for key in node_keys:
- if key.endswith('-1'):
- ungroup_key = key
+ def get_node_key(self):
+ node_id = self.kwargs.get('node_id')
+ if str(node_id) == const.UNGROUPED_NODE_ID:
+ key = self.util.tree.ungrouped_key
+ elif str(node_id) == const.EMPTY_NODE_ID:
+ key = const.EMPTY_NODE_KEY
+ else:
+ node = get_object_or_404(Node, id=node_id)
+ key = node.key
+ return key
+
+ def get_queryset(self):
+ user = self.get_object()
+ self.util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
+ key = self.get_node_key()
+ nodes_items = self.util.get_nodes_with_assets()
+ assets_system_users = {}
+ for item in nodes_items:
+ if item["key"] == key:
+ assets_system_users = item["assets"]
break
- ungroup_node = self.get_ungrouped_node(ungroup_key)
- empty_node = self.get_empty_node()
- node_map[ungroup_key] = ungroup_node
- node_map[const.EMPTY_NODE_KEY] = empty_node
+ assets = []
+ for asset_id, system_users in assets_system_users.items():
+ assets.append({"id": asset_id, "system_users": system_users})
+ return assets
+
+ def get_permissions(self):
+ if self.kwargs.get('pk') is None:
+ self.permission_classes = (IsValidUser,)
+ return super().get_permissions()
class UserGrantedNodesApi(UserPermissionCacheMixin, NodesWithUngroupMixin, ListAPIView):
@@ -435,55 +308,6 @@ class UserGrantedNodesWithAssetsAsTreeApi(UserGrantedNodesWithAssetsApi):
return self.serializer_class(queryset, many=True)
-class UserGrantedNodeAssetsApi(UserPermissionCacheMixin, GrantAssetsMixin, ListAPIView):
- """
- 查询用户授权的节点下的资产的api, 与上面api不同的是,只返回某个节点下的资产
- """
- permission_classes = (IsOrgAdminOrAppUser,)
- pagination_class = LimitOffsetPagination
-
- def get_object(self):
- user_id = self.kwargs.get('pk', '')
-
- if user_id:
- user = get_object_or_404(User, id=user_id)
- else:
- user = self.request.user
- return user
-
- def get_node_key(self):
- node_id = self.kwargs.get('node_id')
- if str(node_id) == const.UNGROUPED_NODE_ID:
- key = self.util.tree.ungrouped_key
- elif str(node_id) == const.EMPTY_NODE_ID:
- key = const.EMPTY_NODE_KEY
- else:
- node = get_object_or_404(Node, id=node_id)
- key = node.key
- return key
-
- def get_queryset(self):
- user = self.get_object()
- self.util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
- key = self.get_node_key()
- nodes_items = self.util.get_nodes_with_assets()
- assets_system_users = {}
- for item in nodes_items:
- if item["key"] == key:
- assets_system_users = item["assets"]
- break
- assets = []
- for asset_id, system_users in assets_system_users.items():
- assets.append({"id": asset_id, "system_users": system_users})
- assets = self.search_queryset(assets)
- return assets
-
- def get_permissions(self):
- if self.kwargs.get('pk') is None:
- self.permission_classes = (IsValidUser,)
- return super().get_permissions()
-
-
class ValidateUserAssetPermissionApi(UserPermissionCacheMixin, APIView):
permission_classes = (IsOrgAdminOrAppUser,)
@@ -522,16 +346,12 @@ class GetUserAssetPermissionActionsApi(UserPermissionCacheMixin, RetrieveAPIView
system_id = self.request.query_params.get('system_user_id', '')
user = get_object_or_404(User, id=user_id)
- asset = get_object_or_404(Asset, id=asset_id)
- su = get_object_or_404(SystemUser, id=system_id)
util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
- granted_assets = util.get_assets()
- granted_system_users = granted_assets.get(asset, {})
-
- _object = {}
- if su not in granted_system_users:
- _object['actions'] = 0
- else:
- _object['actions'] = granted_system_users[su]
- return _object
+ assets = util.get_assets()
+ actions = 0
+ for asset in assets:
+ if asset_id == asset["id"]:
+ actions = asset["system_users"].get(system_id, 0)
+ break
+ return {"actions": actions}
diff --git a/apps/perms/hands.py b/apps/perms/hands.py
index bbdc01e1e..aef0f4875 100644
--- a/apps/perms/hands.py
+++ b/apps/perms/hands.py
@@ -2,7 +2,7 @@
#
from users.models import User, UserGroup
-from assets.models import Asset, SystemUser, Node
+from assets.models import Asset, SystemUser, Node, Label
from assets.serializers import NodeSerializer
from applications.serializers import RemoteAppSerializer
from applications.models import RemoteApp
diff --git a/apps/perms/templates/perms/asset_permission_asset.html b/apps/perms/templates/perms/asset_permission_asset.html
index ea5b20e63..d8cd8ee96 100644
--- a/apps/perms/templates/perms/asset_permission_asset.html
+++ b/apps/perms/templates/perms/asset_permission_asset.html
@@ -145,7 +145,7 @@ function addAssets(assets) {
var success = function(data) {
location.reload();
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
@@ -160,7 +160,7 @@ function removeAssets(assets) {
var success = function(data) {
location.reload();
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
@@ -172,7 +172,7 @@ function updateNodes(nodes, success) {
var body = {
nodes: nodes
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
diff --git a/apps/perms/templates/perms/asset_permission_detail.html b/apps/perms/templates/perms/asset_permission_detail.html
index 9c38cfc29..f9c11cb67 100644
--- a/apps/perms/templates/perms/asset_permission_detail.html
+++ b/apps/perms/templates/perms/asset_permission_detail.html
@@ -187,7 +187,7 @@ function updateSystemUser(system_users) {
var body = {
system_users: Object.assign([], system_users)
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(body)
});
@@ -247,7 +247,7 @@ $(document).ready(function () {
var body = {
'is_active': checked
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(body),
});
diff --git a/apps/perms/templates/perms/asset_permission_user.html b/apps/perms/templates/perms/asset_permission_user.html
index eef6bf601..d5e8292e1 100644
--- a/apps/perms/templates/perms/asset_permission_user.html
+++ b/apps/perms/templates/perms/asset_permission_user.html
@@ -160,7 +160,7 @@ function addUsers(users) {
var success = function(data) {
location.reload();
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
@@ -175,7 +175,7 @@ function removeUser(users) {
var success = function(data) {
location.reload();
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
@@ -187,7 +187,7 @@ function updateGroup(groups) {
var body = {
user_groups: groups
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(body)
});
diff --git a/apps/perms/templates/perms/remote_app_permission_detail.html b/apps/perms/templates/perms/remote_app_permission_detail.html
index 98bc6633e..352e9f17a 100644
--- a/apps/perms/templates/perms/remote_app_permission_detail.html
+++ b/apps/perms/templates/perms/remote_app_permission_detail.html
@@ -160,7 +160,7 @@ $(document).ready(function () {
var body = {
'is_active': checked
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(body)
});
diff --git a/apps/perms/templates/perms/remote_app_permission_remote_app.html b/apps/perms/templates/perms/remote_app_permission_remote_app.html
index 63d395941..2e705945f 100644
--- a/apps/perms/templates/perms/remote_app_permission_remote_app.html
+++ b/apps/perms/templates/perms/remote_app_permission_remote_app.html
@@ -120,7 +120,7 @@
var success = function(data) {
location.reload();
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
@@ -134,7 +134,7 @@
var success = function(data) {
location.reload();
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
diff --git a/apps/perms/templates/perms/remote_app_permission_user.html b/apps/perms/templates/perms/remote_app_permission_user.html
index 7433327c5..b1fc8e488 100644
--- a/apps/perms/templates/perms/remote_app_permission_user.html
+++ b/apps/perms/templates/perms/remote_app_permission_user.html
@@ -158,7 +158,7 @@
var success = function(data) {
location.reload();
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
@@ -172,7 +172,7 @@
var success = function(data) {
location.reload();
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
@@ -183,7 +183,7 @@
var body = {
user_groups: groups
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(body)
});
diff --git a/apps/perms/urls/api_urls.py b/apps/perms/urls/api_urls.py
index 7075bda8e..093692a15 100644
--- a/apps/perms/urls/api_urls.py
+++ b/apps/perms/urls/api_urls.py
@@ -39,9 +39,10 @@ asset_permission_urlpatterns = [
# 查询某个用户组授权的资产和资产组
path('user-groups//assets/', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'),
- path('user-groups//nodes/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'),
- path('user-groups//nodes-assets/', api.UserGroupGrantedNodesWithAssetsApi.as_view(), name='user-group-nodes-assets'),
+ path('user-groups//nodes/tree/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'),
+ path('user-groups//nodes/', api.UserGroupGrantedNodesAsTreeApi.as_view(), name='user-group-nodes-as-tree'),
path('user-groups//nodes-assets/tree/', api.UserGroupGrantedNodesWithAssetsAsTreeApi.as_view(), name='user-group-nodes-assets-as-tree'),
+ path('user-groups//nodes-assets/', api.UserGroupGrantedNodesWithAssetsApi.as_view(), name='user-group-nodes-assets'),
path('user-groups//nodes//assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), name='user-group-node-assets'),
# 用户和资产授权变更
diff --git a/apps/settings/templates/settings/email_setting.html b/apps/settings/templates/settings/email_setting.html
index 46c4f5dac..40ce9f4cc 100644
--- a/apps/settings/templates/settings/email_setting.html
+++ b/apps/settings/templates/settings/email_setting.html
@@ -96,7 +96,7 @@ $(document).ready(function () {
function success(message) {
toastr.success(message.msg)
}
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(data),
method: "POST",
diff --git a/apps/settings/templates/settings/ldap_setting.html b/apps/settings/templates/settings/ldap_setting.html
index 58e4ae71b..694fb66f9 100644
--- a/apps/settings/templates/settings/ldap_setting.html
+++ b/apps/settings/templates/settings/ldap_setting.html
@@ -100,7 +100,7 @@ $(document).ready(function () {
function success(message) {
toastr.success(message.msg)
}
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(data),
method: "POST",
@@ -127,7 +127,7 @@ $(document).ready(function () {
function success(message) {
toastr.success(message.msg)
}
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify({'username_list':username_list}),
method: "POST",
diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js
index a216a1ce0..c880a5499 100644
--- a/apps/static/js/jumpserver.js
+++ b/apps/static/js/jumpserver.js
@@ -256,7 +256,7 @@ function formSubmit(props) {
})
}
-function APIUpdateAttr(props) {
+function requestApi(props) {
// props = {url: .., body: , success: , error: , method: ,}
props = props || {};
var user_success_message = props.success_message;
@@ -328,7 +328,7 @@ function objectDelete(obj, name, url, redirectTo) {
// swal("错误", "删除"+"[ "+name+" ]"+"遇到错误", "error");
swal(gettext('Error'), "[ "+name+" ]" + gettext("Being used by the asset, please unbind the asset first."), "error");
};
- APIUpdateAttr({
+ requestApi({
url: url,
body: JSON.stringify(body),
method: 'DELETE',
@@ -369,7 +369,7 @@ function orgDelete(obj, name, url, redirectTo){
swal(gettext("Error"), " [ "+ name + " ] " + gettext("Do not perform this operation under this organization. Try again after switching to another organization"), "error");
}
};
- APIUpdateAttr({
+ requestApi({
url: url,
body: JSON.stringify(body),
method: 'DELETE',
@@ -1109,7 +1109,19 @@ function objectAttrsIsBool(obj, attrs) {
})
}
+function cleanDate(d) {
+ for (var i=0; i<2; i++) {
+ if (isNaN(Date.parse(d))) {
+ d = d.split('+')[0].trimRight();
+ } else {
+ return d
+ }
+ }
+ return ''
+}
+
function formatDateAsCN(d) {
+ d = cleanDate(d);
var date = new Date(d);
var date_s = date.toLocaleString(navigator.language, {hour12: false});
return date_s.split("/").join('-')
@@ -1138,6 +1150,8 @@ function getTimeUnits(u) {
}
function timeOffset(a, b) {
+ a = cleanDate(a);
+ b = cleanDate(b);
var start = new Date(a);
var end = new Date(b);
var offset = (end - start)/1000;
diff --git a/apps/static/js/plugins/moment/moment.min.js b/apps/static/js/plugins/moment/moment.min.js
new file mode 100644
index 000000000..8e6866af0
--- /dev/null
+++ b/apps/static/js/plugins/moment/moment.min.js
@@ -0,0 +1,7 @@
+//! 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
diff --git a/apps/terminal/templates/terminal/session_detail.html b/apps/terminal/templates/terminal/session_detail.html
index 8bec6c50c..b54eedec7 100644
--- a/apps/terminal/templates/terminal/session_detail.html
+++ b/apps/terminal/templates/terminal/session_detail.html
@@ -132,7 +132,7 @@
}, 300)
}
var the_url = "{% url 'api-terminal:tasks-list' %}";
- APIUpdateAttr({url: the_url, method: 'POST', body: JSON.stringify(data), success: success, success_message: 'Terminate success'});
+ requestApi({url: the_url, method: 'POST', body: JSON.stringify(data), success: success, success_message: 'Terminate success'});
}
$(document).ready(function () {
$('.footable').footable();
diff --git a/apps/terminal/templates/terminal/session_list.html b/apps/terminal/templates/terminal/session_list.html
index e9a992481..5dd2e24df 100644
--- a/apps/terminal/templates/terminal/session_list.html
+++ b/apps/terminal/templates/terminal/session_list.html
@@ -90,7 +90,7 @@ function terminateSession(data) {
}
var success_message = '{% trans "Terminate task send, waiting ..." %}';
var the_url = "{% url 'api-terminal:kill-session' %}";
- APIUpdateAttr({
+ requestApi({
url: the_url,
method: 'POST',
body: JSON.stringify(data),
@@ -174,7 +174,7 @@ function finishedSession(data) {
var success = function() {
location.reload();
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
method: 'PATCH',
body: JSON.stringify(data),
diff --git a/apps/users/api/user.py b/apps/users/api/user.py
index c79b9e865..ba8e48b25 100644
--- a/apps/users/api/user.py
+++ b/apps/users/api/user.py
@@ -14,7 +14,7 @@ from rest_framework.pagination import LimitOffsetPagination
from common.permissions import (
IsOrgAdmin, IsCurrentUserOrReadOnly, IsOrgAdminOrAppUser,
- CanUpdateSuperUser,
+ CanUpdateDeleteSuperUser,
)
from common.mixins import IDInCacheFilterMixin
from common.utils import get_logger
@@ -38,7 +38,7 @@ class UserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
search_fields = filter_fields
queryset = User.objects.exclude(role=User.ROLE_APP)
serializer_class = UserSerializer
- permission_classes = (IsOrgAdmin, CanUpdateSuperUser)
+ permission_classes = (IsOrgAdmin, CanUpdateDeleteSuperUser)
pagination_class = LimitOffsetPagination
def send_created_signal(self, users):
diff --git a/apps/users/templates/users/_granted_assets.html b/apps/users/templates/users/_granted_assets.html
new file mode 100644
index 000000000..36cc27a1c
--- /dev/null
+++ b/apps/users/templates/users/_granted_assets.html
@@ -0,0 +1,165 @@
+{% load i18n %}
+
+
+
+
+
diff --git a/apps/users/templates/users/user_detail.html b/apps/users/templates/users/user_detail.html
index 351823a9e..929d7def4 100644
--- a/apps/users/templates/users/user_detail.html
+++ b/apps/users/templates/users/user_detail.html
@@ -280,7 +280,7 @@ function updateUserGroups(groups) {
// clear jumpserver.groups_selected
jumpserver.nodes_selected = {};
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
@@ -305,7 +305,7 @@ $(document).ready(function() {
'is_active': checked
};
var success = '{% trans "Update successfully!" %}';
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(body),
success_message: success
@@ -332,7 +332,7 @@ $(document).ready(function() {
'otp_secret_key': otp_secret_key
};
var success = '{% trans "Update successfully!" %}';
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(body),
success_message: success
@@ -372,7 +372,7 @@ $(document).ready(function() {
var msg = "{% trans "An e-mail has been sent to the user`s mailbox." %}";
swal("{% trans 'Reset password' %}", msg, "success");
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
@@ -398,7 +398,7 @@ $(document).ready(function() {
var msg = "{% trans 'The reset-ssh-public-key E-mail has been sent successfully. Please inform the user to update his new ssh public key.' %}";
swal("{% trans 'Reset SSH public key' %}", msg, "success");
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: body,
success: success
@@ -441,7 +441,7 @@ $(document).ready(function() {
}
);
};
- APIUpdateAttr({ url: the_url, body: JSON.stringify(body), success: success, error: fail});
+ requestApi({ url: the_url, body: JSON.stringify(body), success: success, error: fail});
}).on('click', '.btn-delete-user', function () {
var $this = $(this);
var name = "{{ user_object.name }}";
@@ -466,7 +466,7 @@ $(document).ready(function() {
}
);
};
- APIUpdateAttr({
+ requestApi({
url: the_url,
body: JSON.stringify(body),
success: success
@@ -485,7 +485,7 @@ $(document).ready(function() {
doReset();
});
}).on('click', '#btn-reset-mfa', function () {
- APIUpdateAttr({
+ requestApi({
url: "{% url 'api-users:user-reset-otp' pk=user_object.id %}",
method: "GET",
success_message: "{% trans 'Reset user MFA success' %}"
diff --git a/apps/users/templates/users/user_granted_asset.html b/apps/users/templates/users/user_granted_asset.html
index 225005ffd..fc5197a77 100644
--- a/apps/users/templates/users/user_granted_asset.html
+++ b/apps/users/templates/users/user_granted_asset.html
@@ -23,33 +23,7 @@
-
-
-
-
+ {% include 'users/_granted_assets.html' %}
@@ -58,81 +32,10 @@
{% endblock %}
{% block custom_foot_js %}