mirror of https://github.com/jumpserver/jumpserver
Dev beta (#3048)
* [Update] 统一url地址 * [Update] 修改api * [Update] 使用规范的签名 * [Update] 修改url * [Update] 修改swagger * [Update] 添加serializer class避免报错 * [Update] 修改token * [Update] 支持api key * [Update] 支持生成api key * [Update] 修改api重定向 * [Update] 修改翻译 * [Update] 添加说明文档 * [Update] 修复浏览器关闭后session不失效的问题 * [Update] 修改一些内容 * [Update] 修改 jms脚本 * [Update] 修改重定向 * [Update] 修改搜索trim * [Update] 修改搜索trim * [Update] 添加sys log * [Bugfix] 修改登陆错误 * [Update] 优化User操作private_token的接口 (#3091) * [Update] 优化User操作private_token的接口 * [Update] 优化User操作private_token的接口 2 * [Bugfix] 解决授权了一个节点,当移动节点后,被移动的节点下的资产会放到未分组节点下的问题 * [Update] 升级jquery * [Update] 默认使用page * [Update] 修改使用Orgmodel view set * [Update] 支持 nv的硬盘 https://github.com/jumpserver/jumpserver/issues/1804 * [UPdate] 解决命令执行宽度问题 * [Update] 优化节点 * [Update] 修改nodes过多时创建比较麻烦 * [Update] 修改导入 * [Update] 节点获取更新 * [Update] 修改nodes * [Update] nodes显示full value * [Update] 统一使用nodes select2 函数 * [Update] 修改磁盘大小小数 * [Update] 修改 Node service * [Update] 优化授权节点 * [Update] 修改 node permission * [Update] 修改asset permission * [Stash] * [Update] 修改node assets api * [Update] 修改tree service,支持资产数量 * [Update] 修改暂时完成 * [Update] 修改一些bugpull/3146/head
parent
fe6f7bcfc1
commit
164f48e131
|
@ -3,9 +3,8 @@
|
||||||
|
|
||||||
|
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
from rest_framework.pagination import LimitOffsetPagination
|
|
||||||
from rest_framework_bulk import BulkModelViewSet
|
|
||||||
|
|
||||||
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
from ..hands import IsOrgAdmin, IsAppUser
|
from ..hands import IsOrgAdmin, IsAppUser
|
||||||
from ..models import RemoteApp
|
from ..models import RemoteApp
|
||||||
from ..serializers import RemoteAppSerializer, RemoteAppConnectionInfoSerializer
|
from ..serializers import RemoteAppSerializer, RemoteAppConnectionInfoSerializer
|
||||||
|
@ -16,13 +15,12 @@ __all__ = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class RemoteAppViewSet(BulkModelViewSet):
|
class RemoteAppViewSet(OrgBulkModelViewSet):
|
||||||
filter_fields = ('name',)
|
filter_fields = ('name',)
|
||||||
search_fields = filter_fields
|
search_fields = filter_fields
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
queryset = RemoteApp.objects.all()
|
queryset = RemoteApp.objects.all()
|
||||||
serializer_class = RemoteAppSerializer
|
serializer_class = RemoteAppSerializer
|
||||||
pagination_class = LimitOffsetPagination
|
|
||||||
|
|
||||||
|
|
||||||
class RemoteAppConnectionInfoApi(generics.RetrieveAPIView):
|
class RemoteAppConnectionInfoApi(generics.RetrieveAPIView):
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
from orgs.mixins import OrgModelForm
|
from orgs.mixins.forms import OrgModelForm
|
||||||
from assets.models import SystemUser
|
from assets.models import SystemUser
|
||||||
|
|
||||||
from ..models import RemoteApp
|
from ..models import RemoteApp
|
||||||
|
|
|
@ -5,7 +5,7 @@ import uuid
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orgs.mixins import OrgModelMixin
|
from orgs.mixins.models import OrgModelMixin
|
||||||
from common.fields.model import EncryptJsonDictTextField
|
from common.fields.model import EncryptJsonDictTextField
|
||||||
|
|
||||||
from .. import const
|
from .. import const
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from common.serializers import AdaptedBulkListSerializer
|
from common.serializers import AdaptedBulkListSerializer
|
||||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
|
|
||||||
from .. import const
|
from .. import const
|
||||||
from ..models import RemoteApp
|
from ..models import RemoteApp
|
||||||
|
|
|
@ -1,20 +1,24 @@
|
||||||
# coding:utf-8
|
# coding:utf-8
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.urls import path
|
from django.urls import path, re_path
|
||||||
from rest_framework_bulk.routes import BulkRouter
|
from rest_framework_bulk.routes import BulkRouter
|
||||||
|
|
||||||
|
from common import api as capi
|
||||||
from .. import api
|
from .. import api
|
||||||
|
|
||||||
app_name = 'applications'
|
app_name = 'applications'
|
||||||
|
|
||||||
router = BulkRouter()
|
router = BulkRouter()
|
||||||
router.register(r'remote-app', api.RemoteAppViewSet, 'remote-app')
|
router.register(r'remote-apps', api.RemoteAppViewSet, 'remote-app')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('remote-apps/<uuid:pk>/connection-info/',
|
path('remote-apps/<uuid:pk>/connection-info/',
|
||||||
api.RemoteAppConnectionInfoApi.as_view(),
|
api.RemoteAppConnectionInfoApi.as_view(),
|
||||||
name='remote-app-connection-info')
|
name='remote-app-connection-info')
|
||||||
]
|
]
|
||||||
|
old_version_urlpatterns = [
|
||||||
|
re_path('(?P<resource>remote-app)/.*', capi.redirect_plural_name_api)
|
||||||
|
]
|
||||||
|
|
||||||
urlpatterns += router.urls
|
urlpatterns += router.urls + old_version_urlpatterns
|
||||||
|
|
|
@ -17,8 +17,7 @@ from django.db import transaction
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework_bulk import BulkModelViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
from rest_framework.pagination import LimitOffsetPagination
|
|
||||||
|
|
||||||
from common.mixins import IDInCacheFilterMixin
|
from common.mixins import IDInCacheFilterMixin
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
|
@ -36,7 +35,7 @@ __all__ = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class AdminUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
|
class AdminUserViewSet(OrgBulkModelViewSet):
|
||||||
"""
|
"""
|
||||||
Admin user api set, for add,delete,update,list,retrieve resource
|
Admin user api set, for add,delete,update,list,retrieve resource
|
||||||
"""
|
"""
|
||||||
|
@ -46,11 +45,6 @@ class AdminUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
|
||||||
queryset = AdminUser.objects.all()
|
queryset = AdminUser.objects.all()
|
||||||
serializer_class = serializers.AdminUserSerializer
|
serializer_class = serializers.AdminUserSerializer
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
pagination_class = LimitOffsetPagination
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
queryset = super().get_queryset().all()
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
|
|
||||||
class AdminUserAuthApi(generics.UpdateAPIView):
|
class AdminUserAuthApi(generics.UpdateAPIView):
|
||||||
|
@ -98,7 +92,6 @@ class AdminUserTestConnectiveApi(generics.RetrieveAPIView):
|
||||||
class AdminUserAssetsListView(generics.ListAPIView):
|
class AdminUserAssetsListView(generics.ListAPIView):
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
serializer_class = serializers.AssetSimpleSerializer
|
serializer_class = serializers.AssetSimpleSerializer
|
||||||
pagination_class = LimitOffsetPagination
|
|
||||||
filter_fields = ("hostname", "ip")
|
filter_fields = ("hostname", "ip")
|
||||||
http_method_names = ['get']
|
http_method_names = ['get']
|
||||||
search_fields = filter_fields
|
search_fields = filter_fields
|
||||||
|
|
|
@ -1,27 +1,17 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
import uuid
|
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
from rest_framework.views import APIView
|
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework_bulk import BulkModelViewSet
|
|
||||||
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
|
|
||||||
from rest_framework.pagination import LimitOffsetPagination
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.urls import reverse_lazy
|
|
||||||
from django.core.cache import cache
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from common.mixins import IDInCacheFilterMixin, ApiMessageMixin
|
|
||||||
|
|
||||||
from common.utils import get_logger, get_object_or_none
|
from common.utils import get_logger, get_object_or_none
|
||||||
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
||||||
from orgs.mixins import OrgBulkModelViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX
|
|
||||||
from ..models import Asset, AdminUser, Node
|
from ..models import Asset, AdminUser, Node
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
from ..tasks import update_asset_hardware_info_manual, \
|
from ..tasks import update_asset_hardware_info_manual, \
|
||||||
|
@ -31,9 +21,9 @@ from ..utils import LabelFilter
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AssetViewSet', 'AssetListUpdateApi',
|
'AssetViewSet',
|
||||||
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi',
|
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi',
|
||||||
'AssetGatewayApi', 'AssetBulkUpdateSelectAPI'
|
'AssetGatewayApi',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,7 +36,6 @@ class AssetViewSet(LabelFilter, OrgBulkModelViewSet):
|
||||||
ordering_fields = ("hostname", "ip", "port", "cpu_cores")
|
ordering_fields = ("hostname", "ip", "port", "cpu_cores")
|
||||||
queryset = Asset.objects.all()
|
queryset = Asset.objects.all()
|
||||||
serializer_class = serializers.AssetSerializer
|
serializer_class = serializers.AssetSerializer
|
||||||
pagination_class = LimitOffsetPagination
|
|
||||||
permission_classes = (IsOrgAdminOrAppUser,)
|
permission_classes = (IsOrgAdminOrAppUser,)
|
||||||
success_message = _("%(hostname)s was %(action)s successfully")
|
success_message = _("%(hostname)s was %(action)s successfully")
|
||||||
|
|
||||||
|
@ -73,19 +62,21 @@ class AssetViewSet(LabelFilter, OrgBulkModelViewSet):
|
||||||
node = get_object_or_404(Node, id=node_id)
|
node = get_object_or_404(Node, id=node_id)
|
||||||
show_current_asset = self.request.query_params.get("show_current_asset") in ('1', 'true')
|
show_current_asset = self.request.query_params.get("show_current_asset") in ('1', 'true')
|
||||||
|
|
||||||
|
# 当前节点是顶层节点, 并且仅显示直接资产
|
||||||
if node.is_root() and show_current_asset:
|
if node.is_root() and show_current_asset:
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
Q(nodes=node_id) | Q(nodes__isnull=True)
|
Q(nodes=node_id) | Q(nodes__isnull=True)
|
||||||
)
|
).distinct()
|
||||||
|
# 当前节点是顶层节点,显示所有资产
|
||||||
elif node.is_root() and not show_current_asset:
|
elif node.is_root() and not show_current_asset:
|
||||||
pass
|
return queryset
|
||||||
|
# 当前节点不是鼎城节点,只显示直接资产
|
||||||
elif not node.is_root() and show_current_asset:
|
elif not node.is_root() and show_current_asset:
|
||||||
queryset = queryset.filter(nodes=node)
|
queryset = queryset.filter(nodes=node)
|
||||||
else:
|
else:
|
||||||
queryset = queryset.filter(
|
children = node.get_all_children(with_self=True)
|
||||||
nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key),
|
queryset = queryset.filter(nodes__in=children).distinct()
|
||||||
)
|
return queryset
|
||||||
return queryset.distinct()
|
|
||||||
|
|
||||||
def filter_admin_user_id(self, queryset):
|
def filter_admin_user_id(self, queryset):
|
||||||
admin_user_id = self.request.query_params.get('admin_user_id')
|
admin_user_id = self.request.query_params.get('admin_user_id')
|
||||||
|
@ -102,30 +93,6 @@ class AssetViewSet(LabelFilter, OrgBulkModelViewSet):
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class AssetListUpdateApi(IDInCacheFilterMixin, ListBulkCreateUpdateDestroyAPIView):
|
|
||||||
"""
|
|
||||||
Asset bulk update api
|
|
||||||
"""
|
|
||||||
queryset = Asset.objects.all()
|
|
||||||
serializer_class = serializers.AssetSerializer
|
|
||||||
permission_classes = (IsOrgAdmin,)
|
|
||||||
|
|
||||||
|
|
||||||
class AssetBulkUpdateSelectAPI(APIView):
|
|
||||||
permission_classes = (IsOrgAdmin,)
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
assets_id = request.data.get('assets_id', '')
|
|
||||||
if assets_id:
|
|
||||||
spm = uuid.uuid4().hex
|
|
||||||
key = CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX.format(spm)
|
|
||||||
cache.set(key, assets_id, 300)
|
|
||||||
url = reverse_lazy('assets:asset-bulk-update') + '?spm=%s' % spm
|
|
||||||
return Response({'url': url})
|
|
||||||
error = _('Please select assets that need to be updated')
|
|
||||||
return Response({'error': error}, status=400)
|
|
||||||
|
|
||||||
|
|
||||||
class AssetRefreshHardwareApi(generics.RetrieveAPIView):
|
class AssetRefreshHardwareApi(generics.RetrieveAPIView):
|
||||||
"""
|
"""
|
||||||
Refresh asset hardware info
|
Refresh asset hardware info
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import viewsets, status, generics
|
from rest_framework import generics
|
||||||
from rest_framework.pagination import LimitOffsetPagination
|
|
||||||
from rest_framework import filters
|
from rest_framework import filters
|
||||||
from rest_framework_bulk import BulkModelViewSet
|
from rest_framework_bulk import BulkModelViewSet
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.http import Http404
|
||||||
|
|
||||||
from common.permissions import IsOrgAdminOrAppUser, NeedMFAVerify
|
from common.permissions import IsOrgAdminOrAppUser, NeedMFAVerify
|
||||||
from common.utils import get_object_or_none, get_logger
|
from common.utils import get_object_or_none, get_logger
|
||||||
|
@ -53,7 +53,6 @@ class AssetUserSearchBackend(filters.BaseFilterBackend):
|
||||||
|
|
||||||
|
|
||||||
class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
|
class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
|
||||||
pagination_class = LimitOffsetPagination
|
|
||||||
serializer_class = serializers.AssetUserSerializer
|
serializer_class = serializers.AssetUserSerializer
|
||||||
permission_classes = [IsOrgAdminOrAppUser]
|
permission_classes = [IsOrgAdminOrAppUser]
|
||||||
http_method_names = ['get', 'post']
|
http_method_names = ['get', 'post']
|
||||||
|
@ -67,6 +66,9 @@ class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
|
||||||
AssetUserFilterBackend, AssetUserSearchBackend,
|
AssetUserFilterBackend, AssetUserSearchBackend,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def allow_bulk_destroy(self, qs, filtered):
|
||||||
|
return False
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
# 尽可能先返回更少的数据
|
# 尽可能先返回更少的数据
|
||||||
username = self.request.GET.get('username')
|
username = self.request.GET.get('username')
|
||||||
|
@ -115,14 +117,6 @@ class AssetUserAuthInfoApi(generics.RetrieveAPIView):
|
||||||
serializer_class = serializers.AssetUserAuthInfoSerializer
|
serializer_class = serializers.AssetUserAuthInfoSerializer
|
||||||
permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
|
permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
|
||||||
|
|
||||||
def retrieve(self, request, *args, **kwargs):
|
|
||||||
instance = self.get_object()
|
|
||||||
serializer = self.get_serializer(instance)
|
|
||||||
status_code = status.HTTP_200_OK
|
|
||||||
if not instance:
|
|
||||||
status_code = status.HTTP_400_BAD_REQUEST
|
|
||||||
return Response(serializer.data, status=status_code)
|
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
query_params = self.request.query_params
|
query_params = self.request.query_params
|
||||||
username = query_params.get('username')
|
username = query_params.get('username')
|
||||||
|
@ -133,8 +127,7 @@ class AssetUserAuthInfoApi(generics.RetrieveAPIView):
|
||||||
manger = AssetUserManager()
|
manger = AssetUserManager()
|
||||||
instance = manger.get(username, asset, prefer=prefer)
|
instance = manger.get(username, asset, prefer=prefer)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e, exc_info=True)
|
raise Http404("Not found")
|
||||||
return None
|
|
||||||
else:
|
else:
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
from rest_framework_bulk import BulkModelViewSet
|
|
||||||
from rest_framework.pagination import LimitOffsetPagination
|
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
from ..hands import IsOrgAdmin
|
from ..hands import IsOrgAdmin
|
||||||
from ..models import CommandFilter, CommandFilterRule
|
from ..models import CommandFilter, CommandFilterRule
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
|
@ -13,21 +12,19 @@ from .. import serializers
|
||||||
__all__ = ['CommandFilterViewSet', 'CommandFilterRuleViewSet']
|
__all__ = ['CommandFilterViewSet', 'CommandFilterRuleViewSet']
|
||||||
|
|
||||||
|
|
||||||
class CommandFilterViewSet(BulkModelViewSet):
|
class CommandFilterViewSet(OrgBulkModelViewSet):
|
||||||
filter_fields = ("name",)
|
filter_fields = ("name",)
|
||||||
search_fields = filter_fields
|
search_fields = filter_fields
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
queryset = CommandFilter.objects.all()
|
queryset = CommandFilter.objects.all()
|
||||||
serializer_class = serializers.CommandFilterSerializer
|
serializer_class = serializers.CommandFilterSerializer
|
||||||
pagination_class = LimitOffsetPagination
|
|
||||||
|
|
||||||
|
|
||||||
class CommandFilterRuleViewSet(BulkModelViewSet):
|
class CommandFilterRuleViewSet(OrgBulkModelViewSet):
|
||||||
filter_fields = ("content",)
|
filter_fields = ("content",)
|
||||||
search_fields = filter_fields
|
search_fields = filter_fields
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
serializer_class = serializers.CommandFilterRuleSerializer
|
serializer_class = serializers.CommandFilterRuleSerializer
|
||||||
pagination_class = LimitOffsetPagination
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
fpk = self.kwargs.get('filter_pk')
|
fpk = self.kwargs.get('filter_pk')
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
# ~*~ coding: utf-8 ~*~
|
# ~*~ coding: utf-8 ~*~
|
||||||
|
|
||||||
from rest_framework_bulk import BulkModelViewSet
|
|
||||||
from rest_framework.views import APIView, Response
|
from rest_framework.views import APIView, Response
|
||||||
from rest_framework.pagination import LimitOffsetPagination
|
|
||||||
|
|
||||||
from django.views.generic.detail import SingleObjectMixin
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
|
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from common.permissions import IsOrgAdmin, IsAppUser, IsOrgAdminOrAppUser
|
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
||||||
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
from ..models import Domain, Gateway
|
from ..models import Domain, Gateway
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
|
|
||||||
|
@ -16,11 +14,10 @@ logger = get_logger(__file__)
|
||||||
__all__ = ['DomainViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"]
|
__all__ = ['DomainViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"]
|
||||||
|
|
||||||
|
|
||||||
class DomainViewSet(BulkModelViewSet):
|
class DomainViewSet(OrgBulkModelViewSet):
|
||||||
queryset = Domain.objects.all()
|
queryset = Domain.objects.all()
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
serializer_class = serializers.DomainSerializer
|
serializer_class = serializers.DomainSerializer
|
||||||
pagination_class = LimitOffsetPagination
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = super().get_queryset().all()
|
queryset = super().get_queryset().all()
|
||||||
|
@ -37,13 +34,12 @@ class DomainViewSet(BulkModelViewSet):
|
||||||
return super().get_permissions()
|
return super().get_permissions()
|
||||||
|
|
||||||
|
|
||||||
class GatewayViewSet(BulkModelViewSet):
|
class GatewayViewSet(OrgBulkModelViewSet):
|
||||||
filter_fields = ("domain__name", "name", "username", "ip", "domain")
|
filter_fields = ("domain__name", "name", "username", "ip", "domain")
|
||||||
search_fields = filter_fields
|
search_fields = filter_fields
|
||||||
queryset = Gateway.objects.all()
|
queryset = Gateway.objects.all()
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
serializer_class = serializers.GatewaySerializer
|
serializer_class = serializers.GatewaySerializer
|
||||||
pagination_class = LimitOffsetPagination
|
|
||||||
|
|
||||||
|
|
||||||
class GatewayTestConnectionApi(SingleObjectMixin, APIView):
|
class GatewayTestConnectionApi(SingleObjectMixin, APIView):
|
||||||
|
|
|
@ -13,11 +13,10 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from rest_framework.pagination import LimitOffsetPagination
|
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
|
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from orgs.mixins import OrgBulkModelViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
from ..hands import IsOrgAdmin
|
from ..hands import IsOrgAdmin
|
||||||
from ..models import Label
|
from ..models import Label
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
|
@ -32,7 +31,6 @@ class LabelViewSet(OrgBulkModelViewSet):
|
||||||
search_fields = filter_fields
|
search_fields = filter_fields
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
serializer_class = serializers.LabelSerializer
|
serializer_class = serializers.LabelSerializer
|
||||||
pagination_class = LimitOffsetPagination
|
|
||||||
|
|
||||||
def list(self, request, *args, **kwargs):
|
def list(self, request, *args, **kwargs):
|
||||||
if request.query_params.get("distinct"):
|
if request.query_params.get("distinct"):
|
||||||
|
|
|
@ -13,7 +13,9 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from rest_framework import generics, mixins, viewsets
|
import time
|
||||||
|
|
||||||
|
from rest_framework import generics
|
||||||
from rest_framework.serializers import ValidationError
|
from rest_framework.serializers import ValidationError
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
@ -22,11 +24,11 @@ from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
from common.utils import get_logger, get_object_or_none
|
from common.utils import get_logger, get_object_or_none
|
||||||
from common.tree import TreeNodeSerializer
|
from common.tree import TreeNodeSerializer
|
||||||
|
from orgs.mixins.api import OrgModelViewSet
|
||||||
from ..hands import IsOrgAdmin
|
from ..hands import IsOrgAdmin
|
||||||
from ..models import Node
|
from ..models import Node
|
||||||
from ..tasks import update_assets_hardware_info_util, test_asset_connectivity_util
|
from ..tasks import update_assets_hardware_info_util, test_asset_connectivity_util
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
from ..utils import NodeUtil
|
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
@ -39,29 +41,25 @@ __all__ = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class NodeViewSet(viewsets.ModelViewSet):
|
class NodeViewSet(OrgModelViewSet):
|
||||||
filter_fields = ('value', 'key', )
|
filter_fields = ('value', 'key', 'id')
|
||||||
search_fields = filter_fields
|
search_fields = ('value', )
|
||||||
queryset = Node.objects.all()
|
queryset = Node.objects.all()
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
serializer_class = serializers.NodeSerializer
|
serializer_class = serializers.NodeSerializer
|
||||||
|
|
||||||
|
# 仅支持根节点指直接创建,子节点下的节点需要通过children接口创建
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
child_key = Node.root().get_next_child_key()
|
child_key = Node.root().get_next_child_key()
|
||||||
serializer.validated_data["key"] = child_key
|
serializer.validated_data["key"] = child_key
|
||||||
serializer.save()
|
serializer.save()
|
||||||
|
|
||||||
def update(self, request, *args, **kwargs):
|
def perform_update(self, serializer):
|
||||||
node = self.get_object()
|
node = self.get_object()
|
||||||
if node.is_root():
|
if node.is_root() and node.value != serializer.validated_data['value']:
|
||||||
node_value = node.value
|
msg = _("You can't update the root node name")
|
||||||
post_value = request.data.get('value')
|
raise ValidationError({"error": msg})
|
||||||
if node_value != post_value:
|
return super().perform_update(serializer)
|
||||||
return Response(
|
|
||||||
{"msg": _("You can't update the root node name")},
|
|
||||||
status=400
|
|
||||||
)
|
|
||||||
return super().update(request, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class NodeListAsTreeApi(generics.ListAPIView):
|
class NodeListAsTreeApi(generics.ListAPIView):
|
||||||
|
@ -79,21 +77,72 @@ class NodeListAsTreeApi(generics.ListAPIView):
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
serializer_class = TreeNodeSerializer
|
serializer_class = TreeNodeSerializer
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_tree_queryset(queryset):
|
||||||
|
queryset = [node.as_tree_node() for node in queryset]
|
||||||
|
return queryset
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = Node.objects.all()
|
queryset = Node.objects.all()
|
||||||
util = NodeUtil()
|
|
||||||
nodes = util.get_nodes_by_queryset(queryset)
|
|
||||||
queryset = [node.as_tree_node() for node in nodes]
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
@staticmethod
|
def filter_queryset(self, queryset):
|
||||||
def refresh_nodes(queryset):
|
queryset = super().filter_queryset(queryset)
|
||||||
Node.expire_nodes_assets_amount()
|
queryset = self.to_tree_queryset(queryset)
|
||||||
Node.expire_nodes_full_value()
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class NodeChildrenAsTreeApi(generics.ListAPIView):
|
class NodeChildrenApi(generics.ListCreateAPIView):
|
||||||
|
queryset = Node.objects.all()
|
||||||
|
permission_classes = (IsOrgAdmin,)
|
||||||
|
serializer_class = serializers.NodeSerializer
|
||||||
|
instance = None
|
||||||
|
|
||||||
|
def initial(self, request, *args, **kwargs):
|
||||||
|
self.instance = self.get_object()
|
||||||
|
return super().initial(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def perform_create(self, serializer):
|
||||||
|
data = serializer.validated_data
|
||||||
|
_id = data.get("id")
|
||||||
|
value = data.get("value")
|
||||||
|
if not value:
|
||||||
|
value = self.instance.get_next_child_preset_name()
|
||||||
|
node = self.instance.create_child(value=value, _id=_id)
|
||||||
|
# 避免查询 full value
|
||||||
|
node._full_value = node.value
|
||||||
|
serializer.instance = node
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
pk = self.kwargs.get('pk') or self.request.query_params.get('id')
|
||||||
|
key = self.request.query_params.get("key")
|
||||||
|
if not pk and not key:
|
||||||
|
node = Node.root()
|
||||||
|
return node
|
||||||
|
if pk:
|
||||||
|
node = get_object_or_404(Node, pk=pk)
|
||||||
|
else:
|
||||||
|
node = get_object_or_404(Node, key=key)
|
||||||
|
return node
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
query_all = self.request.query_params.get("all", "0") == "all"
|
||||||
|
if not self.instance:
|
||||||
|
return Node.objects.none()
|
||||||
|
|
||||||
|
if self.instance.is_root():
|
||||||
|
with_self = True
|
||||||
|
else:
|
||||||
|
with_self = False
|
||||||
|
|
||||||
|
if query_all:
|
||||||
|
queryset = self.instance.get_all_children(with_self=with_self)
|
||||||
|
else:
|
||||||
|
queryset = self.instance.get_children(with_self=with_self)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
class NodeChildrenAsTreeApi(NodeChildrenApi):
|
||||||
"""
|
"""
|
||||||
节点子节点作为树返回,
|
节点子节点作为树返回,
|
||||||
[
|
[
|
||||||
|
@ -106,39 +155,26 @@ class NodeChildrenAsTreeApi(generics.ListAPIView):
|
||||||
]
|
]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
permission_classes = (IsOrgAdmin,)
|
|
||||||
serializer_class = TreeNodeSerializer
|
serializer_class = TreeNodeSerializer
|
||||||
node = None
|
http_method_names = ['get']
|
||||||
is_root = False
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
self.check_need_refresh_nodes()
|
queryset = super().get_queryset()
|
||||||
node_key = self.request.query_params.get('key')
|
|
||||||
util = NodeUtil()
|
|
||||||
# 是否包含自己
|
|
||||||
with_self = False
|
|
||||||
if not node_key:
|
|
||||||
node_key = Node.root().key
|
|
||||||
with_self = True
|
|
||||||
self.node = util.get_node_by_key(node_key)
|
|
||||||
queryset = self.node.get_children(with_self=with_self)
|
|
||||||
queryset = [node.as_tree_node() for node in queryset]
|
queryset = [node.as_tree_node() for node in queryset]
|
||||||
|
queryset = self.add_assets_if_need(queryset)
|
||||||
queryset = sorted(queryset)
|
queryset = sorted(queryset)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def filter_assets(self, queryset):
|
def add_assets_if_need(self, queryset):
|
||||||
include_assets = self.request.query_params.get('assets', '0') == '1'
|
include_assets = self.request.query_params.get('assets', '0') == '1'
|
||||||
if not include_assets:
|
if not include_assets:
|
||||||
return queryset
|
return queryset
|
||||||
assets = self.node.get_assets().only(
|
assets = self.instance.get_assets().only(
|
||||||
"id", "hostname", "ip", 'platform', "os", "org_id", "protocols",
|
"id", "hostname", "ip", 'platform', "os",
|
||||||
|
"org_id", "protocols",
|
||||||
)
|
)
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
queryset.append(asset.as_tree_node(self.node))
|
queryset.append(asset.as_tree_node(self.instance))
|
||||||
return queryset
|
|
||||||
|
|
||||||
def filter_queryset(self, queryset):
|
|
||||||
queryset = self.filter_assets(queryset)
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def check_need_refresh_nodes(self):
|
def check_need_refresh_nodes(self):
|
||||||
|
@ -146,59 +182,6 @@ class NodeChildrenAsTreeApi(generics.ListAPIView):
|
||||||
Node.refresh_nodes()
|
Node.refresh_nodes()
|
||||||
|
|
||||||
|
|
||||||
class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
|
||||||
queryset = Node.objects.all()
|
|
||||||
permission_classes = (IsOrgAdmin,)
|
|
||||||
serializer_class = serializers.NodeSerializer
|
|
||||||
instance = None
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
return self.list(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
instance = self.get_object()
|
|
||||||
if not request.data.get("value"):
|
|
||||||
request.data["value"] = instance.get_next_child_preset_name()
|
|
||||||
return super().post(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
|
||||||
instance = self.get_object()
|
|
||||||
value = request.data.get("value")
|
|
||||||
_id = request.data.get('id') or None
|
|
||||||
values = [child.value for child in instance.get_children()]
|
|
||||||
if value in values:
|
|
||||||
raise ValidationError(
|
|
||||||
'The same level node name cannot be the same'
|
|
||||||
)
|
|
||||||
node = instance.create_child(value=value, _id=_id)
|
|
||||||
return Response(self.serializer_class(instance=node).data, status=201)
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
pk = self.kwargs.get('pk') or self.request.query_params.get('id')
|
|
||||||
if not pk:
|
|
||||||
node = Node.root()
|
|
||||||
else:
|
|
||||||
node = get_object_or_404(Node, pk=pk)
|
|
||||||
return node
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
queryset = []
|
|
||||||
query_all = self.request.query_params.get("all")
|
|
||||||
node = self.get_object()
|
|
||||||
|
|
||||||
if node is None:
|
|
||||||
node = Node.root()
|
|
||||||
node.assets__count = node.get_all_assets().count()
|
|
||||||
queryset.append(node)
|
|
||||||
|
|
||||||
if query_all:
|
|
||||||
children = node.get_all_children()
|
|
||||||
else:
|
|
||||||
children = node.get_children()
|
|
||||||
queryset.extend(list(children))
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
|
|
||||||
class NodeAssetsApi(generics.ListAPIView):
|
class NodeAssetsApi(generics.ListAPIView):
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
serializer_class = serializers.AssetSerializer
|
serializer_class = serializers.AssetSerializer
|
||||||
|
|
|
@ -16,18 +16,17 @@
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework_bulk import BulkModelViewSet
|
|
||||||
from rest_framework.pagination import LimitOffsetPagination
|
|
||||||
|
|
||||||
|
from common.serializers import CeleryTaskSerializer
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
||||||
from common.mixins import IDInCacheFilterMixin
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
from orgs.mixins import OrgBulkModelViewSet
|
|
||||||
from ..models import SystemUser, Asset
|
from ..models import SystemUser, Asset
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
from ..tasks import push_system_user_to_assets_manual, \
|
from ..tasks import (
|
||||||
test_system_user_connectivity_manual, push_system_user_a_asset_manual, \
|
push_system_user_to_assets_manual, test_system_user_connectivity_manual,
|
||||||
test_system_user_connectivity_a_asset
|
push_system_user_a_asset_manual, test_system_user_connectivity_a_asset,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
@ -49,7 +48,6 @@ class SystemUserViewSet(OrgBulkModelViewSet):
|
||||||
queryset = SystemUser.objects.all()
|
queryset = SystemUser.objects.all()
|
||||||
serializer_class = serializers.SystemUserSerializer
|
serializer_class = serializers.SystemUserSerializer
|
||||||
permission_classes = (IsOrgAdminOrAppUser,)
|
permission_classes = (IsOrgAdminOrAppUser,)
|
||||||
pagination_class = LimitOffsetPagination
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = super().get_queryset().all()
|
queryset = super().get_queryset().all()
|
||||||
|
@ -92,6 +90,7 @@ class SystemUserPushApi(generics.RetrieveAPIView):
|
||||||
"""
|
"""
|
||||||
queryset = SystemUser.objects.all()
|
queryset = SystemUser.objects.all()
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
|
serializer_class = CeleryTaskSerializer
|
||||||
|
|
||||||
def retrieve(self, request, *args, **kwargs):
|
def retrieve(self, request, *args, **kwargs):
|
||||||
system_user = self.get_object()
|
system_user = self.get_object()
|
||||||
|
@ -108,6 +107,7 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView):
|
||||||
"""
|
"""
|
||||||
queryset = SystemUser.objects.all()
|
queryset = SystemUser.objects.all()
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
|
serializer_class = CeleryTaskSerializer
|
||||||
|
|
||||||
def retrieve(self, request, *args, **kwargs):
|
def retrieve(self, request, *args, **kwargs):
|
||||||
system_user = self.get_object()
|
system_user = self.get_object()
|
||||||
|
@ -118,7 +118,6 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView):
|
||||||
class SystemUserAssetsListView(generics.ListAPIView):
|
class SystemUserAssetsListView(generics.ListAPIView):
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
serializer_class = serializers.AssetSimpleSerializer
|
serializer_class = serializers.AssetSimpleSerializer
|
||||||
pagination_class = LimitOffsetPagination
|
|
||||||
filter_fields = ("hostname", "ip")
|
filter_fields = ("hostname", "ip")
|
||||||
http_method_names = ['get']
|
http_method_names = ['get']
|
||||||
search_fields = filter_fields
|
search_fields = filter_fields
|
||||||
|
|
|
@ -4,7 +4,7 @@ from django import forms
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from orgs.mixins import OrgModelForm
|
from orgs.mixins.forms import OrgModelForm
|
||||||
|
|
||||||
from ..models import Asset, Node
|
from ..models import Asset, Node
|
||||||
|
|
||||||
|
@ -29,9 +29,14 @@ class ProtocolForm(forms.Form):
|
||||||
class AssetCreateForm(OrgModelForm):
|
class AssetCreateForm(OrgModelForm):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
if self.data:
|
||||||
|
return
|
||||||
nodes_field = self.fields['nodes']
|
nodes_field = self.fields['nodes']
|
||||||
nodes_field.choices = ((n.id, n.full_value) for n in
|
if self.instance:
|
||||||
Node.get_queryset())
|
nodes_field.choices = ((n.id, n.full_value) for n in
|
||||||
|
self.instance.nodes.all())
|
||||||
|
else:
|
||||||
|
nodes_field.choices = []
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Asset
|
model = Asset
|
||||||
|
@ -42,7 +47,7 @@ class AssetCreateForm(OrgModelForm):
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'nodes': forms.SelectMultiple(attrs={
|
'nodes': forms.SelectMultiple(attrs={
|
||||||
'class': 'select2', 'data-placeholder': _('Nodes')
|
'class': 'nodes-select2', 'data-placeholder': _('Nodes')
|
||||||
}),
|
}),
|
||||||
'admin_user': forms.Select(attrs={
|
'admin_user': forms.Select(attrs={
|
||||||
'class': 'select2', 'data-placeholder': _('Admin user')
|
'class': 'select2', 'data-placeholder': _('Admin user')
|
||||||
|
@ -68,6 +73,17 @@ class AssetCreateForm(OrgModelForm):
|
||||||
|
|
||||||
|
|
||||||
class AssetUpdateForm(OrgModelForm):
|
class AssetUpdateForm(OrgModelForm):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
if self.data:
|
||||||
|
return
|
||||||
|
nodes_field = self.fields['nodes']
|
||||||
|
if self.instance:
|
||||||
|
nodes_field.choices = ((n.id, n.full_value) for n in
|
||||||
|
self.instance.nodes.all())
|
||||||
|
else:
|
||||||
|
nodes_field.choices = []
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Asset
|
model = Asset
|
||||||
fields = [
|
fields = [
|
||||||
|
@ -77,7 +93,7 @@ class AssetUpdateForm(OrgModelForm):
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'nodes': forms.SelectMultiple(attrs={
|
'nodes': forms.SelectMultiple(attrs={
|
||||||
'class': 'select2', 'data-placeholder': _('Node')
|
'class': 'nodes-select2', 'data-placeholder': _('Node')
|
||||||
}),
|
}),
|
||||||
'admin_user': forms.Select(attrs={
|
'admin_user': forms.Select(attrs={
|
||||||
'class': 'select2', 'data-placeholder': _('Admin user')
|
'class': 'select2', 'data-placeholder': _('Admin user')
|
||||||
|
|
|
@ -5,7 +5,7 @@ from django.core.exceptions import ValidationError
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from orgs.mixins import OrgModelForm
|
from orgs.mixins.forms import OrgModelForm
|
||||||
from ..models import CommandFilter, CommandFilterRule
|
from ..models import CommandFilter, CommandFilterRule
|
||||||
|
|
||||||
__all__ = ['CommandFilterForm', 'CommandFilterRuleForm']
|
__all__ = ['CommandFilterForm', 'CommandFilterRuleForm']
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from orgs.mixins import OrgModelForm
|
from orgs.mixins.forms import OrgModelForm
|
||||||
from ..models import Domain, Asset, Gateway
|
from ..models import Domain, Asset, Gateway
|
||||||
from .user import PasswordAndKeyAuthForm
|
from .user import PasswordAndKeyAuthForm
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ from django import forms
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from common.utils import validate_ssh_private_key, ssh_pubkey_gen, get_logger
|
from common.utils import validate_ssh_private_key, ssh_pubkey_gen, get_logger
|
||||||
from orgs.mixins import OrgModelForm
|
from orgs.mixins.forms import OrgModelForm
|
||||||
from ..models import AdminUser, SystemUser
|
from ..models import AdminUser, SystemUser
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.1.7 on 2019-07-24 12:02
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0036_auto_20190716_1535'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='adminuser',
|
||||||
|
name='_become_pass',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=128),
|
||||||
|
),
|
||||||
|
]
|
|
@ -13,7 +13,7 @@ from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from .utils import Connectivity
|
from .utils import Connectivity
|
||||||
from orgs.mixins import OrgModelMixin, OrgManager
|
from orgs.mixins.models import OrgModelMixin, OrgManager
|
||||||
|
|
||||||
__all__ = ['Asset', 'ProtocolsMixin']
|
__all__ = ['Asset', 'ProtocolsMixin']
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -345,7 +345,6 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
||||||
else:
|
else:
|
||||||
_nodes = [Node.default_node()]
|
_nodes = [Node.default_node()]
|
||||||
asset.nodes.set(_nodes)
|
asset.nodes.set(_nodes)
|
||||||
asset.system_users = [choice(SystemUser.objects.all()) for i in range(3)]
|
|
||||||
logger.debug('Generate fake asset : %s' % asset.ip)
|
logger.debug('Generate fake asset : %s' % asset.ip)
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
print('Error continue')
|
print('Error continue')
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orgs.mixins import OrgManager
|
from orgs.mixins.models import OrgManager
|
||||||
from .base import AssetUser
|
from .base import AssetUser
|
||||||
|
|
||||||
__all__ = ['AuthBook']
|
__all__ = ['AuthBook']
|
||||||
|
|
|
@ -15,7 +15,7 @@ from common.utils import (
|
||||||
)
|
)
|
||||||
from common.validators import alphanumeric
|
from common.validators import alphanumeric
|
||||||
from common import fields
|
from common import fields
|
||||||
from orgs.mixins import OrgModelMixin
|
from orgs.mixins.models import OrgModelMixin
|
||||||
from .utils import private_key_validator, Connectivity
|
from .utils import private_key_validator, Connectivity
|
||||||
|
|
||||||
signer = get_signer()
|
signer = get_signer()
|
||||||
|
|
|
@ -7,7 +7,7 @@ from django.db import models
|
||||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orgs.mixins import OrgModelMixin
|
from orgs.mixins.models import OrgModelMixin
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|
|
@ -9,7 +9,7 @@ import paramiko
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orgs.mixins import OrgModelMixin
|
from orgs.mixins.models import OrgModelMixin
|
||||||
from .base import AssetUser
|
from .base import AssetUser
|
||||||
|
|
||||||
__all__ = ['Domain', 'Gateway']
|
__all__ = ['Domain', 'Gateway']
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
import uuid
|
import uuid
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from orgs.mixins import OrgModelMixin
|
from orgs.mixins.models import OrgModelMixin
|
||||||
|
|
||||||
|
|
||||||
class Label(OrgModelMixin):
|
class Label(OrgModelMixin):
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#
|
#
|
||||||
import uuid
|
import uuid
|
||||||
import re
|
import re
|
||||||
|
import time
|
||||||
|
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
@ -9,10 +10,11 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils.translation import ugettext
|
from django.utils.translation import ugettext
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
|
||||||
from orgs.mixins import OrgModelMixin, OrgManager
|
from orgs.mixins.models import OrgModelMixin, OrgManager
|
||||||
from orgs.utils import set_current_org, get_current_org
|
from orgs.utils import set_current_org, get_current_org
|
||||||
from orgs.models import Organization
|
from orgs.models import Organization
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['Node']
|
__all__ = ['Node']
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,58 +23,81 @@ class NodeQuerySet(models.QuerySet):
|
||||||
raise PermissionError("Bulk delete node deny")
|
raise PermissionError("Bulk delete node deny")
|
||||||
|
|
||||||
|
|
||||||
|
class TreeMixin:
|
||||||
|
time_tree_updated = None
|
||||||
|
time_tree_updated_cache_key = 'NODE_TREE_CREATED_AT'
|
||||||
|
tree_cache_time = 3600
|
||||||
|
_tree_service = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tree(cls):
|
||||||
|
# Todo: 有待优化, 因为每次刷新都会导致其他节点的tree失效
|
||||||
|
# Todo: ungroup node
|
||||||
|
# TOdo: 游离的资产,在树上显示的数量不对
|
||||||
|
# Todo: api key页面有bug
|
||||||
|
from ..utils import TreeService
|
||||||
|
cache_updated_time = cls.get_cache_time()
|
||||||
|
if not cls.time_tree_updated or \
|
||||||
|
cache_updated_time != cls.time_tree_updated:
|
||||||
|
t = TreeService.new()
|
||||||
|
cls.update_cache_tree(t)
|
||||||
|
return t
|
||||||
|
return cls._tree_service
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_cache_time(cls):
|
||||||
|
return cache.get(cls.time_tree_updated_cache_key)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update_cache_tree(cls, t):
|
||||||
|
cls._tree_service = t
|
||||||
|
now = time.time()
|
||||||
|
cls.time_tree_updated = now
|
||||||
|
cache.set(cls.time_tree_updated_cache_key, now, cls.tree_cache_time)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def expire_cache_tree(cls):
|
||||||
|
cache.delete(cls.time_tree_updated_cache_key)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def refresh_tree(cls):
|
||||||
|
cls.expire_cache_tree()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _tree(self):
|
||||||
|
return self.__class__.tree()
|
||||||
|
|
||||||
|
|
||||||
class FamilyMixin:
|
class FamilyMixin:
|
||||||
_parents = None
|
__parents = None
|
||||||
_children = None
|
__children = None
|
||||||
_all_children = None
|
__all_children = None
|
||||||
is_node = True
|
is_node = True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def children(self):
|
def children(self):
|
||||||
if self._children:
|
return self.get_children(with_self=False)
|
||||||
return self._children
|
|
||||||
pattern = r'^{0}:[0-9]+$'.format(self.key)
|
|
||||||
return Node.objects.filter(key__regex=pattern)
|
|
||||||
|
|
||||||
@children.setter
|
|
||||||
def children(self, value):
|
|
||||||
self._children = value
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def all_children(self):
|
def all_children(self):
|
||||||
if self._all_children:
|
return self.get_all_children(with_self=False)
|
||||||
return self._all_children
|
|
||||||
pattern = r'^{0}:'.format(self.key)
|
|
||||||
return Node.objects.filter(
|
|
||||||
key__regex=pattern
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_children(self, with_self=False):
|
def get_children(self, with_self=False):
|
||||||
children = list(self.children)
|
pattern = r'^{0}:[0-9]+$'.format(self.key)
|
||||||
if with_self:
|
if with_self:
|
||||||
children.append(self)
|
pattern += r'|^{0}$'.format(self.key)
|
||||||
return children
|
return Node.objects.filter(key__regex=pattern)
|
||||||
|
|
||||||
def get_all_children(self, with_self=False):
|
def get_all_children(self, with_self=False):
|
||||||
children = self.all_children
|
pattern = r'^{0}:'.format(self.key)
|
||||||
if with_self:
|
if with_self:
|
||||||
children = list(children)
|
pattern += r'|^{0}$'.format(self.key)
|
||||||
children.append(self)
|
children = Node.objects.filter(key__regex=pattern)
|
||||||
return children
|
return children
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parents(self):
|
def parents(self):
|
||||||
if self._parents:
|
return self.get_ancestor(with_self=False)
|
||||||
return self._parents
|
|
||||||
ancestor_keys = self.get_ancestor_keys()
|
|
||||||
ancestor = Node.objects.filter(
|
|
||||||
key__in=ancestor_keys
|
|
||||||
).order_by('key')
|
|
||||||
return ancestor
|
|
||||||
|
|
||||||
@parents.setter
|
|
||||||
def parents(self, value):
|
|
||||||
self._parents = value
|
|
||||||
|
|
||||||
def get_ancestor(self, with_self=False):
|
def get_ancestor(self, with_self=False):
|
||||||
parents = self.parents
|
parents = self.parents
|
||||||
|
@ -83,15 +108,10 @@ class FamilyMixin:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parent(self):
|
def parent(self):
|
||||||
if self._parents:
|
|
||||||
return self._parents[0]
|
|
||||||
if self.is_root():
|
if self.is_root():
|
||||||
return self
|
return self
|
||||||
try:
|
parent_key = self.parent_key
|
||||||
parent = Node.objects.get(key=self.parent_key)
|
return Node.objects.get(key=parent_key)
|
||||||
return parent
|
|
||||||
except Node.DoesNotExist:
|
|
||||||
return Node.root()
|
|
||||||
|
|
||||||
@parent.setter
|
@parent.setter
|
||||||
def parent(self, parent):
|
def parent(self, parent):
|
||||||
|
@ -107,7 +127,7 @@ class FamilyMixin:
|
||||||
child.save()
|
child.save()
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def get_sibling(self, with_self=False):
|
def get_siblings(self, with_self=False):
|
||||||
key = ':'.join(self.key.split(':')[:-1])
|
key = ':'.join(self.key.split(':')[:-1])
|
||||||
pattern = r'^{}:[0-9]+$'.format(key)
|
pattern = r'^{}:[0-9]+$'.format(key)
|
||||||
sibling = Node.objects.filter(
|
sibling = Node.objects.filter(
|
||||||
|
@ -133,12 +153,11 @@ class FamilyMixin:
|
||||||
return parent_keys
|
return parent_keys
|
||||||
|
|
||||||
def is_children(self, other):
|
def is_children(self, other):
|
||||||
pattern = re.compile(r'^{0}:[0-9]+$'.format(self.key))
|
pattern = r'^{0}:[0-9]+$'.format(self.key)
|
||||||
return pattern.match(other.key)
|
return re.match(pattern, other.key)
|
||||||
|
|
||||||
def is_parent(self, other):
|
def is_parent(self, other):
|
||||||
pattern = re.compile(r'^{0}:[0-9]+$'.format(other.key))
|
return other.is_children(self)
|
||||||
return pattern.match(self.key)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parent_key(self):
|
def parent_key(self):
|
||||||
|
@ -158,46 +177,27 @@ class FamilyMixin:
|
||||||
|
|
||||||
|
|
||||||
class FullValueMixin:
|
class FullValueMixin:
|
||||||
_full_value_cache_key = '_NODE_VALUE_{}'
|
_full_value = None
|
||||||
_full_value = ''
|
|
||||||
key = ''
|
key = ''
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def full_value(self):
|
def full_value(self):
|
||||||
if self._full_value:
|
|
||||||
return self._full_value
|
|
||||||
key = self._full_value_cache_key.format(self.key)
|
|
||||||
cached = cache.get(key)
|
|
||||||
if cached:
|
|
||||||
return cached
|
|
||||||
if self.is_root():
|
if self.is_root():
|
||||||
return self.value
|
return self.value
|
||||||
parent_full_value = self.parent.full_value
|
if self._full_value is not None:
|
||||||
value = parent_full_value + ' / ' + self.value
|
return self._full_value
|
||||||
self.full_value = value
|
print("Get full value")
|
||||||
|
value = self._tree.get_node_full_tag(self.key)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@full_value.setter
|
|
||||||
def full_value(self, value):
|
|
||||||
self._full_value = value
|
|
||||||
key = self._full_value_cache_key.format(self.key)
|
|
||||||
cache.set(key, value, 3600*24)
|
|
||||||
|
|
||||||
def expire_full_value(self):
|
class NodeAssetsMixin:
|
||||||
key = self._full_value_cache_key.format(self.key)
|
|
||||||
cache.delete_pattern(key+'*')
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def expire_nodes_full_value(cls, nodes=None):
|
|
||||||
key = cls._full_value_cache_key.format('*')
|
|
||||||
cache.delete_pattern(key+'*')
|
|
||||||
|
|
||||||
|
|
||||||
class AssetsAmountMixin:
|
|
||||||
_assets_amount_cache_key = '_NODE_ASSETS_AMOUNT_{}'
|
_assets_amount_cache_key = '_NODE_ASSETS_AMOUNT_{}'
|
||||||
|
_assets_cache_key = '_NODE_ASSETS_{}'
|
||||||
_assets_amount = None
|
_assets_amount = None
|
||||||
key = ''
|
key = ''
|
||||||
cache_time = 3600 * 24 * 7
|
cache_time = 3600 * 24 * 7
|
||||||
|
id = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def assets_amount(self):
|
def assets_amount(self):
|
||||||
|
@ -207,40 +207,37 @@ class AssetsAmountMixin:
|
||||||
"""
|
"""
|
||||||
if self._assets_amount is not None:
|
if self._assets_amount is not None:
|
||||||
return self._assets_amount
|
return self._assets_amount
|
||||||
cache_key = self._assets_amount_cache_key.format(self.key)
|
amount = self._tree.assets_amount(self.key)
|
||||||
cached = cache.get(cache_key)
|
return amount
|
||||||
if cached is not None:
|
|
||||||
return cached
|
|
||||||
assets_amount = self.get_all_assets().count()
|
|
||||||
self.assets_amount = assets_amount
|
|
||||||
return assets_amount
|
|
||||||
|
|
||||||
@assets_amount.setter
|
# TOdo: 是否依赖tree
|
||||||
def assets_amount(self, value):
|
def get_all_assets(self):
|
||||||
self._assets_amount = value
|
from .asset import Asset
|
||||||
cache_key = self._assets_amount_cache_key.format(self.key)
|
if self.is_root():
|
||||||
cache.set(cache_key, value, self.cache_time)
|
return Asset.objects.filter(org_id=self.org_id)
|
||||||
|
assets_ids = self._tree.all_assets(self.key)
|
||||||
|
return Asset.objects.filter(id__in=assets_ids)
|
||||||
|
|
||||||
def expire_assets_amount(self):
|
def assets_ids(self):
|
||||||
ancestor_keys = self.get_ancestor_keys(with_self=True)
|
assets_ids = self._tree.assets(self.key)
|
||||||
cache_keys = [self._assets_amount_cache_key.format(k) for k in
|
return assets_ids
|
||||||
ancestor_keys]
|
|
||||||
cache.delete_many(cache_keys)
|
|
||||||
|
|
||||||
@classmethod
|
def get_assets(self):
|
||||||
def expire_nodes_assets_amount(cls, nodes=None):
|
from .asset import Asset
|
||||||
key = cls._assets_amount_cache_key.format('*')
|
if self.is_default_node():
|
||||||
cache.delete_pattern(key)
|
assets = Asset.objects.filter(Q(nodes__id=self.id) | Q(nodes__isnull=True))
|
||||||
|
else:
|
||||||
|
assets = Asset.objects.filter(id=self.assets_ids())
|
||||||
|
return assets.distinct()
|
||||||
|
|
||||||
@classmethod
|
def get_valid_assets(self):
|
||||||
def refresh_nodes(cls):
|
return self.get_assets().valid()
|
||||||
from ..utils import NodeUtil
|
|
||||||
util = NodeUtil(with_assets_amount=True)
|
def get_all_valid_assets(self):
|
||||||
util.set_assets_amount()
|
return self.get_all_assets().valid()
|
||||||
util.set_full_value()
|
|
||||||
|
|
||||||
|
|
||||||
class Node(OrgModelMixin, FamilyMixin, FullValueMixin, AssetsAmountMixin):
|
class Node(OrgModelMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixin):
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1'
|
key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1'
|
||||||
value = models.CharField(max_length=128, verbose_name=_("Value"))
|
value = models.CharField(max_length=128, verbose_name=_("Value"))
|
||||||
|
@ -256,7 +253,7 @@ class Node(OrgModelMixin, FamilyMixin, FullValueMixin, AssetsAmountMixin):
|
||||||
ordering = ['key']
|
ordering = ['key']
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.full_value
|
return self.value
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if not other:
|
if not other:
|
||||||
|
@ -316,31 +313,9 @@ class Node(OrgModelMixin, FamilyMixin, FullValueMixin, AssetsAmountMixin):
|
||||||
child = self.__class__.objects.create(id=_id, key=child_key, value=value)
|
child = self.__class__.objects.create(id=_id, key=child_key, value=value)
|
||||||
return child
|
return child
|
||||||
|
|
||||||
def get_assets(self):
|
@classmethod
|
||||||
from .asset import Asset
|
def refresh_nodes(cls):
|
||||||
if self.is_default_node():
|
cls.refresh_tree()
|
||||||
assets = Asset.objects.filter(Q(nodes__id=self.id) | Q(nodes__isnull=True))
|
|
||||||
else:
|
|
||||||
assets = Asset.objects.filter(nodes__id=self.id)
|
|
||||||
return assets.distinct()
|
|
||||||
|
|
||||||
def get_valid_assets(self):
|
|
||||||
return self.get_assets().valid()
|
|
||||||
|
|
||||||
def get_all_assets(self):
|
|
||||||
from .asset import Asset
|
|
||||||
pattern = r'^{0}$|^{0}:'.format(self.key)
|
|
||||||
args = []
|
|
||||||
kwargs = {}
|
|
||||||
if self.is_root():
|
|
||||||
args.append(Q(nodes__key__regex=pattern) | Q(nodes=None))
|
|
||||||
else:
|
|
||||||
kwargs['nodes__key__regex'] = pattern
|
|
||||||
assets = Asset.objects.filter(*args, **kwargs).distinct()
|
|
||||||
return assets
|
|
||||||
|
|
||||||
def get_all_valid_assets(self):
|
|
||||||
return self.get_all_assets().valid()
|
|
||||||
|
|
||||||
def is_default_node(self):
|
def is_default_node(self):
|
||||||
return self.is_root() and self.key == '1'
|
return self.is_root() and self.key == '1'
|
||||||
|
@ -410,19 +385,20 @@ class Node(OrgModelMixin, FamilyMixin, FullValueMixin, AssetsAmountMixin):
|
||||||
return
|
return
|
||||||
return super().delete(using=using, keep_parents=keep_parents)
|
return super().delete(using=using, keep_parents=keep_parents)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_queryset(cls):
|
|
||||||
from ..utils import NodeUtil
|
|
||||||
util = NodeUtil()
|
|
||||||
return sorted(util.nodes)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def generate_fake(cls, count=100):
|
def generate_fake(cls, count=100):
|
||||||
import random
|
import random
|
||||||
org = get_current_org()
|
org = get_current_org()
|
||||||
if not org or not org.is_real():
|
if not org or not org.is_real():
|
||||||
Organization.default().change_to()
|
Organization.default().change_to()
|
||||||
|
i = 0
|
||||||
|
while i < count:
|
||||||
|
nodes = list(cls.objects.all())
|
||||||
|
if count > 100:
|
||||||
|
length = 100
|
||||||
|
else:
|
||||||
|
length = count
|
||||||
|
|
||||||
for i in range(count):
|
for i in range(length):
|
||||||
node = random.choice(cls.objects.all())
|
node = random.choice(nodes)
|
||||||
node.create_child('Node {}'.format(i))
|
node.create_child('Node {}'.format(i))
|
||||||
|
|
|
@ -31,7 +31,7 @@ class AdminUser(AssetUser):
|
||||||
become = models.BooleanField(default=True)
|
become = models.BooleanField(default=True)
|
||||||
become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4)
|
become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4)
|
||||||
become_user = models.CharField(default='root', max_length=64)
|
become_user = models.CharField(default='root', max_length=64)
|
||||||
_become_pass = models.CharField(default='', max_length=128)
|
_become_pass = models.CharField(default='', blank=True, max_length=128)
|
||||||
CONNECTIVITY_CACHE_KEY = '_ADMIN_USER_CONNECTIVE_{}'
|
CONNECTIVITY_CACHE_KEY = '_ADMIN_USER_CONNECTIVE_{}'
|
||||||
_prefer = "admin_user"
|
_prefer = "admin_user"
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ from rest_framework import serializers
|
||||||
from common.serializers import AdaptedBulkListSerializer
|
from common.serializers import AdaptedBulkListSerializer
|
||||||
|
|
||||||
from ..models import Node, AdminUser
|
from ..models import Node, AdminUser
|
||||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
|
|
||||||
from .base import AuthSerializer, AuthSerializerMixin
|
from .base import AuthSerializer, AuthSerializerMixin
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ from rest_framework import serializers
|
||||||
from django.db.models import Prefetch
|
from django.db.models import Prefetch
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
from common.serializers import AdaptedBulkListSerializer
|
from common.serializers import AdaptedBulkListSerializer
|
||||||
from ..models import Asset, Node, Label
|
from ..models import Asset, Node, Label
|
||||||
from .base import ConnectivitySerializer
|
from .base import ConnectivitySerializer
|
||||||
|
|
|
@ -5,7 +5,7 @@ from django.utils.translation import ugettext as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from common.serializers import AdaptedBulkListSerializer
|
from common.serializers import AdaptedBulkListSerializer
|
||||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
from ..models import AuthBook, Asset
|
from ..models import AuthBook, Asset
|
||||||
from ..backends import AssetUserManager
|
from ..backends import AssetUserManager
|
||||||
from .base import ConnectivitySerializer, AuthSerializerMixin
|
from .base import ConnectivitySerializer, AuthSerializerMixin
|
||||||
|
|
|
@ -7,7 +7,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from common.fields import ChoiceDisplayField
|
from common.fields import ChoiceDisplayField
|
||||||
from common.serializers import AdaptedBulkListSerializer
|
from common.serializers import AdaptedBulkListSerializer
|
||||||
from ..models import CommandFilter, CommandFilterRule, SystemUser
|
from ..models import CommandFilter, CommandFilterRule, SystemUser
|
||||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
|
|
||||||
|
|
||||||
class CommandFilterSerializer(BulkOrgResourceModelSerializer):
|
class CommandFilterSerializer(BulkOrgResourceModelSerializer):
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from common.serializers import AdaptedBulkListSerializer
|
from common.serializers import AdaptedBulkListSerializer
|
||||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
|
|
||||||
from ..models import Domain, Gateway
|
from ..models import Domain, Gateway
|
||||||
from .base import AuthSerializerMixin
|
from .base import AuthSerializerMixin
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from common.serializers import AdaptedBulkListSerializer
|
from common.serializers import AdaptedBulkListSerializer
|
||||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
|
|
||||||
from ..models import Label
|
from ..models import Label
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
from ..models import Asset, Node
|
from ..models import Asset, Node
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,22 +13,21 @@ __all__ = [
|
||||||
|
|
||||||
|
|
||||||
class NodeSerializer(BulkOrgResourceModelSerializer):
|
class NodeSerializer(BulkOrgResourceModelSerializer):
|
||||||
assets_amount = serializers.IntegerField(read_only=True)
|
|
||||||
name = serializers.ReadOnlyField(source='value')
|
name = serializers.ReadOnlyField(source='value')
|
||||||
|
value = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("value"))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Node
|
model = Node
|
||||||
only_fields = ['id', 'key', 'value', 'org_id']
|
only_fields = ['id', 'key', 'value', 'org_id']
|
||||||
fields = only_fields + ['name', 'assets_amount']
|
fields = only_fields + ['name', 'full_value']
|
||||||
read_only_fields = [
|
read_only_fields = ['key', 'org_id']
|
||||||
'key', 'name', 'assets_amount', 'org_id',
|
|
||||||
]
|
|
||||||
|
|
||||||
def validate_value(self, data):
|
def validate_value(self, data):
|
||||||
instance = self.instance if self.instance else Node.root()
|
if not self.instance and not data:
|
||||||
children = instance.parent.get_children()
|
return data
|
||||||
children_values = [node.value for node in children if node != instance]
|
instance = self.instance
|
||||||
if data in children_values:
|
siblings = instance.get_siblings()
|
||||||
|
if siblings.filter(value=data):
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
_('The same level node name cannot be the same')
|
_('The same level node name cannot be the same')
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,7 +4,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from common.serializers import AdaptedBulkListSerializer
|
from common.serializers import AdaptedBulkListSerializer
|
||||||
from common.utils import ssh_pubkey_gen
|
from common.utils import ssh_pubkey_gen
|
||||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
from ..models import SystemUser
|
from ..models import SystemUser
|
||||||
from .base import AuthSerializer, AuthSerializerMixin
|
from .base import AuthSerializer, AuthSerializerMixin
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from django.db.models.signals import post_save, m2m_changed, post_delete
|
from django.db.models.signals import post_save, m2m_changed, pre_delete
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
|
@ -38,15 +38,13 @@ def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
|
||||||
test_asset_conn_on_created(instance)
|
test_asset_conn_on_created(instance)
|
||||||
|
|
||||||
# 过期节点资产数量
|
# 过期节点资产数量
|
||||||
nodes = instance.nodes.all()
|
Node.refresh_nodes()
|
||||||
Node.expire_nodes_assets_amount(nodes)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=Asset, dispatch_uid="my_unique_identifier")
|
@receiver(pre_delete, sender=Asset, dispatch_uid="my_unique_identifier")
|
||||||
def on_asset_delete(sender, instance=None, **kwargs):
|
def on_asset_delete(sender, instance=None, **kwargs):
|
||||||
# 过期节点资产数量
|
# 过期节点资产数量
|
||||||
nodes = instance.nodes.all()
|
Node.refresh_nodes()
|
||||||
Node.expire_nodes_assets_amount(nodes)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=SystemUser, dispatch_uid="my_unique_identifier")
|
@receiver(post_save, sender=SystemUser, dispatch_uid="my_unique_identifier")
|
||||||
|
@ -80,19 +78,18 @@ def on_asset_node_changed(sender, instance=None, **kwargs):
|
||||||
logger.debug("Asset nodes change signal received")
|
logger.debug("Asset nodes change signal received")
|
||||||
Asset.expire_all_nodes_keys_cache()
|
Asset.expire_all_nodes_keys_cache()
|
||||||
if isinstance(instance, Asset):
|
if isinstance(instance, Asset):
|
||||||
if kwargs['action'] == 'pre_remove':
|
# nodes = []
|
||||||
nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
# if kwargs['action'] == 'pre_remove':
|
||||||
Node.expire_nodes_assets_amount(nodes)
|
# nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
||||||
if kwargs['action'] == 'post_add':
|
if kwargs['action'] == 'post_add':
|
||||||
nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
||||||
Node.expire_nodes_assets_amount(nodes)
|
|
||||||
system_users_assets = defaultdict(set)
|
system_users_assets = defaultdict(set)
|
||||||
system_users = SystemUser.objects.filter(nodes__in=nodes)
|
system_users = SystemUser.objects.filter(nodes__in=nodes)
|
||||||
# 清理节点缓存
|
|
||||||
for system_user in system_users:
|
for system_user in system_users:
|
||||||
system_users_assets[system_user].update({instance})
|
system_users_assets[system_user].update({instance})
|
||||||
for system_user, assets in system_users_assets.items():
|
for system_user, assets in system_users_assets.items():
|
||||||
system_user.assets.add(*tuple(assets))
|
system_user.assets.add(*tuple(assets))
|
||||||
|
Node.refresh_nodes()
|
||||||
|
|
||||||
|
|
||||||
@receiver(m2m_changed, sender=Asset.nodes.through)
|
@receiver(m2m_changed, sender=Asset.nodes.through)
|
||||||
|
@ -100,7 +97,6 @@ def on_node_assets_changed(sender, instance=None, **kwargs):
|
||||||
if isinstance(instance, Node):
|
if isinstance(instance, Node):
|
||||||
logger.debug("Node assets change signal {} received".format(instance))
|
logger.debug("Node assets change signal {} received".format(instance))
|
||||||
# 当节点和资产关系发生改变时,过期资产数量缓存
|
# 当节点和资产关系发生改变时,过期资产数量缓存
|
||||||
instance.expire_assets_amount()
|
|
||||||
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
||||||
if kwargs['action'] == 'post_add':
|
if kwargs['action'] == 'post_add':
|
||||||
# 重新关联系统用户和资产的关系
|
# 重新关联系统用户和资产的关系
|
||||||
|
@ -112,7 +108,7 @@ def on_node_assets_changed(sender, instance=None, **kwargs):
|
||||||
@receiver(post_save, sender=Node)
|
@receiver(post_save, sender=Node)
|
||||||
def on_node_update_or_created(sender, instance=None, created=False, **kwargs):
|
def on_node_update_or_created(sender, instance=None, created=False, **kwargs):
|
||||||
if instance and not created:
|
if instance and not created:
|
||||||
instance.expire_full_value()
|
Node.refresh_nodes()
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=AuthBook)
|
@receiver(post_save, sender=AuthBook)
|
||||||
|
|
|
@ -24,7 +24,7 @@ FORKS = 10
|
||||||
TIMEOUT = 60
|
TIMEOUT = 60
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
CACHE_MAX_TIME = 60*60*2
|
CACHE_MAX_TIME = 60*60*2
|
||||||
disk_pattern = re.compile(r'^hd|sd|xvd|vd')
|
disk_pattern = re.compile(r'^hd|sd|xvd|vd|nv')
|
||||||
PERIOD_TASK = os.environ.get("PERIOD_TASK", "on")
|
PERIOD_TASK = os.environ.get("PERIOD_TASK", "on")
|
||||||
|
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ def clean_hosts_by_protocol(system_user, assets):
|
||||||
return hosts
|
return hosts
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task(queue="ansible")
|
||||||
def set_assets_hardware_info(assets, result, **kwargs):
|
def set_assets_hardware_info(assets, result, **kwargs):
|
||||||
"""
|
"""
|
||||||
Using ops task run result, to update asset info
|
Using ops task run result, to update asset info
|
||||||
|
@ -106,7 +106,7 @@ def set_assets_hardware_info(assets, result, **kwargs):
|
||||||
for dev, dev_info in info.get('ansible_devices', {}).items():
|
for dev, dev_info in info.get('ansible_devices', {}).items():
|
||||||
if disk_pattern.match(dev) and dev_info['removable'] == '0':
|
if disk_pattern.match(dev) and dev_info['removable'] == '0':
|
||||||
disk_info[dev] = dev_info['size']
|
disk_info[dev] = dev_info['size']
|
||||||
___disk_total = '%s %s' % sum_capacity(disk_info.values())
|
___disk_total = '%.1f %s' % sum_capacity(disk_info.values())
|
||||||
___disk_info = json.dumps(disk_info)
|
___disk_info = json.dumps(disk_info)
|
||||||
|
|
||||||
# ___platform = info.get('ansible_system', 'Unknown')
|
# ___platform = info.get('ansible_system', 'Unknown')
|
||||||
|
@ -148,7 +148,7 @@ def update_assets_hardware_info_util(assets, task_name=None):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task(queue="ansible")
|
||||||
def update_asset_hardware_info_manual(asset):
|
def update_asset_hardware_info_manual(asset):
|
||||||
task_name = _("Update asset hardware info: {}").format(asset.hostname)
|
task_name = _("Update asset hardware info: {}").format(asset.hostname)
|
||||||
update_assets_hardware_info_util(
|
update_assets_hardware_info_util(
|
||||||
|
@ -156,7 +156,7 @@ def update_asset_hardware_info_manual(asset):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task(queue="ansible")
|
||||||
def update_assets_hardware_info_period():
|
def update_assets_hardware_info_period():
|
||||||
"""
|
"""
|
||||||
Update asset hardware period task
|
Update asset hardware period task
|
||||||
|
@ -170,7 +170,7 @@ def update_assets_hardware_info_period():
|
||||||
## ADMIN USER CONNECTIVE ##
|
## ADMIN USER CONNECTIVE ##
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task(queue="ansible")
|
||||||
def test_asset_connectivity_util(assets, task_name=None):
|
def test_asset_connectivity_util(assets, task_name=None):
|
||||||
from ops.utils import update_or_create_ansible_task
|
from ops.utils import update_or_create_ansible_task
|
||||||
|
|
||||||
|
@ -227,7 +227,7 @@ def test_asset_connectivity_util(assets, task_name=None):
|
||||||
return results_summary
|
return results_summary
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task(queue="ansible")
|
||||||
def test_asset_connectivity_manual(asset):
|
def test_asset_connectivity_manual(asset):
|
||||||
task_name = _("Test assets connectivity: {}").format(asset)
|
task_name = _("Test assets connectivity: {}").format(asset)
|
||||||
summary = test_asset_connectivity_util([asset], task_name=task_name)
|
summary = test_asset_connectivity_util([asset], task_name=task_name)
|
||||||
|
@ -238,7 +238,7 @@ def test_asset_connectivity_manual(asset):
|
||||||
return True, ""
|
return True, ""
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task(queue="ansible")
|
||||||
def test_admin_user_connectivity_util(admin_user, task_name):
|
def test_admin_user_connectivity_util(admin_user, task_name):
|
||||||
"""
|
"""
|
||||||
Test asset admin user can connect or not. Using ansible api do that
|
Test asset admin user can connect or not. Using ansible api do that
|
||||||
|
@ -254,7 +254,7 @@ def test_admin_user_connectivity_util(admin_user, task_name):
|
||||||
return summary
|
return summary
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task(queue="ansible")
|
||||||
@register_as_period_task(interval=3600)
|
@register_as_period_task(interval=3600)
|
||||||
def test_admin_user_connectivity_period():
|
def test_admin_user_connectivity_period():
|
||||||
"""
|
"""
|
||||||
|
@ -276,7 +276,7 @@ def test_admin_user_connectivity_period():
|
||||||
cache.set(key, 1, 60*40)
|
cache.set(key, 1, 60*40)
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task(queue="ansible")
|
||||||
def test_admin_user_connectivity_manual(admin_user):
|
def test_admin_user_connectivity_manual(admin_user):
|
||||||
task_name = _("Test admin user connectivity: {}").format(admin_user.name)
|
task_name = _("Test admin user connectivity: {}").format(admin_user.name)
|
||||||
test_admin_user_connectivity_util(admin_user, task_name)
|
test_admin_user_connectivity_util(admin_user, task_name)
|
||||||
|
@ -286,7 +286,7 @@ def test_admin_user_connectivity_manual(admin_user):
|
||||||
## System user connective ##
|
## System user connective ##
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task(queue="ansible")
|
||||||
def test_system_user_connectivity_util(system_user, assets, task_name):
|
def test_system_user_connectivity_util(system_user, assets, task_name):
|
||||||
"""
|
"""
|
||||||
Test system cant connect his assets or not.
|
Test system cant connect his assets or not.
|
||||||
|
@ -344,14 +344,14 @@ def test_system_user_connectivity_util(system_user, assets, task_name):
|
||||||
return results_summary
|
return results_summary
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task(queue="ansible")
|
||||||
def test_system_user_connectivity_manual(system_user):
|
def test_system_user_connectivity_manual(system_user):
|
||||||
task_name = _("Test system user connectivity: {}").format(system_user)
|
task_name = _("Test system user connectivity: {}").format(system_user)
|
||||||
assets = system_user.get_all_assets()
|
assets = system_user.get_all_assets()
|
||||||
return test_system_user_connectivity_util(system_user, assets, task_name)
|
return test_system_user_connectivity_util(system_user, assets, task_name)
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task(queue="ansible")
|
||||||
def test_system_user_connectivity_a_asset(system_user, asset):
|
def test_system_user_connectivity_a_asset(system_user, asset):
|
||||||
task_name = _("Test system user connectivity: {} => {}").format(
|
task_name = _("Test system user connectivity: {} => {}").format(
|
||||||
system_user, asset
|
system_user, asset
|
||||||
|
@ -359,7 +359,7 @@ def test_system_user_connectivity_a_asset(system_user, asset):
|
||||||
return test_system_user_connectivity_util(system_user, [asset], task_name)
|
return test_system_user_connectivity_util(system_user, [asset], task_name)
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task(queue="ansible")
|
||||||
def test_system_user_connectivity_period():
|
def test_system_user_connectivity_period():
|
||||||
if PERIOD_TASK != "on":
|
if PERIOD_TASK != "on":
|
||||||
logger.debug("Period task disabled, test system user connectivity pass")
|
logger.debug("Period task disabled, test system user connectivity pass")
|
||||||
|
@ -483,7 +483,7 @@ def get_push_system_user_tasks(host, system_user):
|
||||||
return tasks
|
return tasks
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task(queue="ansible")
|
||||||
def push_system_user_util(system_user, assets, task_name):
|
def push_system_user_util(system_user, assets, task_name):
|
||||||
from ops.utils import update_or_create_ansible_task
|
from ops.utils import update_or_create_ansible_task
|
||||||
if not system_user.is_need_push():
|
if not system_user.is_need_push():
|
||||||
|
@ -519,14 +519,14 @@ def push_system_user_util(system_user, assets, task_name):
|
||||||
task.run()
|
task.run()
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task(queue="ansible")
|
||||||
def push_system_user_to_assets_manual(system_user):
|
def push_system_user_to_assets_manual(system_user):
|
||||||
assets = system_user.get_all_assets()
|
assets = system_user.get_all_assets()
|
||||||
task_name = _("Push system users to assets: {}").format(system_user.name)
|
task_name = _("Push system users to assets: {}").format(system_user.name)
|
||||||
return push_system_user_util(system_user, assets, task_name=task_name)
|
return push_system_user_util(system_user, assets, task_name=task_name)
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task(queue="ansible")
|
||||||
def push_system_user_a_asset_manual(system_user, asset):
|
def push_system_user_a_asset_manual(system_user, asset):
|
||||||
task_name = _("Push system users to asset: {} => {}").format(
|
task_name = _("Push system users to asset: {} => {}").format(
|
||||||
system_user.name, asset
|
system_user.name, asset
|
||||||
|
@ -534,7 +534,7 @@ def push_system_user_a_asset_manual(system_user, asset):
|
||||||
return push_system_user_util(system_user, [asset], task_name=task_name)
|
return push_system_user_util(system_user, [asset], task_name=task_name)
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task(queue="ansible")
|
||||||
def push_system_user_to_assets(system_user, assets):
|
def push_system_user_to_assets(system_user, assets):
|
||||||
task_name = _("Push system users to assets: {}").format(system_user.name)
|
task_name = _("Push system users to assets: {}").format(system_user.name)
|
||||||
return push_system_user_util(system_user, assets, task_name)
|
return push_system_user_util(system_user, assets, task_name)
|
||||||
|
@ -569,7 +569,7 @@ def get_test_asset_user_connectivity_tasks(asset):
|
||||||
return tasks
|
return tasks
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task(queue="ansible")
|
||||||
def test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=False):
|
def test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=False):
|
||||||
"""
|
"""
|
||||||
:param asset_user: <AuthBook>对象
|
:param asset_user: <AuthBook>对象
|
||||||
|
@ -602,7 +602,7 @@ def test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=False)
|
||||||
asset_user.set_connectivity(summary)
|
asset_user.set_connectivity(summary)
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task(queue="ansible")
|
||||||
def test_asset_users_connectivity_manual(asset_users, run_as_admin=False):
|
def test_asset_users_connectivity_manual(asset_users, run_as_admin=False):
|
||||||
"""
|
"""
|
||||||
:param asset_users: <AuthBook>对象
|
:param asset_users: <AuthBook>对象
|
||||||
|
|
|
@ -236,7 +236,8 @@ function onBodyMouseDown(event){
|
||||||
}
|
}
|
||||||
|
|
||||||
function onRename(event, treeId, treeNode, isCancel){
|
function onRename(event, treeId, treeNode, isCancel){
|
||||||
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node_id);
|
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}"
|
||||||
|
.replace("{{ DEFAULT_PK }}", current_node_id);
|
||||||
var data = {"value": treeNode.name};
|
var data = {"value": treeNode.name};
|
||||||
if (isCancel){
|
if (isCancel){
|
||||||
return
|
return
|
||||||
|
@ -247,10 +248,13 @@ function onRename(event, treeId, treeNode, isCancel){
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
success_message: "{% trans 'Rename success' %}",
|
success_message: "{% trans 'Rename success' %}",
|
||||||
success: function () {
|
success: function () {
|
||||||
treeNode.name = treeNode.name + ' (' + treeNode.meta.node.assets_amount + ')';
|
var assets_amount = treeNode.meta.node.assets_amount;
|
||||||
|
if (!assets_amount) {
|
||||||
|
assets_amount = 0;
|
||||||
|
}
|
||||||
|
treeNode.name = treeNode.name + ' (' + assets_amount + ')';
|
||||||
zTree.updateNode(treeNode);
|
zTree.updateNode(treeNode);
|
||||||
console.log("Success: " + treeNode.name)
|
},
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,9 +88,9 @@
|
||||||
<form>
|
<form>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2" class="no-borders">
|
<td colspan="2" class="no-borders">
|
||||||
<select data-placeholder="{% trans 'Select nodes' %}" id="nodes_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
|
<select data-placeholder="{% trans 'Select nodes' %}" id="nodes_selected" class="nodes-select2" style="width: 100%" multiple="" tabindex="4">
|
||||||
{% for node in nodes %}
|
{% for node in nodes %}
|
||||||
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node }}</option>
|
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node.full_value }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
|
@ -140,7 +140,8 @@ function replaceNodeAssetsAdminUser(nodes) {
|
||||||
|
|
||||||
jumpserver.nodes_selected = {};
|
jumpserver.nodes_selected = {};
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
$('.select2').select2()
|
var url = "{% url 'api-assets:node-list' %}";
|
||||||
|
nodesSelect2Init(".nodes-select2", url)
|
||||||
.on('select2:select', function(evt) {
|
.on('select2:select', function(evt) {
|
||||||
var data = evt.params.data;
|
var data = evt.params.data;
|
||||||
jumpserver.nodes_selected[data.id] = data.text;
|
jumpserver.nodes_selected[data.id] = data.text;
|
||||||
|
|
|
@ -110,6 +110,8 @@ $(document).ready(function () {
|
||||||
$('.select2').select2({
|
$('.select2').select2({
|
||||||
allowClear: true
|
allowClear: true
|
||||||
});
|
});
|
||||||
|
var url = "{% url 'api-assets:node-list' %}";
|
||||||
|
nodesSelect2Init(".nodes-select2", url);
|
||||||
$(".labels").select2({
|
$(".labels").select2({
|
||||||
allowClear: true,
|
allowClear: true,
|
||||||
templateSelection: format
|
templateSelection: format
|
||||||
|
|
|
@ -195,10 +195,7 @@
|
||||||
<form>
|
<form>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2" class="no-borders">
|
<td colspan="2" class="no-borders">
|
||||||
<select data-placeholder="{% trans 'Nodes' %}" id="groups_selected" class="select2 groups" style="width: 100%" multiple="" tabindex="4">
|
<select data-placeholder="{% trans 'Nodes' %}" id="groups_selected" class="nodes-select2 groups" style="width: 100%" multiple="" tabindex="4">
|
||||||
{% for node in nodes_remain %}
|
|
||||||
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -211,7 +208,7 @@
|
||||||
|
|
||||||
{% for node in asset.nodes.all %}
|
{% for node in asset.nodes.all %}
|
||||||
<tr>
|
<tr>
|
||||||
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node }}</b></td>
|
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node.full_value }}</b></td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn btn-danger pull-right btn-xs btn-leave-node" type="button"><i class="fa fa-minus"></i></button>
|
<button class="btn btn-danger pull-right btn-xs btn-leave-node" type="button"><i class="fa fa-minus"></i></button>
|
||||||
</td>
|
</td>
|
||||||
|
@ -291,7 +288,9 @@ function refreshAssetHardware() {
|
||||||
|
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
$('.select2.groups').select2().on('select2:select', function(evt) {
|
var url = "{% url 'api-assets:node-list' %}";
|
||||||
|
nodesSelect2Init(".nodes-select2", url)
|
||||||
|
.on('select2:select', function(evt) {
|
||||||
var data = evt.params.data;
|
var data = evt.params.data;
|
||||||
jumpserver.nodes_selected[data.id] = data.text;
|
jumpserver.nodes_selected[data.id] = data.text;
|
||||||
}).on('select2:unselect', function(evt) {
|
}).on('select2:unselect', function(evt) {
|
||||||
|
|
|
@ -442,9 +442,10 @@ $(document).ready(function(){
|
||||||
var success = function () {
|
var success = function () {
|
||||||
asset_table.ajax.reload()
|
asset_table.ajax.reload()
|
||||||
};
|
};
|
||||||
|
var url = "{% url 'api-assets:node-remove-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node_id);
|
||||||
|
|
||||||
requestApi({
|
requestApi({
|
||||||
'url': '/api/assets/v1/nodes/' + current_node_id + '/assets/remove/',
|
'url': url,
|
||||||
'method': 'PUT',
|
'method': 'PUT',
|
||||||
'body': JSON.stringify(data),
|
'body': JSON.stringify(data),
|
||||||
'success': success
|
'success': success
|
||||||
|
|
|
@ -88,10 +88,7 @@
|
||||||
<form>
|
<form>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2" class="no-borders">
|
<td colspan="2" class="no-borders">
|
||||||
<select data-placeholder="{% trans 'Add to node' %}" id="node_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
|
<select data-placeholder="{% trans 'Add to node' %}" id="node_selected" class="nodes-select2" style="width: 100%" multiple="" tabindex="4">
|
||||||
{% for node in nodes_remain %}
|
|
||||||
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -104,7 +101,7 @@
|
||||||
|
|
||||||
{% for node in system_user.nodes.all|sort %}
|
{% for node in system_user.nodes.all|sort %}
|
||||||
<tr>
|
<tr>
|
||||||
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node }}</b></td>
|
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node.full_value }}</b></td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn btn-danger pull-right btn-xs btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button>
|
<button class="btn btn-danger pull-right btn-xs btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button>
|
||||||
</td>
|
</td>
|
||||||
|
@ -156,6 +153,8 @@ jumpserver.nodes_selected = {};
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
$('.select2').select2()
|
$('.select2').select2()
|
||||||
|
var url = "{% url 'api-assets:node-list' %}";
|
||||||
|
nodesSelect2Init(".nodes-select2", url)
|
||||||
.on('select2:select', function(evt) {
|
.on('select2:select', function(evt) {
|
||||||
var data = evt.params.data;
|
var data = evt.params.data;
|
||||||
jumpserver.nodes_selected[data.id] = data.text;
|
jumpserver.nodes_selected[data.id] = data.text;
|
||||||
|
|
|
@ -21,9 +21,10 @@
|
||||||
|
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
var treeUrl = "{% url 'api-perms:my-nodes-as-tree' %}?&cache_policy=1";
|
var treeUrl = "{% url 'api-perms:my-nodes-children-as-tree' %}?&cache_policy=1";
|
||||||
var assetTableUrl = "{% url 'api-perms:my-assets' %}?cache_policy=1";
|
var assetTableUrl = "{% url 'api-perms:my-assets' %}?cache_policy=1";
|
||||||
var selectUrl = '{% url "api-perms:my-node-assets" node_id=DEFAULT_PK %}?cache_policy=1&all=1';
|
var selectUrl = '{% url "api-perms:my-node-assets" node_id=DEFAULT_PK %}?cache_policy=1&all=1';
|
||||||
|
var systemUsersUrl = "{% url 'api-perms:my-asset-system-users' asset_id=DEFAULT_PK %}";
|
||||||
var showAssetHref = false; // Need input default true
|
var showAssetHref = false; // Need input default true
|
||||||
var actions = {
|
var actions = {
|
||||||
targets: 4, createdCell: function (td, cellData) {
|
targets: 4, createdCell: function (td, cellData) {
|
||||||
|
|
|
@ -1,33 +1,32 @@
|
||||||
# coding:utf-8
|
# coding:utf-8
|
||||||
from django.urls import path
|
from django.urls import path, re_path
|
||||||
from rest_framework_nested import routers
|
from rest_framework_nested import routers
|
||||||
# from rest_framework.routers import DefaultRouter
|
# from rest_framework.routers import DefaultRouter
|
||||||
from rest_framework_bulk.routes import BulkRouter
|
from rest_framework_bulk.routes import BulkRouter
|
||||||
|
|
||||||
|
from common import api as capi
|
||||||
|
|
||||||
from .. import api
|
from .. import api
|
||||||
|
|
||||||
app_name = 'assets'
|
app_name = 'assets'
|
||||||
|
|
||||||
router = BulkRouter()
|
router = BulkRouter()
|
||||||
router.register(r'assets', api.AssetViewSet, 'asset')
|
router.register(r'assets', api.AssetViewSet, 'asset')
|
||||||
router.register(r'admin-user', api.AdminUserViewSet, 'admin-user')
|
router.register(r'admin-users', api.AdminUserViewSet, 'admin-user')
|
||||||
router.register(r'system-user', api.SystemUserViewSet, 'system-user')
|
router.register(r'system-users', api.SystemUserViewSet, 'system-user')
|
||||||
router.register(r'labels', api.LabelViewSet, 'label')
|
router.register(r'labels', api.LabelViewSet, 'label')
|
||||||
router.register(r'nodes', api.NodeViewSet, 'node')
|
router.register(r'nodes', api.NodeViewSet, 'node')
|
||||||
router.register(r'domain', api.DomainViewSet, 'domain')
|
router.register(r'domains', api.DomainViewSet, 'domain')
|
||||||
router.register(r'gateway', api.GatewayViewSet, 'gateway')
|
router.register(r'gateways', api.GatewayViewSet, 'gateway')
|
||||||
router.register(r'cmd-filter', api.CommandFilterViewSet, 'cmd-filter')
|
router.register(r'cmd-filters', api.CommandFilterViewSet, 'cmd-filter')
|
||||||
router.register(r'asset-user', api.AssetUserViewSet, 'asset-user')
|
router.register(r'asset-users', api.AssetUserViewSet, 'asset-user')
|
||||||
router.register(r'asset-user-info', api.AssetUserExportViewSet, 'asset-user-info')
|
router.register(r'asset-users-info', api.AssetUserExportViewSet, 'asset-user-info')
|
||||||
|
|
||||||
cmd_filter_router = routers.NestedDefaultRouter(router, r'cmd-filter', lookup='filter')
|
cmd_filter_router = routers.NestedDefaultRouter(router, r'cmd-filters', lookup='filter')
|
||||||
cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule')
|
cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule')
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('assets-bulk/', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'),
|
|
||||||
path('asset/update/select/',
|
|
||||||
api.AssetBulkUpdateSelectAPI.as_view(), name='asset-bulk-update-select'),
|
|
||||||
path('assets/<uuid:pk>/refresh/',
|
path('assets/<uuid:pk>/refresh/',
|
||||||
api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'),
|
api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'),
|
||||||
path('assets/<uuid:pk>/alive/',
|
path('assets/<uuid:pk>/alive/',
|
||||||
|
@ -35,36 +34,36 @@ urlpatterns = [
|
||||||
path('assets/<uuid:pk>/gateway/',
|
path('assets/<uuid:pk>/gateway/',
|
||||||
api.AssetGatewayApi.as_view(), name='asset-gateway'),
|
api.AssetGatewayApi.as_view(), name='asset-gateway'),
|
||||||
|
|
||||||
path('asset-user/auth-info/',
|
path('asset-users/auth-info/',
|
||||||
api.AssetUserAuthInfoApi.as_view(), name='asset-user-auth-info'),
|
api.AssetUserAuthInfoApi.as_view(), name='asset-user-auth-info'),
|
||||||
path('asset-user/test-connective/',
|
path('asset-users/test-connective/',
|
||||||
api.AssetUserTestConnectiveApi.as_view(), name='asset-user-connective'),
|
api.AssetUserTestConnectiveApi.as_view(), name='asset-user-connective'),
|
||||||
|
|
||||||
|
|
||||||
path('admin-user/<uuid:pk>/nodes/',
|
path('admin-users/<uuid:pk>/nodes/',
|
||||||
api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'),
|
api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'),
|
||||||
path('admin-user/<uuid:pk>/auth/',
|
path('admin-users/<uuid:pk>/auth/',
|
||||||
api.AdminUserAuthApi.as_view(), name='admin-user-auth'),
|
api.AdminUserAuthApi.as_view(), name='admin-user-auth'),
|
||||||
path('admin-user/<uuid:pk>/connective/',
|
path('admin-users/<uuid:pk>/connective/',
|
||||||
api.AdminUserTestConnectiveApi.as_view(), name='admin-user-connective'),
|
api.AdminUserTestConnectiveApi.as_view(), name='admin-user-connective'),
|
||||||
path('admin-user/<uuid:pk>/assets/',
|
path('admin-users/<uuid:pk>/assets/',
|
||||||
api.AdminUserAssetsListView.as_view(), name='admin-user-assets'),
|
api.AdminUserAssetsListView.as_view(), name='admin-user-assets'),
|
||||||
|
|
||||||
path('system-user/<uuid:pk>/auth-info/',
|
path('system-users/<uuid:pk>/auth-info/',
|
||||||
api.SystemUserAuthInfoApi.as_view(), name='system-user-auth-info'),
|
api.SystemUserAuthInfoApi.as_view(), name='system-user-auth-info'),
|
||||||
path('system-user/<uuid:pk>/asset/<uuid:aid>/auth-info/',
|
path('system-users/<uuid:pk>/asset/<uuid:aid>/auth-info/',
|
||||||
api.SystemUserAssetAuthInfoApi.as_view(), name='system-user-asset-auth-info'),
|
api.SystemUserAssetAuthInfoApi.as_view(), name='system-user-asset-auth-info'),
|
||||||
path('system-user/<uuid:pk>/assets/',
|
path('system-users/<uuid:pk>/assets/',
|
||||||
api.SystemUserAssetsListView.as_view(), name='system-user-assets'),
|
api.SystemUserAssetsListView.as_view(), name='system-user-assets'),
|
||||||
path('system-user/<uuid:pk>/push/',
|
path('system-users/<uuid:pk>/push/',
|
||||||
api.SystemUserPushApi.as_view(), name='system-user-push'),
|
api.SystemUserPushApi.as_view(), name='system-user-push'),
|
||||||
path('system-user/<uuid:pk>/asset/<uuid:aid>/push/',
|
path('system-users/<uuid:pk>/asset/<uuid:aid>/push/',
|
||||||
api.SystemUserPushToAssetApi.as_view(), name='system-user-push-to-asset'),
|
api.SystemUserPushToAssetApi.as_view(), name='system-user-push-to-asset'),
|
||||||
path('system-user/<uuid:pk>/asset/<uuid:aid>/test/',
|
path('system-users/<uuid:pk>/asset/<uuid:aid>/test/',
|
||||||
api.SystemUserTestAssetConnectivityApi.as_view(), name='system-user-test-to-asset'),
|
api.SystemUserTestAssetConnectivityApi.as_view(), name='system-user-test-to-asset'),
|
||||||
path('system-user/<uuid:pk>/connective/',
|
path('system-users/<uuid:pk>/connective/',
|
||||||
api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'),
|
api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'),
|
||||||
path('system-user/<uuid:pk>/cmd-filter-rules/',
|
path('system-users/<uuid:pk>/cmd-filter-rules/',
|
||||||
api.SystemUserCommandFilterRuleListApi.as_view(), name='system-user-cmd-filter-rule-list'),
|
api.SystemUserCommandFilterRuleListApi.as_view(), name='system-user-cmd-filter-rule-list'),
|
||||||
|
|
||||||
path('nodes/tree/', api.NodeListAsTreeApi.as_view(), name='node-tree'),
|
path('nodes/tree/', api.NodeListAsTreeApi.as_view(), name='node-tree'),
|
||||||
|
@ -89,10 +88,14 @@ urlpatterns = [
|
||||||
path('nodes/refresh-assets-amount/',
|
path('nodes/refresh-assets-amount/',
|
||||||
api.RefreshAssetsAmount.as_view(), name='refresh-assets-amount'),
|
api.RefreshAssetsAmount.as_view(), name='refresh-assets-amount'),
|
||||||
|
|
||||||
path('gateway/<uuid:pk>/test-connective/',
|
path('gateways/<uuid:pk>/test-connective/',
|
||||||
api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'),
|
api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns += router.urls + cmd_filter_router.urls
|
old_version_urlpatterns = [
|
||||||
|
re_path('(?P<resource>admin-user|system-user|domain|gateway|cmd-filter|asset-user)/.*', capi.redirect_plural_name_api)
|
||||||
|
]
|
||||||
|
|
||||||
|
urlpatterns += router.urls + cmd_filter_router.urls + old_version_urlpatterns
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
# ~*~ coding: utf-8 ~*~
|
# ~*~ coding: utf-8 ~*~
|
||||||
#
|
#
|
||||||
import time
|
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from django.db.models import Prefetch, Q
|
from treelib import Tree
|
||||||
|
from collections import defaultdict
|
||||||
|
from copy import deepcopy
|
||||||
|
import threading
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
from common.utils import get_object_or_none, get_logger
|
from common.utils import get_object_or_none, get_logger, timeit
|
||||||
from common.struct import Stack
|
from .models import SystemUser, Label, Asset
|
||||||
from .models import SystemUser, Label, Node, Asset
|
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
@ -53,204 +55,141 @@ class LabelFilter(LabelFilterMixin):
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class NodeUtil:
|
class TreeService(Tree):
|
||||||
def __init__(self, with_assets_amount=False, debug=False):
|
tag_sep = ' / '
|
||||||
self.stack = Stack()
|
cache_key = '_NODE_FULL_TREE'
|
||||||
self._nodes = {}
|
cache_time = 3600
|
||||||
self.with_assets_amount = with_assets_amount
|
|
||||||
self._debug = debug
|
def __init__(self, *args, **kwargs):
|
||||||
self.init()
|
super().__init__(*args, **kwargs)
|
||||||
|
self.nodes_assets_map = defaultdict(set)
|
||||||
|
self.all_nodes_assets_map = {}
|
||||||
|
self.mutex = threading.Lock()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@timeit
|
||||||
|
def new(cls):
|
||||||
|
from .models import Node
|
||||||
|
from orgs.utils import get_current_org, set_to_root_org
|
||||||
|
|
||||||
|
origin_org = get_current_org()
|
||||||
|
set_to_root_org()
|
||||||
|
all_nodes = Node.objects.all()
|
||||||
|
origin_org.change_to()
|
||||||
|
|
||||||
|
tree = cls()
|
||||||
|
tree.create_node(tag='', identifier='')
|
||||||
|
for node in all_nodes:
|
||||||
|
tree.create_node(
|
||||||
|
tag=node.value, identifier=node.key,
|
||||||
|
parent=node.parent_key,
|
||||||
|
)
|
||||||
|
tree.init_assets_async()
|
||||||
|
return tree
|
||||||
|
|
||||||
|
def init_assets_async(self):
|
||||||
|
t = threading.Thread(target=self.init_assets)
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
def init_assets(self):
|
||||||
|
from orgs.utils import get_current_org, set_to_root_org
|
||||||
|
with self.mutex:
|
||||||
|
origin_org = get_current_org()
|
||||||
|
set_to_root_org()
|
||||||
|
queryset = Asset.objects.all().valid().values_list('id', 'nodes__key')
|
||||||
|
|
||||||
|
if origin_org:
|
||||||
|
origin_org.change_to()
|
||||||
|
for asset_id, key in queryset:
|
||||||
|
if not key:
|
||||||
|
continue
|
||||||
|
self.nodes_assets_map[key].add(asset_id)
|
||||||
|
|
||||||
|
def all_children(self, nid, with_self=True, deep=False):
|
||||||
|
children_ids = self.expand_tree(nid)
|
||||||
|
if not with_self:
|
||||||
|
next(children_ids)
|
||||||
|
return [self.get_node(i, deep=deep) for i in children_ids]
|
||||||
|
|
||||||
|
def ancestors(self, nid, with_self=False, deep=False):
|
||||||
|
ancestor_ids = list(self.rsearch(nid))
|
||||||
|
ancestor_ids.pop()
|
||||||
|
if not with_self:
|
||||||
|
ancestor_ids.pop(0)
|
||||||
|
return [self.get_node(i, deep=deep) for i in ancestor_ids]
|
||||||
|
|
||||||
|
def get_node_full_tag(self, nid):
|
||||||
|
ancestors = self.ancestors(nid)
|
||||||
|
ancestors.reverse()
|
||||||
|
return self.tag_sep.join(n.tag for n in ancestors)
|
||||||
|
|
||||||
|
def get_family(self, nid, deep=False):
|
||||||
|
ancestors = self.ancestors(nid, with_self=False, deep=deep)
|
||||||
|
children = self.all_children(nid, with_self=False)
|
||||||
|
return ancestors + [self[nid]] + children
|
||||||
|
|
||||||
|
def root_node(self):
|
||||||
|
return self.get_node(self.root)
|
||||||
|
|
||||||
|
def get_node(self, nid, deep=False):
|
||||||
|
node = super().get_node(nid)
|
||||||
|
if deep:
|
||||||
|
node = self.copy_node(node)
|
||||||
|
return node
|
||||||
|
|
||||||
|
def parent(self, nid, deep=False):
|
||||||
|
parent = super().parent(nid)
|
||||||
|
if deep:
|
||||||
|
parent = self.copy_node(parent)
|
||||||
|
return parent
|
||||||
|
|
||||||
|
def assets(self, nid):
|
||||||
|
with self.mutex:
|
||||||
|
assets = self.nodes_assets_map[nid]
|
||||||
|
return assets
|
||||||
|
|
||||||
|
def set_assets(self, nid, assets):
|
||||||
|
with self.mutex:
|
||||||
|
self.nodes_assets_map[nid] = assets
|
||||||
|
|
||||||
|
def all_assets(self, nid):
|
||||||
|
assets = self.all_nodes_assets_map.get(nid)
|
||||||
|
if assets:
|
||||||
|
return assets
|
||||||
|
assets = set(self.assets(nid))
|
||||||
|
children = self.children(nid)
|
||||||
|
for child in children:
|
||||||
|
assets.update(self.all_assets(child.identifier))
|
||||||
|
return assets
|
||||||
|
|
||||||
|
def assets_amount(self, nid):
|
||||||
|
return len(self.all_assets(nid))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def sorted_by(node):
|
def copy_node(node):
|
||||||
return [int(i) for i in node.key.split(':')]
|
new_node = deepcopy(node)
|
||||||
|
new_node.fpointer = None
|
||||||
|
return new_node
|
||||||
|
|
||||||
def get_queryset(self):
|
def safe_add_ancestors(self, ancestors):
|
||||||
all_nodes = Node.objects.all()
|
# 如果祖先节点为1个,那么添加该节点, 父节点是root node
|
||||||
if self.with_assets_amount:
|
if len(ancestors) == 1:
|
||||||
all_nodes = all_nodes.prefetch_related(
|
node = ancestors[0]
|
||||||
Prefetch('assets', queryset=Asset.objects.all().only('id'))
|
parent = self.root_node()
|
||||||
)
|
|
||||||
all_nodes = list(all_nodes)
|
|
||||||
for node in all_nodes:
|
|
||||||
node._assets = set(node.assets.all())
|
|
||||||
return all_nodes
|
|
||||||
|
|
||||||
def get_all_nodes(self):
|
|
||||||
all_nodes = sorted(self.get_queryset(), key=self.sorted_by)
|
|
||||||
|
|
||||||
guarder = Node(key='', value='Guarder')
|
|
||||||
guarder._assets = []
|
|
||||||
all_nodes.append(guarder)
|
|
||||||
return all_nodes
|
|
||||||
|
|
||||||
def push_to_stack(self, node):
|
|
||||||
# 入栈之前检查
|
|
||||||
# 如果栈是空的,证明是一颗树的根部
|
|
||||||
if self.stack.is_empty():
|
|
||||||
node._full_value = node.value
|
|
||||||
node._parents = []
|
|
||||||
else:
|
else:
|
||||||
# 如果不是根节点,
|
node, ancestors = ancestors[0], ancestors[1:]
|
||||||
# 该节点的祖先应该是父节点的祖先加上父节点
|
parent_id = ancestors[0].identifier
|
||||||
# 该节点的名字是父节点的名字+自己的名字
|
# 如果父节点不存在, 则先添加父节点
|
||||||
node._parents = [self.stack.top] + self.stack.top._parents
|
if not self.contains(parent_id):
|
||||||
node._full_value = ' / '.join(
|
self.safe_add_ancestors(ancestors)
|
||||||
[self.stack.top._full_value, node.value]
|
parent = self.get_node(parent_id)
|
||||||
)
|
|
||||||
node._children = []
|
|
||||||
node._all_children = []
|
|
||||||
self.debug("入栈: {}".format(node.key))
|
|
||||||
self.stack.push(node)
|
|
||||||
|
|
||||||
# 出栈
|
|
||||||
def pop_from_stack(self):
|
|
||||||
_node = self.stack.pop()
|
|
||||||
self.debug("出栈: {} 栈顶: {}".format(_node.key, self.stack.top.key if self.stack.top else None))
|
|
||||||
self._nodes[_node.key] = _node
|
|
||||||
if not self.stack.top:
|
|
||||||
return
|
|
||||||
if self.with_assets_amount:
|
|
||||||
self.stack.top._assets.update(_node._assets)
|
|
||||||
_node._assets_amount = len(_node._assets)
|
|
||||||
delattr(_node, '_assets')
|
|
||||||
self.stack.top._children.append(_node)
|
|
||||||
self.stack.top._all_children.extend([_node] + _node._all_children)
|
|
||||||
|
|
||||||
def init(self):
|
|
||||||
all_nodes = self.get_all_nodes()
|
|
||||||
for node in all_nodes:
|
|
||||||
self.debug("准备: {} 栈顶: {}".format(node.key, self.stack.top.key if self.stack.top else None))
|
|
||||||
# 入栈之前检查,该节点是不是栈顶节点的子节点
|
|
||||||
# 如果不是,则栈顶出栈
|
|
||||||
while self.stack.top and not self.stack.top.is_children(node):
|
|
||||||
self.pop_from_stack()
|
|
||||||
self.push_to_stack(node)
|
|
||||||
# 出栈最后一个
|
|
||||||
self.debug("剩余: {}".format(', '.join([n.key for n in self.stack])))
|
|
||||||
|
|
||||||
def get_nodes_by_queryset(self, queryset):
|
|
||||||
nodes = []
|
|
||||||
for n in queryset:
|
|
||||||
node = self.get_node_by_key(n.key)
|
|
||||||
if not node:
|
|
||||||
continue
|
|
||||||
nodes.append(node)
|
|
||||||
return nodes
|
|
||||||
|
|
||||||
def get_node_by_key(self, key):
|
|
||||||
return self._nodes.get(key)
|
|
||||||
|
|
||||||
def debug(self, msg):
|
|
||||||
self._debug and logger.debug(msg)
|
|
||||||
|
|
||||||
def set_assets_amount(self):
|
|
||||||
for node in self._nodes.values():
|
|
||||||
node.assets_amount = node._assets_amount
|
|
||||||
|
|
||||||
def set_full_value(self):
|
|
||||||
for node in self._nodes.values():
|
|
||||||
node.full_value = node._full_value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def nodes(self):
|
|
||||||
return list(self._nodes.values())
|
|
||||||
|
|
||||||
def get_family_by_key(self, key):
|
|
||||||
tree_nodes = set()
|
|
||||||
node = self.get_node_by_key(key)
|
|
||||||
if not node:
|
|
||||||
return []
|
|
||||||
tree_nodes.update(node._parents)
|
|
||||||
tree_nodes.add(node)
|
|
||||||
tree_nodes.update(node._all_children)
|
|
||||||
return list(tree_nodes)
|
|
||||||
|
|
||||||
# 使用给定节点生成一颗树
|
|
||||||
# 找到他们的祖先节点
|
|
||||||
# 可选找到他们的子孙节点
|
|
||||||
def get_family(self, node):
|
|
||||||
return self.get_family_by_key(node.key)
|
|
||||||
|
|
||||||
def get_family_keys_by_key(self, key):
|
|
||||||
nodes = self.get_family_by_key(key)
|
|
||||||
return [n.key for n in nodes]
|
|
||||||
|
|
||||||
def get_some_nodes_family_by_keys(self, keys):
|
|
||||||
family = set()
|
|
||||||
for key in keys:
|
|
||||||
family.update(self.get_family_by_key(key))
|
|
||||||
return family
|
|
||||||
|
|
||||||
def get_some_nodes_family_keys_by_keys(self, keys):
|
|
||||||
family = self.get_some_nodes_family_by_keys(keys)
|
|
||||||
return [n.key for n in family]
|
|
||||||
|
|
||||||
def get_nodes_parents_by_key(self, key, with_self=True):
|
|
||||||
parents = set()
|
|
||||||
node = self.get_node_by_key(key)
|
|
||||||
if not node:
|
|
||||||
return []
|
|
||||||
parents.update(set(node._parents))
|
|
||||||
if with_self:
|
|
||||||
parents.add(node)
|
|
||||||
return list(parents)
|
|
||||||
|
|
||||||
def get_node_parents(self, node, with_self=True):
|
|
||||||
return self.get_nodes_parents_by_key(node.key, with_self=with_self)
|
|
||||||
|
|
||||||
def get_nodes_parents_keys_by_key(self, key, with_self=True):
|
|
||||||
nodes = self.get_nodes_parents_by_key(key, with_self=with_self)
|
|
||||||
return [n.key for n in nodes]
|
|
||||||
|
|
||||||
def get_all_children_by_key(self, key, with_self=True):
|
|
||||||
children = set()
|
|
||||||
node = self.get_node_by_key(key)
|
|
||||||
if not node:
|
|
||||||
return []
|
|
||||||
children.update(set(node._all_children))
|
|
||||||
if with_self:
|
|
||||||
children.add(node)
|
|
||||||
return list(children)
|
|
||||||
|
|
||||||
def get_all_children(self, node, with_self=True):
|
|
||||||
return self.get_all_children_by_key(node.key, with_self=with_self)
|
|
||||||
|
|
||||||
def get_all_children_keys_by_key(self, key, with_self=True):
|
|
||||||
nodes = self.get_all_children_by_key(key, with_self=with_self)
|
|
||||||
return [n.key for n in nodes]
|
|
||||||
|
|
||||||
|
|
||||||
def test_node_tree():
|
|
||||||
tree = NodeUtil()
|
|
||||||
for node in tree._nodes.values():
|
|
||||||
print("Check {}".format(node.key))
|
|
||||||
children_wanted = node.get_all_children().count()
|
|
||||||
children = len(node._children)
|
|
||||||
if children != children_wanted:
|
|
||||||
print("{} children not equal: {} != {}".format(node.key, children, children_wanted))
|
|
||||||
|
|
||||||
assets_amount_wanted = node.get_all_assets().count()
|
|
||||||
if node._assets_amount != assets_amount_wanted:
|
|
||||||
print("{} assets amount not equal: {} != {}".format(
|
|
||||||
node.key, node._assets_amount, assets_amount_wanted)
|
|
||||||
)
|
|
||||||
|
|
||||||
full_value_wanted = node.full_value
|
|
||||||
if node._full_value != full_value_wanted:
|
|
||||||
print("{} full value not equal: {} != {}".format(
|
|
||||||
node.key, node._full_value, full_value_wanted)
|
|
||||||
)
|
|
||||||
|
|
||||||
parents_wanted = node.get_ancestor().count()
|
|
||||||
parents = len(node._parents)
|
|
||||||
if parents != parents_wanted:
|
|
||||||
print("{} parents count not equal: {} != {}".format(
|
|
||||||
node.key, parents, parents_wanted)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
print("Add node: {} {}".format(node.identifier, parent.identifier))
|
||||||
|
# 如果当前节点已再树中,则移动当前节点到父节点中
|
||||||
|
# 这个是由于 当前节点放到了二级节点中
|
||||||
|
if self.contains(node.identifier):
|
||||||
|
self.move_node(node.identifier, parent.identifier)
|
||||||
|
else:
|
||||||
|
self.add_node(node, parent)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -83,7 +83,6 @@ class AdminUserDetailView(PermissionsMixin, DetailView):
|
||||||
context = {
|
context = {
|
||||||
'app': _('Assets'),
|
'app': _('Assets'),
|
||||||
'action': _('Admin user detail'),
|
'action': _('Admin user detail'),
|
||||||
'nodes': Node.get_queryset(),
|
|
||||||
}
|
}
|
||||||
kwargs.update(context)
|
kwargs.update(context)
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
|
@ -16,8 +16,7 @@ from common.utils import get_object_or_none, get_logger
|
||||||
from common.permissions import PermissionsMixin, IsOrgAdmin, IsValidUser
|
from common.permissions import PermissionsMixin, IsOrgAdmin, IsValidUser
|
||||||
from common.const import KEY_CACHE_RESOURCES_ID
|
from common.const import KEY_CACHE_RESOURCES_ID
|
||||||
from .. import forms
|
from .. import forms
|
||||||
from ..utils import NodeUtil
|
from ..models import Asset, Label, Node
|
||||||
from ..models import Asset, SystemUser, Label, Node
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
@ -196,13 +195,9 @@ class AssetDetailView(PermissionsMixin, DetailView):
|
||||||
).select_related('admin_user', 'domain')
|
).select_related('admin_user', 'domain')
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
nodes_remain = Node.objects.exclude(assets=self.object).only('key')
|
|
||||||
util = NodeUtil()
|
|
||||||
nodes_remain = util.get_nodes_by_queryset(nodes_remain)
|
|
||||||
context = {
|
context = {
|
||||||
'app': _('Assets'),
|
'app': _('Assets'),
|
||||||
'action': _('Asset detail'),
|
'action': _('Asset detail'),
|
||||||
'nodes_remain': nodes_remain,
|
|
||||||
}
|
}
|
||||||
kwargs.update(context)
|
kwargs.update(context)
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
|
@ -98,14 +98,9 @@ class SystemUserAssetView(PermissionsMixin, DetailView):
|
||||||
permission_classes = [IsOrgAdmin]
|
permission_classes = [IsOrgAdmin]
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
from ..utils import NodeUtil
|
|
||||||
nodes_remain = Node.objects.exclude(systemuser=self.object)
|
|
||||||
util = NodeUtil()
|
|
||||||
nodes_remain = util.get_nodes_by_queryset(nodes_remain)
|
|
||||||
context = {
|
context = {
|
||||||
'app': _('assets'),
|
'app': _('assets'),
|
||||||
'action': _('System user asset'),
|
'action': _('System user asset'),
|
||||||
'nodes_remain': nodes_remain
|
|
||||||
}
|
}
|
||||||
kwargs.update(context)
|
kwargs.update(context)
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db.models.signals import post_save
|
||||||
|
|
||||||
|
|
||||||
class AuditsConfig(AppConfig):
|
class AuditsConfig(AppConfig):
|
||||||
|
@ -6,3 +8,5 @@ class AuditsConfig(AppConfig):
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
from . import signals_handler
|
from . import signals_handler
|
||||||
|
if settings.SYSLOG_ENABLE:
|
||||||
|
post_save.connect(signals_handler.on_audits_log_create)
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Generated by Django 2.1.7 on 2019-07-26 09:53
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_loginlog_reason_to_str(apps, schema_editor):
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
|
||||||
|
reason_map = {
|
||||||
|
"0": "",
|
||||||
|
"1": 'Username/password check failed',
|
||||||
|
"2": 'MFA authentication failed',
|
||||||
|
"3": "Username does not exist",
|
||||||
|
"4": "Password expired",
|
||||||
|
}
|
||||||
|
|
||||||
|
model = apps.get_model("audits", "UserLoginLog")
|
||||||
|
for k, v in reason_map.items():
|
||||||
|
model.objects.using(db_alias).filter(reason=k).update(reason=v)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('audits', '0005_auto_20190228_1715'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='userloginlog',
|
||||||
|
name='reason',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=128, verbose_name='Reason'),
|
||||||
|
),
|
||||||
|
migrations.RunPython(migrate_loginlog_reason_to_str),
|
||||||
|
]
|
|
@ -5,7 +5,7 @@ from django.db.models import Q
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from orgs.mixins import OrgModelMixin
|
from orgs.mixins.models import OrgModelMixin
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'FTPLog', 'OperateLog', 'PasswordChangeLog', 'UserLoginLog',
|
'FTPLog', 'OperateLog', 'PasswordChangeLog', 'UserLoginLog',
|
||||||
|
@ -72,20 +72,6 @@ class UserLoginLog(models.Model):
|
||||||
(MFA_UNKNOWN, _('-')),
|
(MFA_UNKNOWN, _('-')),
|
||||||
)
|
)
|
||||||
|
|
||||||
REASON_NOTHING = 0
|
|
||||||
REASON_PASSWORD = 1
|
|
||||||
REASON_MFA = 2
|
|
||||||
REASON_NOT_EXIST = 3
|
|
||||||
REASON_PASSWORD_EXPIRED = 4
|
|
||||||
|
|
||||||
REASON_CHOICE = (
|
|
||||||
(REASON_NOTHING, _('-')),
|
|
||||||
(REASON_PASSWORD, _('Username/password check failed')),
|
|
||||||
(REASON_MFA, _('MFA authentication failed')),
|
|
||||||
(REASON_NOT_EXIST, _("Username does not exist")),
|
|
||||||
(REASON_PASSWORD_EXPIRED, _("Password expired")),
|
|
||||||
)
|
|
||||||
|
|
||||||
STATUS_CHOICE = (
|
STATUS_CHOICE = (
|
||||||
(True, _('Success')),
|
(True, _('Success')),
|
||||||
(False, _('Failed'))
|
(False, _('Failed'))
|
||||||
|
@ -97,7 +83,7 @@ class UserLoginLog(models.Model):
|
||||||
city = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('Login city'))
|
city = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('Login city'))
|
||||||
user_agent = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('User agent'))
|
user_agent = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('User agent'))
|
||||||
mfa = models.SmallIntegerField(default=MFA_UNKNOWN, choices=MFA_CHOICE, verbose_name=_('MFA'))
|
mfa = models.SmallIntegerField(default=MFA_UNKNOWN, choices=MFA_CHOICE, verbose_name=_('MFA'))
|
||||||
reason = models.SmallIntegerField(default=0, choices=REASON_CHOICE, verbose_name=_('Reason'))
|
reason = models.CharField(default='', max_length=128, blank=True, verbose_name=_('Reason'))
|
||||||
status = models.BooleanField(max_length=2, default=True, choices=STATUS_CHOICE, verbose_name=_('Status'))
|
status = models.BooleanField(max_length=2, default=True, choices=STATUS_CHOICE, verbose_name=_('Status'))
|
||||||
datetime = models.DateTimeField(default=timezone.now, verbose_name=_('Date login'))
|
datetime = models.DateTimeField(default=timezone.now, verbose_name=_('Date login'))
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,36 @@
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from .models import FTPLog
|
from terminal.models import Session
|
||||||
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
class FTPLogSerializer(serializers.ModelSerializer):
|
class FTPLogSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = FTPLog
|
model = models.FTPLog
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class LoginLogSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = models.UserLoginLog
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class OperateLogSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = models.OperateLog
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordChangeLogSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = models.PasswordChangeLog
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class SessionAuditSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Session
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
|
@ -4,13 +4,18 @@
|
||||||
from django.db.models.signals import post_save, post_delete
|
from django.db.models.signals import post_save, post_delete
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
from rest_framework.renderers import JSONRenderer
|
||||||
|
|
||||||
from jumpserver.utils import current_request
|
from jumpserver.utils import current_request
|
||||||
from common.utils import get_request_ip, get_logger
|
from common.utils import get_request_ip, get_logger, get_syslogger
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from .models import OperateLog, PasswordChangeLog
|
from terminal.models import Session
|
||||||
|
from . import models
|
||||||
|
from . import serializers
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
sys_logger = get_syslogger("audits")
|
||||||
|
json_render = JSONRenderer()
|
||||||
|
|
||||||
|
|
||||||
MODELS_NEED_RECORD = (
|
MODELS_NEED_RECORD = (
|
||||||
|
@ -36,7 +41,7 @@ def create_operate_log(action, sender, resource):
|
||||||
}
|
}
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
try:
|
try:
|
||||||
OperateLog.objects.create(**data)
|
models.OperateLog.objects.create(**data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Create operate log error: {}".format(e))
|
logger.error("Create operate log error: {}".format(e))
|
||||||
|
|
||||||
|
@ -44,15 +49,15 @@ def create_operate_log(action, sender, resource):
|
||||||
@receiver(post_save, dispatch_uid="my_unique_identifier")
|
@receiver(post_save, dispatch_uid="my_unique_identifier")
|
||||||
def on_object_created_or_update(sender, instance=None, created=False, **kwargs):
|
def on_object_created_or_update(sender, instance=None, created=False, **kwargs):
|
||||||
if created:
|
if created:
|
||||||
action = OperateLog.ACTION_CREATE
|
action = models.OperateLog.ACTION_CREATE
|
||||||
else:
|
else:
|
||||||
action = OperateLog.ACTION_UPDATE
|
action = models.OperateLog.ACTION_UPDATE
|
||||||
create_operate_log(action, sender, instance)
|
create_operate_log(action, sender, instance)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, dispatch_uid="my_unique_identifier")
|
@receiver(post_delete, dispatch_uid="my_unique_identifier")
|
||||||
def on_object_delete(sender, instance=None, **kwargs):
|
def on_object_delete(sender, instance=None, **kwargs):
|
||||||
create_operate_log(OperateLog.ACTION_DELETE, sender, instance)
|
create_operate_log(models.OperateLog.ACTION_DELETE, sender, instance)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=User, dispatch_uid="my_unique_identifier")
|
@receiver(post_save, sender=User, dispatch_uid="my_unique_identifier")
|
||||||
|
@ -61,7 +66,32 @@ def on_user_change_password(sender, instance=None, **kwargs):
|
||||||
if not current_request or not current_request.user.is_authenticated:
|
if not current_request or not current_request.user.is_authenticated:
|
||||||
return
|
return
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
PasswordChangeLog.objects.create(
|
models.PasswordChangeLog.objects.create(
|
||||||
user=instance, change_by=current_request.user,
|
user=instance, change_by=current_request.user,
|
||||||
remote_addr=get_request_ip(current_request),
|
remote_addr=get_request_ip(current_request),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def on_audits_log_create(sender, instance=None, **kwargs):
|
||||||
|
if sender == models.UserLoginLog:
|
||||||
|
category = "login_log"
|
||||||
|
serializer = serializers.LoginLogSerializer
|
||||||
|
elif sender == models.FTPLog:
|
||||||
|
serializer = serializers.FTPLogSerializer
|
||||||
|
category = "ftp_log"
|
||||||
|
elif sender == models.OperateLog:
|
||||||
|
category = "operation_log"
|
||||||
|
serializer = serializers.OperateLogSerializer
|
||||||
|
elif sender == models.PasswordChangeLog:
|
||||||
|
category = "password_change_log"
|
||||||
|
serializer = serializers.PasswordChangeLogSerializer
|
||||||
|
elif sender == Session:
|
||||||
|
category = "host_session_log"
|
||||||
|
serializer = serializers.SessionAuditSerializer
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
s = serializer(instance=instance)
|
||||||
|
data = json_render.render(s.data).decode(errors='ignore')
|
||||||
|
msg = "{} - {}".format(category, data)
|
||||||
|
sys_logger.info(msg)
|
||||||
|
|
|
@ -72,7 +72,7 @@
|
||||||
<td class="text-center">{{ login_log.ip }}</td>
|
<td class="text-center">{{ login_log.ip }}</td>
|
||||||
<td class="text-center">{{ login_log.city }}</td>
|
<td class="text-center">{{ login_log.city }}</td>
|
||||||
<td class="text-center">{{ login_log.get_mfa_display }}</td>
|
<td class="text-center">{{ login_log.get_mfa_display }}</td>
|
||||||
<td class="text-center">{{ login_log.get_reason_display }}</td>
|
<td class="text-center">{% trans login_log.reason %}</td>
|
||||||
<td class="text-center">{{ login_log.get_status_display }}</td>
|
<td class="text-center">{{ login_log.get_status_display }}</td>
|
||||||
<td class="text-center">{{ login_log.datetime }}</td>
|
<td class="text-center">{{ login_log.datetime }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -2,3 +2,6 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
from .auth import *
|
from .auth import *
|
||||||
|
from .token import *
|
||||||
|
from .mfa import *
|
||||||
|
from .access_key import *
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
|
||||||
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
|
from common.permissions import IsValidUser
|
||||||
|
from .. import serializers
|
||||||
|
|
||||||
|
|
||||||
|
class AccessKeyViewSet(ModelViewSet):
|
||||||
|
permission_classes = (IsValidUser,)
|
||||||
|
serializer_class = serializers.AccessKeySerializer
|
||||||
|
search_fields = ['^id', '^secret']
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return self.request.user.access_keys.all()
|
||||||
|
|
||||||
|
def perform_create(self, serializer):
|
||||||
|
user = self.request.user
|
||||||
|
user.create_access_key()
|
|
@ -16,15 +16,17 @@ from rest_framework.views import APIView
|
||||||
|
|
||||||
from common.utils import get_logger, get_request_ip
|
from common.utils import get_logger, get_request_ip
|
||||||
from common.permissions import IsOrgAdminOrAppUser, IsValidUser
|
from common.permissions import IsOrgAdminOrAppUser, IsValidUser
|
||||||
from orgs.mixins import RootOrgViewMixin
|
from orgs.mixins.api import RootOrgViewMixin
|
||||||
from users.serializers import UserSerializer
|
from users.serializers import UserSerializer
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from assets.models import Asset, SystemUser
|
from assets.models import Asset, SystemUser
|
||||||
from audits.models import UserLoginLog as LoginLog
|
from audits.models import UserLoginLog as LoginLog
|
||||||
from users.utils import (
|
from users.utils import (
|
||||||
check_user_valid, check_otp_code, increase_login_failed_count,
|
check_otp_code, increase_login_failed_count,
|
||||||
is_block_login, clean_failed_count
|
is_block_login, clean_failed_count
|
||||||
)
|
)
|
||||||
|
from .. import const
|
||||||
|
from ..utils import check_user_valid
|
||||||
from ..serializers import OtpVerifySerializer
|
from ..serializers import OtpVerifySerializer
|
||||||
from ..signals import post_auth_success, post_auth_failed
|
from ..signals import post_auth_success, post_auth_failed
|
||||||
|
|
||||||
|
@ -53,27 +55,15 @@ class UserAuthApi(RootOrgViewMixin, APIView):
|
||||||
user, msg = self.check_user_valid(request)
|
user, msg = self.check_user_valid(request)
|
||||||
if not user:
|
if not user:
|
||||||
username = request.data.get('username', '')
|
username = request.data.get('username', '')
|
||||||
exist = User.objects.filter(username=username).first()
|
self.send_auth_signal(success=False, username=username, reason=msg)
|
||||||
reason = LoginLog.REASON_PASSWORD if exist else LoginLog.REASON_NOT_EXIST
|
|
||||||
self.send_auth_signal(success=False, username=username, reason=reason)
|
|
||||||
increase_login_failed_count(username, ip)
|
increase_login_failed_count(username, ip)
|
||||||
return Response({'msg': msg}, status=401)
|
return Response({'msg': msg}, status=401)
|
||||||
|
|
||||||
if user.password_has_expired:
|
|
||||||
self.send_auth_signal(
|
|
||||||
success=False, username=username,
|
|
||||||
reason=LoginLog.REASON_PASSWORD_EXPIRED
|
|
||||||
)
|
|
||||||
msg = _("The user {} password has expired, please update.".format(
|
|
||||||
user.username))
|
|
||||||
logger.info(msg)
|
|
||||||
return Response({'msg': msg}, status=401)
|
|
||||||
|
|
||||||
if not user.otp_enabled:
|
if not user.otp_enabled:
|
||||||
self.send_auth_signal(success=True, user=user)
|
self.send_auth_signal(success=True, user=user)
|
||||||
# 登陆成功,清除原来的缓存计数
|
# 登陆成功,清除原来的缓存计数
|
||||||
clean_failed_count(username, ip)
|
clean_failed_count(username, ip)
|
||||||
token = user.create_bearer_token(request)
|
token, expired_at = user.create_bearer_token(request)
|
||||||
return Response(
|
return Response(
|
||||||
{'token': token, 'user': self.serializer_class(user).data}
|
{'token': token, 'user': self.serializer_class(user).data}
|
||||||
)
|
)
|
||||||
|
@ -167,10 +157,10 @@ class UserOtpAuthApi(RootOrgViewMixin, APIView):
|
||||||
status=401
|
status=401
|
||||||
)
|
)
|
||||||
if not check_otp_code(user.otp_secret_key, otp_code):
|
if not check_otp_code(user.otp_secret_key, otp_code):
|
||||||
self.send_auth_signal(success=False, username=user.username, reason=LoginLog.REASON_MFA)
|
self.send_auth_signal(success=False, username=user.username, reason=const.mfa_failed)
|
||||||
return Response({'msg': _('MFA certification failed')}, status=401)
|
return Response({'msg': _('MFA certification failed')}, status=401)
|
||||||
self.send_auth_signal(success=True, user=user)
|
self.send_auth_signal(success=True, user=user)
|
||||||
token = user.create_bearer_token(request)
|
token, expired_at = user.create_bearer_token(request)
|
||||||
data = {'token': token, 'user': self.serializer_class(user).data}
|
data = {'token': token, 'user': self.serializer_class(user).data}
|
||||||
return Response(data)
|
return Response(data)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
from rest_framework.permissions import AllowAny
|
||||||
|
from rest_framework.generics import CreateAPIView
|
||||||
|
|
||||||
|
from .. import serializers
|
||||||
|
|
||||||
|
|
||||||
|
class MFAChallengeApi(CreateAPIView):
|
||||||
|
permission_classes = (AllowAny,)
|
||||||
|
serializer_class = serializers.MFAChallengeSerializer
|
|
@ -0,0 +1,95 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from django.core.cache import cache
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
|
from rest_framework.permissions import AllowAny
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.generics import CreateAPIView
|
||||||
|
from drf_yasg.utils import swagger_auto_schema
|
||||||
|
|
||||||
|
from common.utils import get_request_ip, get_logger
|
||||||
|
from users.utils import (
|
||||||
|
check_otp_code, increase_login_failed_count,
|
||||||
|
is_block_login, clean_failed_count
|
||||||
|
)
|
||||||
|
from ..utils import check_user_valid
|
||||||
|
from ..signals import post_auth_success, post_auth_failed
|
||||||
|
from .. import serializers
|
||||||
|
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
__all__ = ['TokenCreateApi']
|
||||||
|
|
||||||
|
|
||||||
|
class AuthFailedError(Exception):
|
||||||
|
def __init__(self, msg, reason=None):
|
||||||
|
self.msg = msg
|
||||||
|
self.reason = reason
|
||||||
|
|
||||||
|
|
||||||
|
class MFARequiredError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TokenCreateApi(CreateAPIView):
|
||||||
|
permission_classes = (AllowAny,)
|
||||||
|
serializer_class = serializers.BearerTokenSerializer
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_is_block(username, ip):
|
||||||
|
if is_block_login(username, ip):
|
||||||
|
msg = _("Log in frequently and try again later")
|
||||||
|
logger.warn(msg + ': ' + username + ':' + ip)
|
||||||
|
raise AuthFailedError(msg)
|
||||||
|
|
||||||
|
def check_user_valid(self):
|
||||||
|
request = self.request
|
||||||
|
username = request.data.get('username', '')
|
||||||
|
password = request.data.get('password', '')
|
||||||
|
public_key = request.data.get('public_key', '')
|
||||||
|
user, msg = check_user_valid(
|
||||||
|
username=username, password=password,
|
||||||
|
public_key=public_key
|
||||||
|
)
|
||||||
|
if not user:
|
||||||
|
raise AuthFailedError(msg)
|
||||||
|
return user
|
||||||
|
|
||||||
|
def create(self, request, *args, **kwargs):
|
||||||
|
username = self.request.data.get('username')
|
||||||
|
ip = self.request.data.get('remote_addr', None)
|
||||||
|
ip = ip or get_request_ip(self.request)
|
||||||
|
user = None
|
||||||
|
try:
|
||||||
|
self.check_is_block(username, ip)
|
||||||
|
user = self.check_user_valid()
|
||||||
|
if user.otp_enabled:
|
||||||
|
raise MFARequiredError()
|
||||||
|
self.send_auth_signal(success=True, user=user)
|
||||||
|
clean_failed_count(username, ip)
|
||||||
|
return super().create(request, *args, **kwargs)
|
||||||
|
except AuthFailedError as e:
|
||||||
|
increase_login_failed_count(username, ip)
|
||||||
|
self.send_auth_signal(success=False, user=user, username=username, reason=str(e))
|
||||||
|
return Response({'msg': str(e)}, status=401)
|
||||||
|
except MFARequiredError:
|
||||||
|
msg = _("MFA required")
|
||||||
|
seed = uuid.uuid4().hex
|
||||||
|
cache.set(seed, user.username, 300)
|
||||||
|
resp = {'msg': msg, "choices": ["otp"], "req": seed}
|
||||||
|
return Response(resp, status=300)
|
||||||
|
|
||||||
|
def send_auth_signal(self, success=True, user=None, username='', reason=''):
|
||||||
|
if success:
|
||||||
|
post_auth_success.send(
|
||||||
|
sender=self.__class__, user=user, request=self.request
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
post_auth_failed.send(
|
||||||
|
sender=self.__class__, username=username,
|
||||||
|
request=self.request, reason=reason
|
||||||
|
)
|
|
@ -11,6 +11,7 @@ from django.utils.six import text_type
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from rest_framework import HTTP_HEADER_ENCODING
|
from rest_framework import HTTP_HEADER_ENCODING
|
||||||
from rest_framework import authentication, exceptions
|
from rest_framework import authentication, exceptions
|
||||||
|
from common.auth import signature
|
||||||
from rest_framework.authentication import CSRFCheck
|
from rest_framework.authentication import CSRFCheck
|
||||||
|
|
||||||
from common.utils import get_object_or_none, make_signature, http_to_unixtime
|
from common.utils import get_object_or_none, make_signature, http_to_unixtime
|
||||||
|
@ -108,8 +109,8 @@ class AccessKeyAuthentication(authentication.BaseAuthentication):
|
||||||
|
|
||||||
class AccessTokenAuthentication(authentication.BaseAuthentication):
|
class AccessTokenAuthentication(authentication.BaseAuthentication):
|
||||||
keyword = 'Bearer'
|
keyword = 'Bearer'
|
||||||
model = get_user_model()
|
|
||||||
expiration = settings.TOKEN_EXPIRATION or 3600
|
expiration = settings.TOKEN_EXPIRATION or 3600
|
||||||
|
model = get_user_model()
|
||||||
|
|
||||||
def authenticate(self, request):
|
def authenticate(self, request):
|
||||||
auth = authentication.get_authorization_header(request).split()
|
auth = authentication.get_authorization_header(request).split()
|
||||||
|
@ -133,8 +134,9 @@ class AccessTokenAuthentication(authentication.BaseAuthentication):
|
||||||
return self.authenticate_credentials(token)
|
return self.authenticate_credentials(token)
|
||||||
|
|
||||||
def authenticate_credentials(self, token):
|
def authenticate_credentials(self, token):
|
||||||
|
model = get_user_model()
|
||||||
user_id = cache.get(token)
|
user_id = cache.get(token)
|
||||||
user = get_object_or_none(self.model, id=user_id)
|
user = get_object_or_none(model, id=user_id)
|
||||||
|
|
||||||
if not user:
|
if not user:
|
||||||
msg = _('Invalid token or cache refreshed.')
|
msg = _('Invalid token or cache refreshed.')
|
||||||
|
@ -167,3 +169,25 @@ class SessionAuthentication(authentication.SessionAuthentication):
|
||||||
|
|
||||||
# CSRF passed with authenticated user
|
# CSRF passed with authenticated user
|
||||||
return user, None
|
return user, None
|
||||||
|
|
||||||
|
|
||||||
|
class SignatureAuthentication(signature.SignatureAuthentication):
|
||||||
|
# The HTTP header used to pass the consumer key ID.
|
||||||
|
|
||||||
|
# A method to fetch (User instance, user_secret_string) from the
|
||||||
|
# consumer key ID, or None in case it is not found. Algorithm
|
||||||
|
# will be what the client has sent, in the case that both RSA
|
||||||
|
# and HMAC are supported at your site (and also for expansion).
|
||||||
|
model = get_user_model()
|
||||||
|
|
||||||
|
def fetch_user_data(self, key_id, algorithm="hmac-sha256"):
|
||||||
|
# ...
|
||||||
|
# example implementation:
|
||||||
|
try:
|
||||||
|
key = AccessKey.objects.get(id=key_id)
|
||||||
|
if not key.is_active:
|
||||||
|
return None, None
|
||||||
|
user, secret = key.user, str(key.secret)
|
||||||
|
return user, secret
|
||||||
|
except AccessKey.DoesNotExist:
|
||||||
|
return None, None
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
password_failed = _('Username/password check failed')
|
||||||
|
mfa_failed = _('MFA authentication failed')
|
||||||
|
user_not_exist = _("Username does not exist")
|
||||||
|
password_expired = _("Password expired")
|
||||||
|
user_invalid = _('Disabled or expired')
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Generated by Django 2.1.7 on 2019-07-29 06:23
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
from django.db import migrations, models
|
||||||
|
from django.utils.timezone import utc
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('authentication', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='accesskey',
|
||||||
|
name='date_created',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(2019, 7, 29, 6, 23, 54, 115123, tzinfo=utc)),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='accesskey',
|
||||||
|
name='is_active',
|
||||||
|
field=models.BooleanField(default=True, verbose_name='Active'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -12,6 +12,8 @@ class AccessKey(models.Model):
|
||||||
default=uuid.uuid4, editable=False)
|
default=uuid.uuid4, editable=False)
|
||||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='User',
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='User',
|
||||||
on_delete=models.CASCADE, related_name='access_keys')
|
on_delete=models.CASCADE, related_name='access_keys')
|
||||||
|
is_active = models.BooleanField(default=True, verbose_name=_('Active'))
|
||||||
|
date_created = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
def get_id(self):
|
def get_id(self):
|
||||||
return str(self.id)
|
return str(self.id)
|
||||||
|
|
|
@ -1,20 +1,89 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
from django.core.cache import cache
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from users.models import User
|
||||||
from .models import AccessKey
|
from .models import AccessKey
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['AccessKeySerializer']
|
__all__ = [
|
||||||
|
'AccessKeySerializer', 'OtpVerifySerializer', 'BearerTokenSerializer',
|
||||||
|
'MFAChallengeSerializer',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class AccessKeySerializer(serializers.ModelSerializer):
|
class AccessKeySerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AccessKey
|
model = AccessKey
|
||||||
fields = ['id', 'secret']
|
fields = ['id', 'secret', 'is_active', 'date_created']
|
||||||
read_only_fields = ['id', 'secret']
|
read_only_fields = ['id', 'secret', 'date_created']
|
||||||
|
|
||||||
|
|
||||||
class OtpVerifySerializer(serializers.Serializer):
|
class OtpVerifySerializer(serializers.Serializer):
|
||||||
code = serializers.CharField(max_length=6, min_length=6)
|
code = serializers.CharField(max_length=6, min_length=6)
|
||||||
|
|
||||||
|
|
||||||
|
class BearerTokenMixin(serializers.Serializer):
|
||||||
|
token = serializers.CharField(read_only=True)
|
||||||
|
keyword = serializers.SerializerMethodField()
|
||||||
|
date_expired = serializers.DateTimeField(read_only=True)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_keyword(obj):
|
||||||
|
return 'Bearer'
|
||||||
|
|
||||||
|
def create_response(self, username):
|
||||||
|
request = self.context.get("request")
|
||||||
|
try:
|
||||||
|
user = User.objects.get(username=username)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
raise serializers.ValidationError("username %s not exist" % username)
|
||||||
|
token, date_expired = user.create_bearer_token(request)
|
||||||
|
instance = {
|
||||||
|
"username": username,
|
||||||
|
"token": token,
|
||||||
|
"date_expired": date_expired,
|
||||||
|
}
|
||||||
|
return instance
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BearerTokenSerializer(BearerTokenMixin, serializers.Serializer):
|
||||||
|
username = serializers.CharField()
|
||||||
|
password = serializers.CharField(write_only=True, allow_null=True,
|
||||||
|
required=False)
|
||||||
|
public_key = serializers.CharField(write_only=True, allow_null=True,
|
||||||
|
required=False)
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
username = validated_data.get("username")
|
||||||
|
return self.create_response(username)
|
||||||
|
|
||||||
|
|
||||||
|
class MFAChallengeSerializer(BearerTokenMixin, serializers.Serializer):
|
||||||
|
req = serializers.CharField(write_only=True)
|
||||||
|
auth_type = serializers.CharField(write_only=True)
|
||||||
|
code = serializers.CharField(write_only=True)
|
||||||
|
|
||||||
|
def validate_req(self, attr):
|
||||||
|
username = cache.get(attr)
|
||||||
|
if not username:
|
||||||
|
raise serializers.ValidationError("Not valid, may be expired")
|
||||||
|
self.context["username"] = username
|
||||||
|
|
||||||
|
def validate_code(self, code):
|
||||||
|
username = self.context["username"]
|
||||||
|
user = User.objects.get(username=username)
|
||||||
|
ok = user.check_otp(code)
|
||||||
|
if not ok:
|
||||||
|
msg = "Otp code not valid, may be expired"
|
||||||
|
raise serializers.ValidationError(msg)
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
username = self.context["username"]
|
||||||
|
return self.create_response(username)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
{% extends '_modal.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
{% block modal_id %}access_key_modal{% endblock %}
|
||||||
|
{% block modal_class %}modal-lg{% endblock %}
|
||||||
|
{% block modal_title%}{% trans "API key list" %}{% endblock %}
|
||||||
|
{% block modal_body %}
|
||||||
|
<style>
|
||||||
|
.inmodal .modal-body {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
#access_key_list_table_wrapper {
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<div class="alert alert-info help-message">
|
||||||
|
{% trans 'Using api key sign api header, every requests header difference'%}, <a href="https://tools.ietf.org/html/draft-cavage-http-signatures-08">{% trans 'docs' %} </a>
|
||||||
|
</div>
|
||||||
|
<table class="table table-striped table-bordered table-hover " id="access_key_list_table" style="padding-top: 10px">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-center">
|
||||||
|
<input type="checkbox" id="check_all" class="ipt_check_all" >
|
||||||
|
</th>
|
||||||
|
<th class="text-center">{% trans 'ID' %}</th>
|
||||||
|
<th class="text-center">{% trans 'Secret' %}</th>
|
||||||
|
<th class="text-center">{% trans 'Active' %}</th>
|
||||||
|
<th class="text-center">{% trans 'Date' %}</th>
|
||||||
|
<th class="text-center">{% trans 'Action' %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div id="uc" hidden>
|
||||||
|
<button class="btn btn-primary btn-sm" id="create-btn" href="#"> {% trans "Create" %} </button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var table = null;
|
||||||
|
function initTable() {
|
||||||
|
var options = {
|
||||||
|
ele: $('#access_key_list_table'),
|
||||||
|
columnDefs: [
|
||||||
|
{targets: 2, createdCell: function (td, cellData) {
|
||||||
|
var btn = '<button class="btn btn-primary btn-xs btn-secret" data-secret="SECRET">{% trans 'Show' %}</button>';
|
||||||
|
btn = btn.replace("SECRET", cellData);
|
||||||
|
$(td).html(btn)
|
||||||
|
}},
|
||||||
|
{targets: 3, createdCell: function (td, cellData) {
|
||||||
|
if (cellData) {
|
||||||
|
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||||
|
} else {
|
||||||
|
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
{targets: 4, createdCell: function (td, cellData) {
|
||||||
|
var date = toSafeLocalDateStr(cellData);
|
||||||
|
$(td).html(date)
|
||||||
|
}},
|
||||||
|
{targets: 5, createdCell: function (td, cellData, rowData) {
|
||||||
|
var btn = '';
|
||||||
|
var btn_del = '<a class="btn btn-xs btn-danger m-l-xs btn-del" data-id="ID">{% trans "Delete" %}</a>';
|
||||||
|
var btn_inactive = '<a class="btn btn-xs btn-info m-l-xs btn-inactive" data-id="ID">{% trans "Disable" %}</a>';
|
||||||
|
var btn_active = '<a class="btn btn-xs btn-primary m-l-xs btn-active" data-id="ID">{% trans "Enable" %}</a>';
|
||||||
|
|
||||||
|
btn += btn_del;
|
||||||
|
if (rowData.is_active) {
|
||||||
|
btn += btn_inactive
|
||||||
|
} else {
|
||||||
|
btn += btn_active
|
||||||
|
}
|
||||||
|
btn = btn.replaceAll("ID", cellData);
|
||||||
|
$(td).html(btn);
|
||||||
|
}}
|
||||||
|
],
|
||||||
|
ajax_url: '{% url "api-auth:access-key-list" %}',
|
||||||
|
columns: [
|
||||||
|
{data: "id"},
|
||||||
|
{data: "id"},
|
||||||
|
{data: "secret"},
|
||||||
|
{data: "is_active"},
|
||||||
|
{data: "date_created"},
|
||||||
|
{data: "id", orderable: false}
|
||||||
|
],
|
||||||
|
uc_html: $('#uc').html()
|
||||||
|
};
|
||||||
|
table = jumpserver.initServerSideDataTable(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
}).on("show.bs.modal", "#access_key_modal", function () {
|
||||||
|
if (!table) {
|
||||||
|
initTable()
|
||||||
|
}
|
||||||
|
}).on("click", "#create-btn", function () {
|
||||||
|
var url = "{% url "api-auth:access-key-list" %}";
|
||||||
|
var data = {
|
||||||
|
url: url,
|
||||||
|
method: 'POST',
|
||||||
|
success: function () {
|
||||||
|
table.ajax.reload();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
requestApi(data)
|
||||||
|
}).on("click", ".btn-secret", function () {
|
||||||
|
var $this = $(this);
|
||||||
|
$this.parent().html($this.data("secret"))
|
||||||
|
}).on("click", ".btn-del", function () {
|
||||||
|
var url = "{% url "api-auth:access-key-detail" pk=DEFAULT_PK %}";
|
||||||
|
url = url.replace("{{ DEFAULT_PK }}", $(this).data("id")) ;
|
||||||
|
objectDelete($(this), $(this).data("id"), url);
|
||||||
|
}).on("click", ".btn-active", function () {
|
||||||
|
var url = "{% url "api-auth:access-key-detail" pk=DEFAULT_PK %}";
|
||||||
|
url = url.replace("{{ DEFAULT_PK }}", $(this).data("id")) ;
|
||||||
|
var data = {
|
||||||
|
url: url,
|
||||||
|
body: JSON.stringify({"is_active": true}),
|
||||||
|
method: "PATCH",
|
||||||
|
success: function () {
|
||||||
|
table.ajax.reload();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
requestApi(data)
|
||||||
|
}).on("click", ".btn-inactive", function () {
|
||||||
|
var url = "{% url "api-auth:access-key-detail" pk=DEFAULT_PK %}";
|
||||||
|
url = url.replace("{{ DEFAULT_PK }}", $(this).data("id")) ;
|
||||||
|
var data = {
|
||||||
|
url: url,
|
||||||
|
body: JSON.stringify({"is_active": false}),
|
||||||
|
method: "PATCH",
|
||||||
|
success: function () {
|
||||||
|
table.ajax.reload();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
requestApi(data)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
{% block modal_button %}
|
||||||
|
<button data-dismiss="modal" class="btn btn-white close_btn2" type="button">{% trans "Close" %}</button>
|
||||||
|
{% endblock %}
|
|
@ -18,7 +18,7 @@
|
||||||
<link href="{% static 'css/login-style.css' %}" rel="stylesheet">
|
<link href="{% static 'css/login-style.css' %}" rel="stylesheet">
|
||||||
|
|
||||||
<!-- scripts -->
|
<!-- scripts -->
|
||||||
<script src="{% static 'js/jquery-2.1.1.js' %}"></script>
|
<script src="{% static 'js/jquery-3.1.1.min.js' %}"></script>
|
||||||
<script src="{% static 'js/plugins/sweetalert/sweetalert.min.js' %}"></script>
|
<script src="{% static 'js/plugins/sweetalert/sweetalert.min.js' %}"></script>
|
||||||
<script src="{% static 'js/bootstrap.min.js' %}"></script>
|
<script src="{% static 'js/bootstrap.min.js' %}"></script>
|
||||||
<script src="{% static 'js/plugins/datatables/datatables.min.js' %}"></script>
|
<script src="{% static 'js/plugins/datatables/datatables.min.js' %}"></script>
|
||||||
|
|
|
@ -4,18 +4,27 @@
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
from rest_framework.routers import DefaultRouter
|
||||||
|
|
||||||
from .. import api
|
from .. import api
|
||||||
|
|
||||||
|
router = DefaultRouter()
|
||||||
|
router.register('access-keys', api.AccessKeyViewSet, 'access-key')
|
||||||
|
|
||||||
|
|
||||||
app_name = 'authentication'
|
app_name = 'authentication'
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# path('token/', api.UserToken.as_view(), name='user-token'),
|
# path('token/', api.UserToken.as_view(), name='user-token'),
|
||||||
path('auth/', api.UserAuthApi.as_view(), name='user-auth'),
|
path('auth/', api.UserAuthApi.as_view(), name='user-auth'),
|
||||||
|
path('tokens/', api.TokenCreateApi.as_view(), name='auth-token'),
|
||||||
|
path('mfa/challenge/', api.MFAChallengeApi.as_view(), name='mfa-challenge'),
|
||||||
path('connection-token/',
|
path('connection-token/',
|
||||||
api.UserConnectionTokenApi.as_view(), name='connection-token'),
|
api.UserConnectionTokenApi.as_view(), name='connection-token'),
|
||||||
path('otp/auth/', api.UserOtpAuthApi.as_view(), name='user-otp-auth'),
|
path('otp/auth/', api.UserOtpAuthApi.as_view(), name='user-otp-auth'),
|
||||||
path('otp/verify/', api.UserOtpVerifyApi.as_view(), name='user-otp-verify'),
|
path('otp/verify/', api.UserOtpVerifyApi.as_view(), name='user-otp-verify'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
urlpatterns += router.urls
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from common.utils import get_ip_city, validate_ip
|
from django.contrib.auth import authenticate
|
||||||
|
|
||||||
|
from common.utils import get_ip_city, get_object_or_none, validate_ip
|
||||||
|
from users.models import User
|
||||||
|
from . import const
|
||||||
|
|
||||||
|
|
||||||
def write_login_log(*args, **kwargs):
|
def write_login_log(*args, **kwargs):
|
||||||
|
@ -16,3 +20,36 @@ def write_login_log(*args, **kwargs):
|
||||||
kwargs.update({'ip': ip, 'city': city})
|
kwargs.update({'ip': ip, 'city': city})
|
||||||
UserLoginLog.objects.create(**kwargs)
|
UserLoginLog.objects.create(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def check_user_valid(**kwargs):
|
||||||
|
password = kwargs.pop('password', None)
|
||||||
|
public_key = kwargs.pop('public_key', None)
|
||||||
|
email = kwargs.pop('email', None)
|
||||||
|
username = kwargs.pop('username', None)
|
||||||
|
|
||||||
|
if username:
|
||||||
|
user = get_object_or_none(User, username=username)
|
||||||
|
elif email:
|
||||||
|
user = get_object_or_none(User, email=email)
|
||||||
|
else:
|
||||||
|
user = None
|
||||||
|
|
||||||
|
if user is None:
|
||||||
|
return None, const.user_not_exist
|
||||||
|
elif not user.is_valid:
|
||||||
|
return None, const.user_invalid
|
||||||
|
elif user.password_has_expired:
|
||||||
|
return None, const.password_expired
|
||||||
|
|
||||||
|
if password and authenticate(username=username, password=password):
|
||||||
|
return user, ''
|
||||||
|
|
||||||
|
if public_key and user.public_key:
|
||||||
|
public_key_saved = user.public_key.split()
|
||||||
|
if len(public_key_saved) == 1:
|
||||||
|
if public_key == public_key_saved[0]:
|
||||||
|
return user, ''
|
||||||
|
elif len(public_key_saved) > 1:
|
||||||
|
if public_key == public_key_saved[1]:
|
||||||
|
return user, ''
|
||||||
|
return None, const.password_failed
|
||||||
|
|
|
@ -26,6 +26,7 @@ from users.utils import (
|
||||||
)
|
)
|
||||||
from ..signals import post_auth_success, post_auth_failed
|
from ..signals import post_auth_success, post_auth_failed
|
||||||
from .. import forms
|
from .. import forms
|
||||||
|
from .. import const
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
@ -81,7 +82,7 @@ class UserLoginView(FormView):
|
||||||
user = form.get_user()
|
user = form.get_user()
|
||||||
# user password expired
|
# user password expired
|
||||||
if user.password_has_expired:
|
if user.password_has_expired:
|
||||||
reason = LoginLog.REASON_PASSWORD_EXPIRED
|
reason = const.password_expired
|
||||||
self.send_auth_signal(success=False, username=user.username, reason=reason)
|
self.send_auth_signal(success=False, username=user.username, reason=reason)
|
||||||
return self.render_to_response(self.get_context_data(password_expired=True))
|
return self.render_to_response(self.get_context_data(password_expired=True))
|
||||||
|
|
||||||
|
@ -96,7 +97,7 @@ class UserLoginView(FormView):
|
||||||
# write login failed log
|
# write login failed log
|
||||||
username = form.cleaned_data.get('username')
|
username = form.cleaned_data.get('username')
|
||||||
exist = User.objects.filter(username=username).first()
|
exist = User.objects.filter(username=username).first()
|
||||||
reason = LoginLog.REASON_PASSWORD if exist else LoginLog.REASON_NOT_EXIST
|
reason = const.password_failed if exist else const.user_not_exist
|
||||||
# limit user login failed count
|
# limit user login failed count
|
||||||
ip = get_request_ip(self.request)
|
ip = get_request_ip(self.request)
|
||||||
increase_login_failed_count(username, ip)
|
increase_login_failed_count(username, ip)
|
||||||
|
@ -167,7 +168,7 @@ class UserLoginOtpView(FormView):
|
||||||
else:
|
else:
|
||||||
self.send_auth_signal(
|
self.send_auth_signal(
|
||||||
success=False, username=user.username,
|
success=False, username=user.username,
|
||||||
reason=LoginLog.REASON_MFA
|
reason=const.mfa_failed
|
||||||
)
|
)
|
||||||
form.add_error(
|
form.add_error(
|
||||||
'otp_code', _('MFA code invalid, or ntp sync server time')
|
'otp_code', _('MFA code invalid, or ntp sync server time')
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
|
@ -0,0 +1,112 @@
|
||||||
|
from rest_framework import authentication
|
||||||
|
from rest_framework import exceptions
|
||||||
|
|
||||||
|
from httpsig import HeaderVerifier, utils
|
||||||
|
|
||||||
|
"""
|
||||||
|
Reusing failure exceptions serves several purposes:
|
||||||
|
|
||||||
|
1. Lack of useful information regarding the failure inhibits attackers
|
||||||
|
from learning about valid keyIDs or other forms of information leakage.
|
||||||
|
Using the same actual object for any failure makes preventing such
|
||||||
|
leakage through mistakenly-distinct error messages less likely.
|
||||||
|
|
||||||
|
2. In an API scenario, the object is created once and raised many times
|
||||||
|
rather than generated on every failure, which could lead to higher loads
|
||||||
|
or memory usage in high-volume attack scenarios.
|
||||||
|
|
||||||
|
"""
|
||||||
|
FAILED = exceptions.AuthenticationFailed('Invalid signature.')
|
||||||
|
|
||||||
|
|
||||||
|
class SignatureAuthentication(authentication.BaseAuthentication):
|
||||||
|
"""
|
||||||
|
DRF authentication class for HTTP Signature support.
|
||||||
|
|
||||||
|
You must subclass this class in your own project and implement the
|
||||||
|
`fetch_user_data(self, keyId, algorithm)` method, returning a tuple of
|
||||||
|
the User object and a bytes object containing the user's secret. Note
|
||||||
|
that key_id and algorithm are DIRTY as they are supplied by the client
|
||||||
|
and so must be verified in your subclass!
|
||||||
|
|
||||||
|
You may set the following class properties in your subclass to configure
|
||||||
|
authentication for your particular use case:
|
||||||
|
|
||||||
|
:param www_authenticate_realm: Default: "api"
|
||||||
|
:param required_headers: Default: ["(request-target)", "date"]
|
||||||
|
"""
|
||||||
|
|
||||||
|
www_authenticate_realm = "api"
|
||||||
|
required_headers = ["(request-target)", "date"]
|
||||||
|
|
||||||
|
def fetch_user_data(self, key_id, algorithm=None):
|
||||||
|
"""Retuns a tuple (User, secret) or (None, None)."""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def authenticate_header(self, request):
|
||||||
|
"""
|
||||||
|
DRF sends this for unauthenticated responses if we're the primary
|
||||||
|
authenticator.
|
||||||
|
"""
|
||||||
|
h = " ".join(self.required_headers)
|
||||||
|
return 'Signature realm="%s",headers="%s"' % (
|
||||||
|
self.www_authenticate_realm, h)
|
||||||
|
|
||||||
|
def authenticate(self, request):
|
||||||
|
"""
|
||||||
|
Perform the actual authentication.
|
||||||
|
|
||||||
|
Note that the exception raised is always the same. This is so that we
|
||||||
|
don't leak information about in/valid keyIds and other such useful
|
||||||
|
things.
|
||||||
|
"""
|
||||||
|
auth_header = authentication.get_authorization_header(request)
|
||||||
|
if not auth_header or len(auth_header) == 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
method, fields = utils.parse_authorization_header(auth_header)
|
||||||
|
|
||||||
|
# Ignore foreign Authorization headers.
|
||||||
|
if method.lower() != 'signature':
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Verify basic header structure.
|
||||||
|
if len(fields) == 0:
|
||||||
|
raise FAILED
|
||||||
|
|
||||||
|
# Ensure all required fields were included.
|
||||||
|
if len({"keyid", "algorithm", "signature"} - set(fields.keys())) > 0:
|
||||||
|
raise FAILED
|
||||||
|
|
||||||
|
# Fetch the secret associated with the keyid
|
||||||
|
user, secret = self.fetch_user_data(
|
||||||
|
fields["keyid"],
|
||||||
|
algorithm=fields["algorithm"]
|
||||||
|
)
|
||||||
|
|
||||||
|
if not (user and secret):
|
||||||
|
raise FAILED
|
||||||
|
|
||||||
|
# Gather all request headers and translate them as stated in the Django docs:
|
||||||
|
# https://docs.djangoproject.com/en/1.6/ref/request-response/#django.http.HttpRequest.META
|
||||||
|
headers = {}
|
||||||
|
for key in request.META.keys():
|
||||||
|
if key.startswith("HTTP_") or \
|
||||||
|
key in ("CONTENT_TYPE", "CONTENT_LENGTH"):
|
||||||
|
header = key[5:].lower().replace('_', '-')
|
||||||
|
headers[header] = request.META[key]
|
||||||
|
|
||||||
|
# Verify headers
|
||||||
|
hs = HeaderVerifier(
|
||||||
|
headers,
|
||||||
|
secret,
|
||||||
|
required_headers=self.required_headers,
|
||||||
|
method=request.method.lower(),
|
||||||
|
path=request.get_full_path()
|
||||||
|
)
|
||||||
|
|
||||||
|
# All of that just to get to this.
|
||||||
|
if not hs.verify():
|
||||||
|
raise FAILED
|
||||||
|
|
||||||
|
return user, fields["keyid"]
|
|
@ -1,9 +1,14 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
from .mixins import BulkListSerializerMixin
|
|
||||||
from rest_framework_bulk.serializers import BulkListSerializer
|
from rest_framework_bulk.serializers import BulkListSerializer
|
||||||
|
from rest_framework import serializers
|
||||||
|
from .mixins import BulkListSerializerMixin
|
||||||
|
|
||||||
|
|
||||||
class AdaptedBulkListSerializer(BulkListSerializerMixin, BulkListSerializer):
|
class AdaptedBulkListSerializer(BulkListSerializerMixin, BulkListSerializer):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CeleryTaskSerializer(serializers.Serializer):
|
||||||
|
task = serializers.CharField(read_only=True)
|
||||||
|
|
|
@ -29,6 +29,6 @@ def send_mail_async(*args, **kwargs):
|
||||||
args = tuple(args)
|
args = tuple(args)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
send_mail(*args, **kwargs)
|
return send_mail(*args, **kwargs)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Sending mail error: {}".format(e))
|
logger.error("Sending mail error: {}".format(e))
|
||||||
|
|
|
@ -31,6 +31,10 @@ def get_logger(name=None):
|
||||||
return logging.getLogger('jumpserver.%s' % name)
|
return logging.getLogger('jumpserver.%s' % name)
|
||||||
|
|
||||||
|
|
||||||
|
def get_syslogger(name=None):
|
||||||
|
return logging.getLogger('jms.%s' % name)
|
||||||
|
|
||||||
|
|
||||||
def timesince(dt, since='', default="just now"):
|
def timesince(dt, since='', default="just now"):
|
||||||
"""
|
"""
|
||||||
Returns string representing "time since" e.g.
|
Returns string representing "time since" e.g.
|
||||||
|
|
|
@ -379,6 +379,8 @@ defaults = {
|
||||||
'ASSETS_PERM_CACHE_TIME': 3600*24,
|
'ASSETS_PERM_CACHE_TIME': 3600*24,
|
||||||
'SECURITY_MFA_VERIFY_TTL': 3600,
|
'SECURITY_MFA_VERIFY_TTL': 3600,
|
||||||
'ASSETS_PERM_CACHE_ENABLE': False,
|
'ASSETS_PERM_CACHE_ENABLE': False,
|
||||||
|
'SYSLOG_ADDR': '', # '192.168.0.1:514'
|
||||||
|
'SYSLOG_FACILITY': 'user',
|
||||||
'PERM_SINGLE_ASSET_TO_UNGROUP_NODE': False,
|
'PERM_SINGLE_ASSET_TO_UNGROUP_NODE': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -217,6 +217,9 @@ LOGGING = {
|
||||||
'simple': {
|
'simple': {
|
||||||
'format': '%(levelname)s %(message)s'
|
'format': '%(levelname)s %(message)s'
|
||||||
},
|
},
|
||||||
|
'syslog': {
|
||||||
|
'format': 'jumpserver: %(message)s'
|
||||||
|
},
|
||||||
'msg': {
|
'msg': {
|
||||||
'format': '%(message)s'
|
'format': '%(message)s'
|
||||||
}
|
}
|
||||||
|
@ -249,19 +252,10 @@ LOGGING = {
|
||||||
'backupCount': 7,
|
'backupCount': 7,
|
||||||
'filename': ANSIBLE_LOG_FILE,
|
'filename': ANSIBLE_LOG_FILE,
|
||||||
},
|
},
|
||||||
'gunicorn_file': {
|
'syslog': {
|
||||||
'encoding': 'utf8',
|
'level': 'INFO',
|
||||||
'level': 'DEBUG',
|
'class': 'logging.NullHandler',
|
||||||
'class': 'logging.handlers.RotatingFileHandler',
|
'formatter': 'syslog'
|
||||||
'formatter': 'msg',
|
|
||||||
'maxBytes': 1024*1024*100,
|
|
||||||
'backupCount': 2,
|
|
||||||
'filename': GUNICORN_LOG_FILE,
|
|
||||||
},
|
|
||||||
'gunicorn_console': {
|
|
||||||
'level': 'DEBUG',
|
|
||||||
'class': 'logging.StreamHandler',
|
|
||||||
'formatter': 'msg'
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'loggers': {
|
'loggers': {
|
||||||
|
@ -271,25 +265,17 @@ LOGGING = {
|
||||||
'level': LOG_LEVEL,
|
'level': LOG_LEVEL,
|
||||||
},
|
},
|
||||||
'django.request': {
|
'django.request': {
|
||||||
'handlers': ['console', 'file'],
|
'handlers': ['console', 'file', 'syslog'],
|
||||||
'level': LOG_LEVEL,
|
'level': LOG_LEVEL,
|
||||||
'propagate': False,
|
'propagate': False,
|
||||||
},
|
},
|
||||||
'django.server': {
|
'django.server': {
|
||||||
'handlers': ['console', 'file'],
|
'handlers': ['console', 'file', 'syslog'],
|
||||||
'level': LOG_LEVEL,
|
'level': LOG_LEVEL,
|
||||||
'propagate': False,
|
'propagate': False,
|
||||||
},
|
},
|
||||||
'jumpserver': {
|
'jumpserver': {
|
||||||
'handlers': ['console', 'file'],
|
'handlers': ['console', 'file', 'syslog'],
|
||||||
'level': LOG_LEVEL,
|
|
||||||
},
|
|
||||||
'jumpserver.users.api': {
|
|
||||||
'handlers': ['console', 'file'],
|
|
||||||
'level': LOG_LEVEL,
|
|
||||||
},
|
|
||||||
'jumpserver.users.view': {
|
|
||||||
'handlers': ['console', 'file'],
|
|
||||||
'level': LOG_LEVEL,
|
'level': LOG_LEVEL,
|
||||||
},
|
},
|
||||||
'ops.ansible_api': {
|
'ops.ansible_api': {
|
||||||
|
@ -300,17 +286,28 @@ LOGGING = {
|
||||||
'handlers': ['console', 'file'],
|
'handlers': ['console', 'file'],
|
||||||
'level': "INFO",
|
'level': "INFO",
|
||||||
},
|
},
|
||||||
'gunicorn': {
|
'jms_audits': {
|
||||||
'handlers': ['gunicorn_console', 'gunicorn_file'],
|
'handlers': ['syslog'],
|
||||||
'level': 'INFO',
|
'level': 'INFO'
|
||||||
},
|
},
|
||||||
# 'django.db': {
|
'django.db': {
|
||||||
# 'handlers': ['console', 'file'],
|
'handlers': ['console', 'file'],
|
||||||
# 'level': 'DEBUG'
|
'level': 'DEBUG'
|
||||||
# }
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SYSLOG_ENABLE = False
|
||||||
|
|
||||||
|
if CONFIG.SYSLOG_ADDR != '' and len(CONFIG.SYSLOG_ADDR.split(':')) == 2:
|
||||||
|
host, port = CONFIG.SYSLOG_ADDR.split(':')
|
||||||
|
SYSLOG_ENABLE = True
|
||||||
|
LOGGING['handlers']['syslog'].update({
|
||||||
|
'class': 'logging.handlers.SysLogHandler',
|
||||||
|
'facility': CONFIG.SYSLOG_FACILITY,
|
||||||
|
'address': (host, int(port)),
|
||||||
|
})
|
||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/1.10/topics/i18n/
|
# https://docs.djangoproject.com/en/1.10/topics/i18n/
|
||||||
# LANGUAGE_CODE = 'en'
|
# LANGUAGE_CODE = 'en'
|
||||||
|
@ -391,6 +388,7 @@ REST_FRAMEWORK = {
|
||||||
'authentication.backends.api.AccessKeyAuthentication',
|
'authentication.backends.api.AccessKeyAuthentication',
|
||||||
'authentication.backends.api.AccessTokenAuthentication',
|
'authentication.backends.api.AccessTokenAuthentication',
|
||||||
'authentication.backends.api.PrivateTokenAuthentication',
|
'authentication.backends.api.PrivateTokenAuthentication',
|
||||||
|
'authentication.backends.api.SignatureAuthentication',
|
||||||
'authentication.backends.api.SessionAuthentication',
|
'authentication.backends.api.SessionAuthentication',
|
||||||
),
|
),
|
||||||
'DEFAULT_FILTER_BACKENDS': (
|
'DEFAULT_FILTER_BACKENDS': (
|
||||||
|
@ -403,7 +401,7 @@ REST_FRAMEWORK = {
|
||||||
'SEARCH_PARAM': "search",
|
'SEARCH_PARAM': "search",
|
||||||
'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S %z',
|
'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S %z',
|
||||||
'DATETIME_INPUT_FORMATS': ['iso-8601', '%Y-%m-%d %H:%M:%S %z'],
|
'DATETIME_INPUT_FORMATS': ['iso-8601', '%Y-%m-%d %H:%M:%S %z'],
|
||||||
# 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
|
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
|
||||||
# 'PAGE_SIZE': 15
|
# 'PAGE_SIZE': 15
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -601,9 +599,12 @@ USER_GUIDE_URL = ""
|
||||||
|
|
||||||
SWAGGER_SETTINGS = {
|
SWAGGER_SETTINGS = {
|
||||||
'DEFAULT_AUTO_SCHEMA_CLASS': 'jumpserver.swagger.CustomSwaggerAutoSchema',
|
'DEFAULT_AUTO_SCHEMA_CLASS': 'jumpserver.swagger.CustomSwaggerAutoSchema',
|
||||||
|
'USE_SESSION_AUTH': True,
|
||||||
'SECURITY_DEFINITIONS': {
|
'SECURITY_DEFINITIONS': {
|
||||||
'basic': {
|
'Bearer': {
|
||||||
'type': 'basic'
|
'type': 'apiKey',
|
||||||
|
'name': 'Authorization',
|
||||||
|
'in': 'header'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,13 +7,44 @@ from drf_yasg import openapi
|
||||||
|
|
||||||
class CustomSwaggerAutoSchema(SwaggerAutoSchema):
|
class CustomSwaggerAutoSchema(SwaggerAutoSchema):
|
||||||
def get_tags(self, operation_keys):
|
def get_tags(self, operation_keys):
|
||||||
if len(operation_keys) > 2 and operation_keys[1].startswith('v'):
|
if len(operation_keys) > 2:
|
||||||
return [operation_keys[2]]
|
return [operation_keys[0] + '_' + operation_keys[1]]
|
||||||
return super().get_tags(operation_keys)
|
return super().get_tags(operation_keys)
|
||||||
|
|
||||||
|
def get_operation_id(self, operation_keys):
|
||||||
|
action = ''
|
||||||
|
dump_keys = [k for k in operation_keys]
|
||||||
|
if hasattr(self.view, 'action'):
|
||||||
|
action = self.view.action
|
||||||
|
if action == "bulk_destroy":
|
||||||
|
action = "bulk_delete"
|
||||||
|
if dump_keys[-2] == "children":
|
||||||
|
if self.path.find('id') < 0:
|
||||||
|
dump_keys.insert(-2, "root")
|
||||||
|
if dump_keys[0] == "perms" and dump_keys[1] == "users":
|
||||||
|
if self.path.find('{id}') < 0:
|
||||||
|
dump_keys.insert(2, "my")
|
||||||
|
if action.replace('bulk_', '') == dump_keys[-1]:
|
||||||
|
dump_keys[-1] = action
|
||||||
|
return super().get_operation_id(tuple(dump_keys))
|
||||||
|
|
||||||
|
def get_operation(self, operation_keys):
|
||||||
|
operation = super().get_operation(operation_keys)
|
||||||
|
operation.summary = operation.operation_id
|
||||||
|
return operation
|
||||||
|
|
||||||
|
|
||||||
def get_swagger_view(version='v1'):
|
def get_swagger_view(version='v1'):
|
||||||
from .urls import api_v1_patterns, api_v2_patterns
|
from .urls import api_v1, api_v2
|
||||||
|
from django.urls import path, include
|
||||||
|
api_v1_patterns = [
|
||||||
|
path('api/v1/', include(api_v1))
|
||||||
|
]
|
||||||
|
|
||||||
|
api_v2_patterns = [
|
||||||
|
path('api/v2/', include(api_v2))
|
||||||
|
]
|
||||||
|
|
||||||
if version == "v2":
|
if version == "v2":
|
||||||
patterns = api_v2_patterns
|
patterns = api_v2_patterns
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -7,26 +7,26 @@ from django.conf.urls.static import static
|
||||||
from django.conf.urls.i18n import i18n_patterns
|
from django.conf.urls.i18n import i18n_patterns
|
||||||
from django.views.i18n import JavaScriptCatalog
|
from django.views.i18n import JavaScriptCatalog
|
||||||
|
|
||||||
from .views import IndexView, LunaView, I18NView, HealthCheckView
|
from .views import IndexView, LunaView, I18NView, HealthCheckView, redirect_format_api
|
||||||
from .swagger import get_swagger_view
|
from .swagger import get_swagger_view
|
||||||
|
|
||||||
api_v1 = [
|
api_v1 = [
|
||||||
path('users/v1/', include('users.urls.api_urls', namespace='api-users')),
|
path('users/', include('users.urls.api_urls', namespace='api-users')),
|
||||||
path('assets/v1/', include('assets.urls.api_urls', namespace='api-assets')),
|
path('assets/', include('assets.urls.api_urls', namespace='api-assets')),
|
||||||
path('perms/v1/', include('perms.urls.api_urls', namespace='api-perms')),
|
path('perms/', include('perms.urls.api_urls', namespace='api-perms')),
|
||||||
path('terminal/v1/', include('terminal.urls.api_urls', namespace='api-terminal')),
|
path('terminal/', include('terminal.urls.api_urls', namespace='api-terminal')),
|
||||||
path('ops/v1/', include('ops.urls.api_urls', namespace='api-ops')),
|
path('ops/', include('ops.urls.api_urls', namespace='api-ops')),
|
||||||
path('audits/v1/', include('audits.urls.api_urls', namespace='api-audits')),
|
path('audits/', include('audits.urls.api_urls', namespace='api-audits')),
|
||||||
path('orgs/v1/', include('orgs.urls.api_urls', namespace='api-orgs')),
|
path('orgs/', include('orgs.urls.api_urls', namespace='api-orgs')),
|
||||||
path('settings/v1/', include('settings.urls.api_urls', namespace='api-settings')),
|
path('settings/', include('settings.urls.api_urls', namespace='api-settings')),
|
||||||
path('authentication/v1/', include('authentication.urls.api_urls', namespace='api-auth')),
|
path('authentication/', include('authentication.urls.api_urls', namespace='api-auth')),
|
||||||
path('common/v1/', include('common.urls.api_urls', namespace='api-common')),
|
path('common/', include('common.urls.api_urls', namespace='api-common')),
|
||||||
path('applications/v1/', include('applications.urls.api_urls', namespace='api-applications')),
|
path('applications/', include('applications.urls.api_urls', namespace='api-applications')),
|
||||||
]
|
]
|
||||||
|
|
||||||
api_v2 = [
|
api_v2 = [
|
||||||
path('terminal/v2/', include('terminal.urls.api_urls_v2', namespace='api-terminal-v2')),
|
path('terminal/', include('terminal.urls.api_urls_v2', namespace='api-terminal-v2')),
|
||||||
path('users/v2/', include('users.urls.api_urls_v2', namespace='api-users-v2')),
|
path('users/', include('users.urls.api_urls_v2', namespace='api-users-v2')),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,30 +48,23 @@ if settings.XPACK_ENABLED:
|
||||||
path('xpack/', include('xpack.urls.view_urls', namespace='xpack'))
|
path('xpack/', include('xpack.urls.view_urls', namespace='xpack'))
|
||||||
)
|
)
|
||||||
api_v1.append(
|
api_v1.append(
|
||||||
path('xpack/v1/', include('xpack.urls.api_urls', namespace='api-xpack'))
|
path('xpack/', include('xpack.urls.api_urls', namespace='api-xpack'))
|
||||||
)
|
)
|
||||||
|
|
||||||
js_i18n_patterns = i18n_patterns(
|
js_i18n_patterns = i18n_patterns(
|
||||||
path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'),
|
path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'),
|
||||||
)
|
)
|
||||||
|
|
||||||
api_v1_patterns = [
|
|
||||||
path('api/', include(api_v1))
|
|
||||||
]
|
|
||||||
|
|
||||||
api_v2_patterns = [
|
|
||||||
path('api/', include(api_v2))
|
|
||||||
]
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', IndexView.as_view(), name='index'),
|
path('', IndexView.as_view(), name='index'),
|
||||||
path('', include(api_v2_patterns)),
|
path('api/v1/', include(api_v1)),
|
||||||
path('', include(api_v1_patterns)),
|
path('api/v2/', include(api_v2)),
|
||||||
|
re_path('api/(?P<app>\w+)/(?P<version>v\d)/.*', redirect_format_api),
|
||||||
path('api/health/', HealthCheckView.as_view(), name="health"),
|
path('api/health/', HealthCheckView.as_view(), name="health"),
|
||||||
path('luna/', LunaView.as_view(), name='luna-view'),
|
path('luna/', LunaView.as_view(), name='luna-view'),
|
||||||
path('i18n/<str:lang>/', I18NView.as_view(), name='i18n-switch'),
|
path('i18n/<str:lang>/', I18NView.as_view(), name='i18n-switch'),
|
||||||
path('settings/', include('settings.urls.view_urls', namespace='settings')),
|
path('settings/', include('settings.urls.view_urls', namespace='settings')),
|
||||||
# path('api/v2/', include(api_v2_patterns)),
|
|
||||||
|
|
||||||
# External apps url
|
# External apps url
|
||||||
path('captcha/', include('captcha.urls')),
|
path('captcha/', include('captcha.urls')),
|
||||||
|
|
|
@ -2,14 +2,13 @@ import datetime
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect, JsonResponse
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.views.generic import TemplateView, View
|
from django.views.generic import TemplateView, View
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
@ -208,7 +207,7 @@ class I18NView(View):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
api_url_pattern = re.compile(r'^/api/(?P<version>\w+)/(?P<app>\w+)/(?P<extra>.*)$')
|
api_url_pattern = re.compile(r'^/api/(?P<app>\w+)/(?P<version>v\d)/(?P<extra>.*)$')
|
||||||
|
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
|
@ -216,18 +215,16 @@ def redirect_format_api(request, *args, **kwargs):
|
||||||
_path, query = request.path, request.GET.urlencode()
|
_path, query = request.path, request.GET.urlencode()
|
||||||
matched = api_url_pattern.match(_path)
|
matched = api_url_pattern.match(_path)
|
||||||
if matched:
|
if matched:
|
||||||
version, app, extra = matched.groups()
|
kwargs = matched.groupdict()
|
||||||
_path = '/api/{app}/{version}/{extra}?{query}'.format(**{
|
kwargs["query"] = query
|
||||||
"app": app, "version": version, "extra": extra,
|
_path = '/api/{version}/{app}/{extra}?{query}'.format(**kwargs).rstrip("?")
|
||||||
"query": query
|
|
||||||
})
|
|
||||||
return HttpResponseTemporaryRedirect(_path)
|
return HttpResponseTemporaryRedirect(_path)
|
||||||
else:
|
else:
|
||||||
return Response({"msg": "Redirect url failed: {}".format(_path)}, status=404)
|
return JsonResponse({"msg": "Redirect url failed: {}".format(_path)}, status=404)
|
||||||
|
|
||||||
|
|
||||||
class HealthCheckView(APIView):
|
class HealthCheckView(APIView):
|
||||||
permission_classes = ()
|
permission_classes = ()
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
return Response({"status": 1, "time": int(time.time())})
|
return JsonResponse({"status": 1, "time": int(time.time())})
|
||||||
|
|
Binary file not shown.
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Jumpserver 0.3.3\n"
|
"Project-Id-Version: Jumpserver 0.3.3\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2019-07-31 16:35+0800\n"
|
"POT-Creation-Date: 2019-08-07 16:56+0800\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
||||||
"Language-Team: Jumpserver team<ibuler@qq.com>\n"
|
"Language-Team: Jumpserver team<ibuler@qq.com>\n"
|
||||||
|
@ -78,7 +78,7 @@ msgstr "运行参数"
|
||||||
#: assets/forms/domain.py:15 assets/forms/label.py:13
|
#: assets/forms/domain.py:15 assets/forms/label.py:13
|
||||||
#: assets/models/asset.py:318 assets/models/authbook.py:24
|
#: assets/models/asset.py:318 assets/models/authbook.py:24
|
||||||
#: assets/serializers/admin_user.py:32 assets/serializers/asset_user.py:81
|
#: assets/serializers/admin_user.py:32 assets/serializers/asset_user.py:81
|
||||||
#: assets/serializers/system_user.py:30
|
#: assets/serializers/system_user.py:31
|
||||||
#: assets/templates/assets/admin_user_list.html:46
|
#: assets/templates/assets/admin_user_list.html:46
|
||||||
#: assets/templates/assets/domain_detail.html:60
|
#: assets/templates/assets/domain_detail.html:60
|
||||||
#: assets/templates/assets/domain_list.html:26
|
#: assets/templates/assets/domain_list.html:26
|
||||||
|
@ -218,7 +218,7 @@ msgstr "参数"
|
||||||
#: perms/models/asset_permission.py:117 perms/models/base.py:41
|
#: perms/models/asset_permission.py:117 perms/models/base.py:41
|
||||||
#: perms/templates/perms/asset_permission_detail.html:98
|
#: perms/templates/perms/asset_permission_detail.html:98
|
||||||
#: perms/templates/perms/remote_app_permission_detail.html:90
|
#: perms/templates/perms/remote_app_permission_detail.html:90
|
||||||
#: users/models/user.py:372 users/serializers/v1.py:120
|
#: users/models/user.py:372 users/serializers/v1.py:119
|
||||||
#: users/templates/users/user_detail.html:111
|
#: users/templates/users/user_detail.html:111
|
||||||
#: xpack/plugins/change_auth_plan/models.py:106
|
#: xpack/plugins/change_auth_plan/models.py:106
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113
|
||||||
|
@ -471,6 +471,7 @@ msgstr "更新"
|
||||||
#: assets/templates/assets/label_list.html:40
|
#: assets/templates/assets/label_list.html:40
|
||||||
#: assets/templates/assets/system_user_detail.html:30
|
#: assets/templates/assets/system_user_detail.html:30
|
||||||
#: assets/templates/assets/system_user_list.html:86 audits/models.py:34
|
#: assets/templates/assets/system_user_list.html:86 audits/models.py:34
|
||||||
|
#: authentication/templates/authentication/_access_key_modal.html:65
|
||||||
#: ops/templates/ops/task_list.html:64
|
#: ops/templates/ops/task_list.html:64
|
||||||
#: perms/templates/perms/asset_permission_detail.html:34
|
#: perms/templates/perms/asset_permission_detail.html:34
|
||||||
#: perms/templates/perms/asset_permission_list.html:179
|
#: perms/templates/perms/asset_permission_list.html:179
|
||||||
|
@ -527,6 +528,7 @@ msgstr "创建远程应用"
|
||||||
#: assets/templates/assets/system_user_list.html:60 audits/models.py:38
|
#: assets/templates/assets/system_user_list.html:60 audits/models.py:38
|
||||||
#: audits/templates/audits/operate_log_list.html:41
|
#: audits/templates/audits/operate_log_list.html:41
|
||||||
#: audits/templates/audits/operate_log_list.html:67
|
#: audits/templates/audits/operate_log_list.html:67
|
||||||
|
#: authentication/templates/authentication/_access_key_modal.html:30
|
||||||
#: ops/templates/ops/adhoc_history.html:59 ops/templates/ops/task_adhoc.html:64
|
#: ops/templates/ops/adhoc_history.html:59 ops/templates/ops/task_adhoc.html:64
|
||||||
#: ops/templates/ops/task_history.html:65 ops/templates/ops/task_list.html:34
|
#: ops/templates/ops/task_history.html:65 ops/templates/ops/task_list.html:34
|
||||||
#: perms/forms/asset_permission.py:21
|
#: perms/forms/asset_permission.py:21
|
||||||
|
@ -579,15 +581,11 @@ msgstr "远程应用详情"
|
||||||
msgid "My RemoteApp"
|
msgid "My RemoteApp"
|
||||||
msgstr "我的远程应用"
|
msgstr "我的远程应用"
|
||||||
|
|
||||||
#: assets/api/asset.py:51
|
#: assets/api/asset.py:42
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(hostname)s was %(action)s successfully"
|
msgid "%(hostname)s was %(action)s successfully"
|
||||||
msgstr "%(hostname)s %(action)s成功"
|
msgstr "%(hostname)s %(action)s成功"
|
||||||
|
|
||||||
#: assets/api/asset.py:125
|
|
||||||
msgid "Please select assets that need to be updated"
|
|
||||||
msgstr "请选择需要更新的资产"
|
|
||||||
|
|
||||||
#: assets/api/node.py:61
|
#: assets/api/node.py:61
|
||||||
msgid "You can't update the root node name"
|
msgid "You can't update the root node name"
|
||||||
msgstr "不能修改根节点名称"
|
msgstr "不能修改根节点名称"
|
||||||
|
@ -609,7 +607,7 @@ msgstr "不可达"
|
||||||
msgid "Reachable"
|
msgid "Reachable"
|
||||||
msgstr "可连接"
|
msgstr "可连接"
|
||||||
|
|
||||||
#: assets/const.py:79 assets/models/utils.py:45 authentication/utils.py:9
|
#: assets/const.py:79 assets/models/utils.py:45 authentication/utils.py:13
|
||||||
#: xpack/plugins/license/models.py:78
|
#: xpack/plugins/license/models.py:78
|
||||||
msgid "Unknown"
|
msgid "Unknown"
|
||||||
msgstr "未知"
|
msgstr "未知"
|
||||||
|
@ -656,7 +654,7 @@ msgid "Domain"
|
||||||
msgstr "网域"
|
msgstr "网域"
|
||||||
|
|
||||||
#: assets/forms/asset.py:58 assets/forms/asset.py:80 assets/forms/asset.py:93
|
#: assets/forms/asset.py:58 assets/forms/asset.py:80 assets/forms/asset.py:93
|
||||||
#: assets/forms/asset.py:128 assets/models/node.py:253
|
#: assets/forms/asset.py:128 assets/models/node.py:254
|
||||||
#: assets/templates/assets/asset_create.html:42
|
#: assets/templates/assets/asset_create.html:42
|
||||||
#: perms/forms/asset_permission.py:72 perms/forms/asset_permission.py:79
|
#: perms/forms/asset_permission.py:72 perms/forms/asset_permission.py:79
|
||||||
#: perms/models/asset_permission.py:112
|
#: perms/models/asset_permission.py:112
|
||||||
|
@ -718,7 +716,7 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC"
|
||||||
#: assets/templates/assets/admin_user_list.html:45
|
#: assets/templates/assets/admin_user_list.html:45
|
||||||
#: assets/templates/assets/domain_gateway_list.html:71
|
#: assets/templates/assets/domain_gateway_list.html:71
|
||||||
#: assets/templates/assets/system_user_detail.html:62
|
#: assets/templates/assets/system_user_detail.html:62
|
||||||
#: assets/templates/assets/system_user_list.html:52 audits/models.py:94
|
#: assets/templates/assets/system_user_list.html:52 audits/models.py:80
|
||||||
#: audits/templates/audits/login_log_list.html:51 authentication/forms.py:13
|
#: audits/templates/audits/login_log_list.html:51 authentication/forms.py:13
|
||||||
#: authentication/templates/authentication/login.html:65
|
#: authentication/templates/authentication/login.html:65
|
||||||
#: authentication/templates/authentication/new_login.html:92
|
#: authentication/templates/authentication/new_login.html:92
|
||||||
|
@ -1116,8 +1114,8 @@ msgstr "默认资产组"
|
||||||
#: terminal/templates/terminal/command_list.html:65
|
#: terminal/templates/terminal/command_list.html:65
|
||||||
#: terminal/templates/terminal/session_list.html:27
|
#: terminal/templates/terminal/session_list.html:27
|
||||||
#: terminal/templates/terminal/session_list.html:71 users/forms.py:316
|
#: terminal/templates/terminal/session_list.html:71 users/forms.py:316
|
||||||
#: users/models/user.py:128 users/models/user.py:458
|
#: users/models/user.py:127 users/models/user.py:458
|
||||||
#: users/serializers/v1.py:109 users/templates/users/user_group_detail.html:78
|
#: users/serializers/v1.py:108 users/templates/users/user_group_detail.html:78
|
||||||
#: users/templates/users/user_group_list.html:36 users/views/user.py:243
|
#: users/templates/users/user_group_list.html:36 users/views/user.py:243
|
||||||
#: xpack/plugins/orgs/forms.py:26
|
#: xpack/plugins/orgs/forms.py:26
|
||||||
#: xpack/plugins/orgs/templates/orgs/org_detail.html:113
|
#: xpack/plugins/orgs/templates/orgs/org_detail.html:113
|
||||||
|
@ -1125,7 +1123,7 @@ msgstr "默认资产组"
|
||||||
msgid "User"
|
msgid "User"
|
||||||
msgstr "用户"
|
msgstr "用户"
|
||||||
|
|
||||||
#: assets/models/label.py:19 assets/models/node.py:244
|
#: assets/models/label.py:19 assets/models/node.py:245
|
||||||
#: assets/templates/assets/label_list.html:15 settings/models.py:30
|
#: assets/templates/assets/label_list.html:15 settings/models.py:30
|
||||||
msgid "Value"
|
msgid "Value"
|
||||||
msgstr "值"
|
msgstr "值"
|
||||||
|
@ -1134,11 +1132,11 @@ msgstr "值"
|
||||||
msgid "Category"
|
msgid "Category"
|
||||||
msgstr "分类"
|
msgstr "分类"
|
||||||
|
|
||||||
#: assets/models/node.py:243
|
#: assets/models/node.py:244
|
||||||
msgid "Key"
|
msgid "Key"
|
||||||
msgstr "键"
|
msgstr "键"
|
||||||
|
|
||||||
#: assets/models/node.py:301
|
#: assets/models/node.py:302
|
||||||
msgid "New node"
|
msgid "New node"
|
||||||
msgstr "新节点"
|
msgstr "新节点"
|
||||||
|
|
||||||
|
@ -1244,98 +1242,98 @@ msgstr "密钥不合法"
|
||||||
msgid "The same level node name cannot be the same"
|
msgid "The same level node name cannot be the same"
|
||||||
msgstr "同级别节点名字不能重复"
|
msgstr "同级别节点名字不能重复"
|
||||||
|
|
||||||
#: assets/serializers/system_user.py:31
|
#: assets/serializers/system_user.py:32
|
||||||
msgid "Login mode display"
|
msgid "Login mode display"
|
||||||
msgstr "登录模式显示"
|
msgstr "登录模式显示"
|
||||||
|
|
||||||
#: assets/serializers/system_user.py:66
|
#: assets/serializers/system_user.py:67
|
||||||
msgid "* Automatic login mode must fill in the username."
|
msgid "* Automatic login mode must fill in the username."
|
||||||
msgstr "自动登录模式,必须填写用户名"
|
msgstr "自动登录模式,必须填写用户名"
|
||||||
|
|
||||||
#: assets/serializers/system_user.py:75
|
#: assets/serializers/system_user.py:78
|
||||||
msgid "Password or private key required"
|
msgid "Password or private key required"
|
||||||
msgstr "密码或密钥密码需要一个"
|
msgstr "密码或密钥密码需要一个"
|
||||||
|
|
||||||
#: assets/tasks.py:34
|
#: assets/tasks.py:33
|
||||||
msgid "Asset has been disabled, skipped: {}"
|
msgid "Asset has been disabled, skipped: {}"
|
||||||
msgstr "资产或许不支持ansible, 跳过: {}"
|
msgstr "资产或许不支持ansible, 跳过: {}"
|
||||||
|
|
||||||
#: assets/tasks.py:38
|
#: assets/tasks.py:37
|
||||||
msgid "Asset may not be support ansible, skipped: {}"
|
msgid "Asset may not be support ansible, skipped: {}"
|
||||||
msgstr "资产或许不支持ansible, 跳过: {}"
|
msgstr "资产或许不支持ansible, 跳过: {}"
|
||||||
|
|
||||||
#: assets/tasks.py:51
|
#: assets/tasks.py:50
|
||||||
msgid "No assets matched, stop task"
|
msgid "No assets matched, stop task"
|
||||||
msgstr "没有匹配到资产,结束任务"
|
msgstr "没有匹配到资产,结束任务"
|
||||||
|
|
||||||
#: assets/tasks.py:61
|
#: assets/tasks.py:60
|
||||||
msgid "No assets matched related system user protocol, stop task"
|
msgid "No assets matched related system user protocol, stop task"
|
||||||
msgstr "没有匹配到与系统用户协议相关的资产,结束任务"
|
msgstr "没有匹配到与系统用户协议相关的资产,结束任务"
|
||||||
|
|
||||||
#: assets/tasks.py:87
|
#: assets/tasks.py:86
|
||||||
msgid "Get asset info failed: {}"
|
msgid "Get asset info failed: {}"
|
||||||
msgstr "获取资产信息失败:{}"
|
msgstr "获取资产信息失败:{}"
|
||||||
|
|
||||||
#: assets/tasks.py:137
|
#: assets/tasks.py:136
|
||||||
msgid "Update some assets hardware info"
|
msgid "Update some assets hardware info"
|
||||||
msgstr "更新资产硬件信息"
|
msgstr "更新资产硬件信息"
|
||||||
|
|
||||||
#: assets/tasks.py:154
|
#: assets/tasks.py:153
|
||||||
msgid "Update asset hardware info: {}"
|
msgid "Update asset hardware info: {}"
|
||||||
msgstr "更新资产硬件信息: {}"
|
msgstr "更新资产硬件信息: {}"
|
||||||
|
|
||||||
#: assets/tasks.py:179
|
#: assets/tasks.py:178
|
||||||
msgid "Test assets connectivity"
|
msgid "Test assets connectivity"
|
||||||
msgstr "测试资产可连接性"
|
msgstr "测试资产可连接性"
|
||||||
|
|
||||||
#: assets/tasks.py:233
|
#: assets/tasks.py:232
|
||||||
msgid "Test assets connectivity: {}"
|
msgid "Test assets connectivity: {}"
|
||||||
msgstr "测试资产可连接性: {}"
|
msgstr "测试资产可连接性: {}"
|
||||||
|
|
||||||
#: assets/tasks.py:275
|
#: assets/tasks.py:274
|
||||||
msgid "Test admin user connectivity period: {}"
|
msgid "Test admin user connectivity period: {}"
|
||||||
msgstr "定期测试管理账号可连接性: {}"
|
msgstr "定期测试管理账号可连接性: {}"
|
||||||
|
|
||||||
#: assets/tasks.py:282
|
#: assets/tasks.py:281
|
||||||
msgid "Test admin user connectivity: {}"
|
msgid "Test admin user connectivity: {}"
|
||||||
msgstr "测试管理行号可连接性: {}"
|
msgstr "测试管理行号可连接性: {}"
|
||||||
|
|
||||||
#: assets/tasks.py:350
|
#: assets/tasks.py:349
|
||||||
msgid "Test system user connectivity: {}"
|
msgid "Test system user connectivity: {}"
|
||||||
msgstr "测试系统用户可连接性: {}"
|
msgstr "测试系统用户可连接性: {}"
|
||||||
|
|
||||||
#: assets/tasks.py:357
|
#: assets/tasks.py:356
|
||||||
msgid "Test system user connectivity: {} => {}"
|
msgid "Test system user connectivity: {} => {}"
|
||||||
msgstr "测试系统用户可连接性: {} => {}"
|
msgstr "测试系统用户可连接性: {} => {}"
|
||||||
|
|
||||||
#: assets/tasks.py:370
|
#: assets/tasks.py:369
|
||||||
msgid "Test system user connectivity period: {}"
|
msgid "Test system user connectivity period: {}"
|
||||||
msgstr "定期测试系统用户可连接性: {}"
|
msgstr "定期测试系统用户可连接性: {}"
|
||||||
|
|
||||||
#: assets/tasks.py:479 assets/tasks.py:565
|
#: assets/tasks.py:478 assets/tasks.py:564
|
||||||
#: xpack/plugins/change_auth_plan/models.py:522
|
#: xpack/plugins/change_auth_plan/models.py:522
|
||||||
msgid "The asset {} system platform {} does not support run Ansible tasks"
|
msgid "The asset {} system platform {} does not support run Ansible tasks"
|
||||||
msgstr "资产 {} 系统平台 {} 不支持运行 Ansible 任务"
|
msgstr "资产 {} 系统平台 {} 不支持运行 Ansible 任务"
|
||||||
|
|
||||||
#: assets/tasks.py:491
|
#: assets/tasks.py:490
|
||||||
msgid ""
|
msgid ""
|
||||||
"Push system user task skip, auto push not enable or protocol is not ssh or "
|
"Push system user task skip, auto push not enable or protocol is not ssh or "
|
||||||
"rdp: {}"
|
"rdp: {}"
|
||||||
msgstr "推送系统用户任务跳过,自动推送没有打开,或协议不是ssh或rdp: {}"
|
msgstr "推送系统用户任务跳过,自动推送没有打开,或协议不是ssh或rdp: {}"
|
||||||
|
|
||||||
#: assets/tasks.py:498
|
#: assets/tasks.py:497
|
||||||
msgid "For security, do not push user {}"
|
msgid "For security, do not push user {}"
|
||||||
msgstr "为了安全,禁止推送用户 {}"
|
msgstr "为了安全,禁止推送用户 {}"
|
||||||
|
|
||||||
#: assets/tasks.py:526 assets/tasks.py:540
|
#: assets/tasks.py:525 assets/tasks.py:539
|
||||||
msgid "Push system users to assets: {}"
|
msgid "Push system users to assets: {}"
|
||||||
msgstr "推送系统用户到入资产: {}"
|
msgstr "推送系统用户到入资产: {}"
|
||||||
|
|
||||||
#: assets/tasks.py:532
|
#: assets/tasks.py:531
|
||||||
msgid "Push system users to asset: {} => {}"
|
msgid "Push system users to asset: {} => {}"
|
||||||
msgstr "推送系统用户到入资产: {} => {}"
|
msgstr "推送系统用户到入资产: {} => {}"
|
||||||
|
|
||||||
#: assets/tasks.py:612
|
#: assets/tasks.py:611
|
||||||
msgid "Test asset user connectivity: {}"
|
msgid "Test asset user connectivity: {}"
|
||||||
msgstr "测试资产用户可连接性: {}"
|
msgstr "测试资产用户可连接性: {}"
|
||||||
|
|
||||||
|
@ -1418,6 +1416,7 @@ msgstr "获取认证信息错误"
|
||||||
|
|
||||||
#: assets/templates/assets/_asset_user_auth_view_modal.html:97
|
#: assets/templates/assets/_asset_user_auth_view_modal.html:97
|
||||||
#: assets/templates/assets/_user_asset_detail_modal.html:23
|
#: assets/templates/assets/_user_asset_detail_modal.html:23
|
||||||
|
#: authentication/templates/authentication/_access_key_modal.html:143
|
||||||
#: authentication/templates/authentication/_mfa_confirm_modal.html:53
|
#: authentication/templates/authentication/_mfa_confirm_modal.html:53
|
||||||
#: settings/templates/settings/_ldap_list_users_modal.html:99
|
#: settings/templates/settings/_ldap_list_users_modal.html:99
|
||||||
#: templates/_modal.html:22
|
#: templates/_modal.html:22
|
||||||
|
@ -1701,7 +1700,8 @@ msgstr "硬盘"
|
||||||
msgid "Date joined"
|
msgid "Date joined"
|
||||||
msgstr "创建日期"
|
msgstr "创建日期"
|
||||||
|
|
||||||
#: assets/templates/assets/asset_detail.html:150
|
#: assets/templates/assets/asset_detail.html:150 authentication/models.py:15
|
||||||
|
#: authentication/templates/authentication/_access_key_modal.html:28
|
||||||
#: perms/models/asset_permission.py:115 perms/models/base.py:38
|
#: perms/models/asset_permission.py:115 perms/models/base.py:38
|
||||||
#: perms/templates/perms/asset_permission_create_update.html:55
|
#: perms/templates/perms/asset_permission_create_update.html:55
|
||||||
#: perms/templates/perms/asset_permission_detail.html:120
|
#: perms/templates/perms/asset_permission_detail.html:120
|
||||||
|
@ -2133,7 +2133,7 @@ msgstr "操作"
|
||||||
msgid "Filename"
|
msgid "Filename"
|
||||||
msgstr "文件名"
|
msgstr "文件名"
|
||||||
|
|
||||||
#: audits/models.py:23 audits/models.py:90
|
#: audits/models.py:23 audits/models.py:76
|
||||||
#: audits/templates/audits/ftp_log_list.html:76
|
#: audits/templates/audits/ftp_log_list.html:76
|
||||||
#: ops/templates/ops/command_execution_list.html:65
|
#: ops/templates/ops/command_execution_list.html:65
|
||||||
#: ops/templates/ops/task_list.html:31
|
#: ops/templates/ops/task_list.html:31
|
||||||
|
@ -2143,7 +2143,9 @@ msgstr "文件名"
|
||||||
msgid "Success"
|
msgid "Success"
|
||||||
msgstr "成功"
|
msgstr "成功"
|
||||||
|
|
||||||
#: audits/models.py:32 xpack/plugins/vault/templates/vault/vault.html:46
|
#: audits/models.py:32
|
||||||
|
#: authentication/templates/authentication/_access_key_modal.html:38
|
||||||
|
#: xpack/plugins/vault/templates/vault/vault.html:46
|
||||||
msgid "Create"
|
msgid "Create"
|
||||||
msgstr "创建"
|
msgstr "创建"
|
||||||
|
|
||||||
|
@ -2169,55 +2171,39 @@ msgstr "禁用"
|
||||||
msgid "Enabled"
|
msgid "Enabled"
|
||||||
msgstr "启用"
|
msgstr "启用"
|
||||||
|
|
||||||
#: audits/models.py:72 audits/models.py:82
|
#: audits/models.py:72
|
||||||
msgid "-"
|
msgid "-"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: audits/models.py:83
|
#: audits/models.py:77 xpack/plugins/cloud/models.py:267
|
||||||
msgid "Username/password check failed"
|
|
||||||
msgstr "用户名/密码 校验失败"
|
|
||||||
|
|
||||||
#: audits/models.py:84
|
|
||||||
msgid "MFA authentication failed"
|
|
||||||
msgstr "MFA 认证失败"
|
|
||||||
|
|
||||||
#: audits/models.py:85
|
|
||||||
msgid "Username does not exist"
|
|
||||||
msgstr "用户名不存在"
|
|
||||||
|
|
||||||
#: audits/models.py:86
|
|
||||||
msgid "Password expired"
|
|
||||||
msgstr "密码过期"
|
|
||||||
|
|
||||||
#: audits/models.py:91 xpack/plugins/cloud/models.py:267
|
|
||||||
#: xpack/plugins/cloud/models.py:290
|
#: xpack/plugins/cloud/models.py:290
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
msgstr "失败"
|
msgstr "失败"
|
||||||
|
|
||||||
#: audits/models.py:95
|
#: audits/models.py:81
|
||||||
msgid "Login type"
|
msgid "Login type"
|
||||||
msgstr "登录方式"
|
msgstr "登录方式"
|
||||||
|
|
||||||
#: audits/models.py:96
|
#: audits/models.py:82
|
||||||
msgid "Login ip"
|
msgid "Login ip"
|
||||||
msgstr "登录IP"
|
msgstr "登录IP"
|
||||||
|
|
||||||
#: audits/models.py:97
|
#: audits/models.py:83
|
||||||
msgid "Login city"
|
msgid "Login city"
|
||||||
msgstr "登录城市"
|
msgstr "登录城市"
|
||||||
|
|
||||||
#: audits/models.py:98
|
#: audits/models.py:84
|
||||||
msgid "User agent"
|
msgid "User agent"
|
||||||
msgstr "Agent"
|
msgstr "Agent"
|
||||||
|
|
||||||
#: audits/models.py:99 audits/templates/audits/login_log_list.html:56
|
#: audits/models.py:85 audits/templates/audits/login_log_list.html:56
|
||||||
#: authentication/templates/authentication/_mfa_confirm_modal.html:14
|
#: authentication/templates/authentication/_mfa_confirm_modal.html:14
|
||||||
#: users/forms.py:175 users/models/user.py:353
|
#: users/forms.py:175 users/models/user.py:353
|
||||||
#: users/templates/users/first_login.html:45
|
#: users/templates/users/first_login.html:45
|
||||||
msgid "MFA"
|
msgid "MFA"
|
||||||
msgstr "MFA"
|
msgstr "MFA"
|
||||||
|
|
||||||
#: audits/models.py:100 audits/templates/audits/login_log_list.html:57
|
#: audits/models.py:86 audits/templates/audits/login_log_list.html:57
|
||||||
#: xpack/plugins/change_auth_plan/models.py:417
|
#: xpack/plugins/change_auth_plan/models.py:417
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:15
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:15
|
||||||
#: xpack/plugins/cloud/models.py:281
|
#: xpack/plugins/cloud/models.py:281
|
||||||
|
@ -2225,14 +2211,14 @@ msgstr "MFA"
|
||||||
msgid "Reason"
|
msgid "Reason"
|
||||||
msgstr "原因"
|
msgstr "原因"
|
||||||
|
|
||||||
#: audits/models.py:101 audits/templates/audits/login_log_list.html:58
|
#: audits/models.py:87 audits/templates/audits/login_log_list.html:58
|
||||||
#: xpack/plugins/cloud/models.py:278 xpack/plugins/cloud/models.py:313
|
#: xpack/plugins/cloud/models.py:278 xpack/plugins/cloud/models.py:313
|
||||||
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:70
|
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:70
|
||||||
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:65
|
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:65
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "状态"
|
msgstr "状态"
|
||||||
|
|
||||||
#: audits/models.py:102
|
#: audits/models.py:88
|
||||||
msgid "Date login"
|
msgid "Date login"
|
||||||
msgstr "登录日期"
|
msgstr "登录日期"
|
||||||
|
|
||||||
|
@ -2264,13 +2250,14 @@ msgstr "选择用户"
|
||||||
#: ops/templates/ops/command_execution_list.html:43
|
#: ops/templates/ops/command_execution_list.html:43
|
||||||
#: ops/templates/ops/command_execution_list.html:48
|
#: ops/templates/ops/command_execution_list.html:48
|
||||||
#: ops/templates/ops/task_list.html:13 ops/templates/ops/task_list.html:18
|
#: ops/templates/ops/task_list.html:13 ops/templates/ops/task_list.html:18
|
||||||
#: templates/_base_list.html:41 templates/_header_bar.html:8
|
#: templates/_base_list.html:41
|
||||||
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:52
|
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:52
|
||||||
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:48
|
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:48
|
||||||
msgid "Search"
|
msgid "Search"
|
||||||
msgstr "搜索"
|
msgstr "搜索"
|
||||||
|
|
||||||
#: audits/templates/audits/login_log_list.html:50
|
#: audits/templates/audits/login_log_list.html:50
|
||||||
|
#: authentication/templates/authentication/_access_key_modal.html:26
|
||||||
#: ops/templates/ops/adhoc_detail.html:49
|
#: ops/templates/ops/adhoc_detail.html:49
|
||||||
#: ops/templates/ops/adhoc_history_detail.html:49
|
#: ops/templates/ops/adhoc_history_detail.html:49
|
||||||
#: ops/templates/ops/task_detail.html:56
|
#: ops/templates/ops/task_detail.html:56
|
||||||
|
@ -2289,6 +2276,7 @@ msgid "City"
|
||||||
msgstr "城市"
|
msgstr "城市"
|
||||||
|
|
||||||
#: audits/templates/audits/login_log_list.html:59
|
#: audits/templates/audits/login_log_list.html:59
|
||||||
|
#: authentication/templates/authentication/_access_key_modal.html:29
|
||||||
#: ops/templates/ops/task_list.html:32
|
#: ops/templates/ops/task_list.html:32
|
||||||
msgid "Date"
|
msgid "Date"
|
||||||
msgstr "日期"
|
msgstr "日期"
|
||||||
|
@ -2319,79 +2307,99 @@ msgstr "登录日志"
|
||||||
msgid "Command execution log"
|
msgid "Command execution log"
|
||||||
msgstr "命令执行"
|
msgstr "命令执行"
|
||||||
|
|
||||||
#: authentication/api/auth.py:49
|
#: authentication/api/auth.py:51 authentication/api/token.py:45
|
||||||
#: authentication/templates/authentication/login.html:52
|
#: authentication/templates/authentication/login.html:52
|
||||||
#: authentication/templates/authentication/new_login.html:77
|
#: authentication/templates/authentication/new_login.html:77
|
||||||
msgid "Log in frequently and try again later"
|
msgid "Log in frequently and try again later"
|
||||||
msgstr "登录频繁, 稍后重试"
|
msgstr "登录频繁, 稍后重试"
|
||||||
|
|
||||||
#: authentication/api/auth.py:67
|
#: authentication/api/auth.py:76
|
||||||
msgid "The user {} password has expired, please update."
|
|
||||||
msgstr "用户 {} 密码已经过期,请更新。"
|
|
||||||
|
|
||||||
#: authentication/api/auth.py:86
|
|
||||||
msgid "Please carry seed value and conduct MFA secondary certification"
|
msgid "Please carry seed value and conduct MFA secondary certification"
|
||||||
msgstr "请携带seed值, 进行MFA二次认证"
|
msgstr "请携带seed值, 进行MFA二次认证"
|
||||||
|
|
||||||
#: authentication/api/auth.py:166
|
#: authentication/api/auth.py:156
|
||||||
msgid "Please verify the user name and password first"
|
msgid "Please verify the user name and password first"
|
||||||
msgstr "请先进行用户名和密码验证"
|
msgstr "请先进行用户名和密码验证"
|
||||||
|
|
||||||
#: authentication/api/auth.py:171
|
#: authentication/api/auth.py:161
|
||||||
msgid "MFA certification failed"
|
msgid "MFA certification failed"
|
||||||
msgstr "MFA认证失败"
|
msgstr "MFA认证失败"
|
||||||
|
|
||||||
#: authentication/backends/api.py:52
|
#: authentication/api/token.py:80
|
||||||
|
msgid "MFA required"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentication/backends/api.py:53
|
||||||
msgid "Invalid signature header. No credentials provided."
|
msgid "Invalid signature header. No credentials provided."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentication/backends/api.py:55
|
#: authentication/backends/api.py:56
|
||||||
msgid "Invalid signature header. Signature string should not contain spaces."
|
msgid "Invalid signature header. Signature string should not contain spaces."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentication/backends/api.py:62
|
#: authentication/backends/api.py:63
|
||||||
msgid "Invalid signature header. Format like AccessKeyId:Signature"
|
msgid "Invalid signature header. Format like AccessKeyId:Signature"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentication/backends/api.py:66
|
#: authentication/backends/api.py:67
|
||||||
msgid ""
|
msgid ""
|
||||||
"Invalid signature header. Signature string should not contain invalid "
|
"Invalid signature header. Signature string should not contain invalid "
|
||||||
"characters."
|
"characters."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentication/backends/api.py:86 authentication/backends/api.py:102
|
#: authentication/backends/api.py:87 authentication/backends/api.py:103
|
||||||
msgid "Invalid signature."
|
msgid "Invalid signature."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentication/backends/api.py:93
|
#: authentication/backends/api.py:94
|
||||||
msgid "HTTP header: Date not provide or not %a, %d %b %Y %H:%M:%S GMT"
|
msgid "HTTP header: Date not provide or not %a, %d %b %Y %H:%M:%S GMT"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentication/backends/api.py:98
|
#: authentication/backends/api.py:99
|
||||||
msgid "Expired, more than 15 minutes"
|
msgid "Expired, more than 15 minutes"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentication/backends/api.py:105
|
#: authentication/backends/api.py:106
|
||||||
msgid "User disabled."
|
msgid "User disabled."
|
||||||
msgstr "用户已禁用"
|
msgstr "用户已禁用"
|
||||||
|
|
||||||
#: authentication/backends/api.py:120
|
#: authentication/backends/api.py:121
|
||||||
msgid "Invalid token header. No credentials provided."
|
msgid "Invalid token header. No credentials provided."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentication/backends/api.py:123
|
#: authentication/backends/api.py:124
|
||||||
msgid "Invalid token header. Sign string should not contain spaces."
|
msgid "Invalid token header. Sign string should not contain spaces."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentication/backends/api.py:130
|
#: authentication/backends/api.py:131
|
||||||
msgid ""
|
msgid ""
|
||||||
"Invalid token header. Sign string should not contain invalid characters."
|
"Invalid token header. Sign string should not contain invalid characters."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentication/backends/api.py:140
|
#: authentication/backends/api.py:141
|
||||||
msgid "Invalid token or cache refreshed."
|
msgid "Invalid token or cache refreshed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentication/const.py:6
|
||||||
|
msgid "Username/password check failed"
|
||||||
|
msgstr "用户名/密码 校验失败"
|
||||||
|
|
||||||
|
#: authentication/const.py:7
|
||||||
|
msgid "MFA authentication failed"
|
||||||
|
msgstr "MFA 认证失败"
|
||||||
|
|
||||||
|
#: authentication/const.py:8
|
||||||
|
msgid "Username does not exist"
|
||||||
|
msgstr "用户名不存在"
|
||||||
|
|
||||||
|
#: authentication/const.py:9
|
||||||
|
msgid "Password expired"
|
||||||
|
msgstr "密码过期"
|
||||||
|
|
||||||
|
#: authentication/const.py:10
|
||||||
|
msgid "Disabled or expired"
|
||||||
|
msgstr "禁用或失效"
|
||||||
|
|
||||||
#: authentication/forms.py:21
|
#: authentication/forms.py:21
|
||||||
msgid ""
|
msgid ""
|
||||||
"The username or password you entered is incorrect, please enter it again."
|
"The username or password you entered is incorrect, please enter it again."
|
||||||
|
@ -2418,10 +2426,43 @@ msgstr "账号已被锁定(请联系管理员解锁 或 {}分钟后重试)"
|
||||||
msgid "MFA code"
|
msgid "MFA code"
|
||||||
msgstr "MFA 验证码"
|
msgstr "MFA 验证码"
|
||||||
|
|
||||||
#: authentication/models.py:33
|
#: authentication/models.py:35
|
||||||
msgid "Private Token"
|
msgid "Private Token"
|
||||||
msgstr "ssh密钥"
|
msgstr "ssh密钥"
|
||||||
|
|
||||||
|
#: authentication/templates/authentication/_access_key_modal.html:6
|
||||||
|
msgid "API key list"
|
||||||
|
msgstr "API Key列表"
|
||||||
|
|
||||||
|
#: authentication/templates/authentication/_access_key_modal.html:18
|
||||||
|
msgid "Using api key sign api header, every requests header difference"
|
||||||
|
msgstr "使用api key签名请求头,每个请求的头部是不一样的"
|
||||||
|
|
||||||
|
#: authentication/templates/authentication/_access_key_modal.html:18
|
||||||
|
msgid "docs"
|
||||||
|
msgstr "文档"
|
||||||
|
|
||||||
|
#: authentication/templates/authentication/_access_key_modal.html:27
|
||||||
|
msgid "Secret"
|
||||||
|
msgstr "密文"
|
||||||
|
|
||||||
|
#: authentication/templates/authentication/_access_key_modal.html:48
|
||||||
|
msgid "Show"
|
||||||
|
msgstr "显示"
|
||||||
|
|
||||||
|
#: authentication/templates/authentication/_access_key_modal.html:66
|
||||||
|
#: users/models/user.py:288 users/templates/users/user_profile.html:94
|
||||||
|
#: users/templates/users/user_profile.html:163
|
||||||
|
#: users/templates/users/user_profile.html:166
|
||||||
|
msgid "Disable"
|
||||||
|
msgstr "禁用"
|
||||||
|
|
||||||
|
#: authentication/templates/authentication/_access_key_modal.html:67
|
||||||
|
#: users/models/user.py:289 users/templates/users/user_profile.html:92
|
||||||
|
#: users/templates/users/user_profile.html:170
|
||||||
|
msgid "Enable"
|
||||||
|
msgstr "启用"
|
||||||
|
|
||||||
#: authentication/templates/authentication/_mfa_confirm_modal.html:5
|
#: authentication/templates/authentication/_mfa_confirm_modal.html:5
|
||||||
msgid "MFA confirm"
|
msgid "MFA confirm"
|
||||||
msgstr "MFA确认"
|
msgstr "MFA确认"
|
||||||
|
@ -2480,7 +2521,7 @@ msgstr "改变世界,从一点点开始。"
|
||||||
#: authentication/templates/authentication/login.html:46
|
#: authentication/templates/authentication/login.html:46
|
||||||
#: authentication/templates/authentication/login.html:73
|
#: authentication/templates/authentication/login.html:73
|
||||||
#: authentication/templates/authentication/new_login.html:101
|
#: authentication/templates/authentication/new_login.html:101
|
||||||
#: templates/_header_bar.html:101
|
#: templates/_header_bar.html:83
|
||||||
msgid "Login"
|
msgid "Login"
|
||||||
msgstr "登录"
|
msgstr "登录"
|
||||||
|
|
||||||
|
@ -2550,20 +2591,20 @@ msgstr "如果不能提供MFA验证码,请联系管理员!"
|
||||||
msgid "Welcome back, please enter username and password to login"
|
msgid "Welcome back, please enter username and password to login"
|
||||||
msgstr "欢迎回来,请输入用户名和密码登录"
|
msgstr "欢迎回来,请输入用户名和密码登录"
|
||||||
|
|
||||||
#: authentication/views/login.py:80
|
#: authentication/views/login.py:81
|
||||||
msgid "Please enable cookies and try again."
|
msgid "Please enable cookies and try again."
|
||||||
msgstr "设置你的浏览器支持cookie"
|
msgstr "设置你的浏览器支持cookie"
|
||||||
|
|
||||||
#: authentication/views/login.py:173 users/views/user.py:386
|
#: authentication/views/login.py:174 users/views/user.py:386
|
||||||
#: users/views/user.py:411
|
#: users/views/user.py:411
|
||||||
msgid "MFA code invalid, or ntp sync server time"
|
msgid "MFA code invalid, or ntp sync server time"
|
||||||
msgstr "MFA验证码不正确,或者服务器端时间不对"
|
msgstr "MFA验证码不正确,或者服务器端时间不对"
|
||||||
|
|
||||||
#: authentication/views/login.py:204
|
#: authentication/views/login.py:205
|
||||||
msgid "Logout success"
|
msgid "Logout success"
|
||||||
msgstr "退出登录成功"
|
msgstr "退出登录成功"
|
||||||
|
|
||||||
#: authentication/views/login.py:205
|
#: authentication/views/login.py:206
|
||||||
msgid "Logout success, return login page"
|
msgid "Logout success, return login page"
|
||||||
msgstr "退出登录成功,返回到登录页面"
|
msgstr "退出登录成功,返回到登录页面"
|
||||||
|
|
||||||
|
@ -2642,11 +2683,11 @@ msgstr "不能包含特殊字符"
|
||||||
msgid "This field must be unique."
|
msgid "This field must be unique."
|
||||||
msgstr "字段必须唯一"
|
msgstr "字段必须唯一"
|
||||||
|
|
||||||
#: jumpserver/views.py:189 templates/_nav.html:4 templates/_nav_audits.html:4
|
#: jumpserver/views.py:188 templates/_nav.html:4 templates/_nav_audits.html:4
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
msgstr "仪表盘"
|
msgstr "仪表盘"
|
||||||
|
|
||||||
#: jumpserver/views.py:198
|
#: jumpserver/views.py:197
|
||||||
msgid ""
|
msgid ""
|
||||||
"<div>Luna is a separately deployed program, you need to deploy Luna, coco, "
|
"<div>Luna is a separately deployed program, you need to deploy Luna, coco, "
|
||||||
"configure nginx for url distribution,</div> </div>If you see this page, "
|
"configure nginx for url distribution,</div> </div>If you see this page, "
|
||||||
|
@ -3298,21 +3339,21 @@ msgstr "连接LDAP成功"
|
||||||
msgid "Match {} s users"
|
msgid "Match {} s users"
|
||||||
msgstr "匹配 {} 个用户"
|
msgstr "匹配 {} 个用户"
|
||||||
|
|
||||||
#: settings/api.py:160
|
#: settings/api.py:161
|
||||||
msgid "succeed: {} failed: {} total: {}"
|
msgid "succeed: {} failed: {} total: {}"
|
||||||
msgstr "成功:{} 失败:{} 总数:{}"
|
msgstr "成功:{} 失败:{} 总数:{}"
|
||||||
|
|
||||||
#: settings/api.py:182 settings/api.py:218
|
#: settings/api.py:183 settings/api.py:219
|
||||||
msgid ""
|
msgid ""
|
||||||
"Error: Account invalid (Please make sure the information such as Access key "
|
"Error: Account invalid (Please make sure the information such as Access key "
|
||||||
"or Secret key is correct)"
|
"or Secret key is correct)"
|
||||||
msgstr "错误:账户无效 (请确保 Access key 或 Secret key 等信息正确)"
|
msgstr "错误:账户无效 (请确保 Access key 或 Secret key 等信息正确)"
|
||||||
|
|
||||||
#: settings/api.py:188 settings/api.py:224
|
#: settings/api.py:189 settings/api.py:225
|
||||||
msgid "Create succeed"
|
msgid "Create succeed"
|
||||||
msgstr "创建成功"
|
msgstr "创建成功"
|
||||||
|
|
||||||
#: settings/api.py:206 settings/api.py:244
|
#: settings/api.py:207 settings/api.py:245
|
||||||
#: settings/templates/settings/terminal_setting.html:154
|
#: settings/templates/settings/terminal_setting.html:154
|
||||||
msgid "Delete succeed"
|
msgid "Delete succeed"
|
||||||
msgstr "删除成功"
|
msgstr "删除成功"
|
||||||
|
@ -3846,19 +3887,19 @@ msgstr "创建录像存储"
|
||||||
msgid "Create command storage"
|
msgid "Create command storage"
|
||||||
msgstr "创建命令存储"
|
msgstr "创建命令存储"
|
||||||
|
|
||||||
#: templates/_header_bar.html:31
|
#: templates/_header_bar.html:12
|
||||||
msgid "Help"
|
msgid "Help"
|
||||||
msgstr "帮助"
|
msgstr "帮助"
|
||||||
|
|
||||||
#: templates/_header_bar.html:38 users/templates/users/_base_otp.html:29
|
#: templates/_header_bar.html:19 users/templates/users/_base_otp.html:29
|
||||||
msgid "Docs"
|
msgid "Docs"
|
||||||
msgstr "文档"
|
msgstr "文档"
|
||||||
|
|
||||||
#: templates/_header_bar.html:44
|
#: templates/_header_bar.html:25
|
||||||
msgid "Commercial support"
|
msgid "Commercial support"
|
||||||
msgstr "商业支持"
|
msgstr "商业支持"
|
||||||
|
|
||||||
#: templates/_header_bar.html:89 templates/_nav_user.html:32 users/forms.py:154
|
#: templates/_header_bar.html:70 templates/_nav_user.html:32 users/forms.py:154
|
||||||
#: users/templates/users/_user.html:43
|
#: users/templates/users/_user.html:43
|
||||||
#: users/templates/users/first_login.html:39
|
#: users/templates/users/first_login.html:39
|
||||||
#: users/templates/users/user_password_update.html:40
|
#: users/templates/users/user_password_update.html:40
|
||||||
|
@ -3869,15 +3910,19 @@ msgstr "商业支持"
|
||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "个人信息"
|
msgstr "个人信息"
|
||||||
|
|
||||||
#: templates/_header_bar.html:92
|
#: templates/_header_bar.html:73
|
||||||
msgid "Admin page"
|
msgid "Admin page"
|
||||||
msgstr "管理页面"
|
msgstr "管理页面"
|
||||||
|
|
||||||
#: templates/_header_bar.html:94
|
#: templates/_header_bar.html:75
|
||||||
msgid "User page"
|
msgid "User page"
|
||||||
msgstr "用户页面"
|
msgstr "用户页面"
|
||||||
|
|
||||||
#: templates/_header_bar.html:97
|
#: templates/_header_bar.html:78
|
||||||
|
msgid "API Key"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/_header_bar.html:79
|
||||||
msgid "Logout"
|
msgid "Logout"
|
||||||
msgstr "注销登录"
|
msgstr "注销登录"
|
||||||
|
|
||||||
|
@ -4413,7 +4458,7 @@ msgid ""
|
||||||
"You should use your ssh client tools connect terminal: {} <br /> <br />{}"
|
"You should use your ssh client tools connect terminal: {} <br /> <br />{}"
|
||||||
msgstr "你可以使用ssh客户端工具连接终端"
|
msgstr "你可以使用ssh客户端工具连接终端"
|
||||||
|
|
||||||
#: users/api/user.py:97
|
#: users/api/user.py:96
|
||||||
msgid "You do not have permission."
|
msgid "You do not have permission."
|
||||||
msgstr "你没有权限"
|
msgstr "你没有权限"
|
||||||
|
|
||||||
|
@ -4450,7 +4495,7 @@ msgstr "添加到用户组"
|
||||||
msgid "Public key should not be the same as your old one."
|
msgid "Public key should not be the same as your old one."
|
||||||
msgstr "不能和原来的密钥相同"
|
msgstr "不能和原来的密钥相同"
|
||||||
|
|
||||||
#: users/forms.py:91 users/forms.py:252 users/serializers/v1.py:95
|
#: users/forms.py:91 users/forms.py:252 users/serializers/v1.py:94
|
||||||
msgid "Not a valid ssh public key"
|
msgid "Not a valid ssh public key"
|
||||||
msgstr "ssh密钥不合法"
|
msgstr "ssh密钥不合法"
|
||||||
|
|
||||||
|
@ -4540,29 +4585,28 @@ msgstr "选择用户"
|
||||||
msgid "User auth from {}, go there change password"
|
msgid "User auth from {}, go there change password"
|
||||||
msgstr "用户认证源来自 {}, 请去相应系统修改密码"
|
msgstr "用户认证源来自 {}, 请去相应系统修改密码"
|
||||||
|
|
||||||
#: users/models/user.py:127 users/models/user.py:466
|
#: users/models/user.py:126 users/models/user.py:466
|
||||||
msgid "Administrator"
|
msgid "Administrator"
|
||||||
msgstr "管理员"
|
msgstr "管理员"
|
||||||
|
|
||||||
#: users/models/user.py:129
|
#: users/models/user.py:128
|
||||||
msgid "Application"
|
msgid "Application"
|
||||||
msgstr "应用程序"
|
msgstr "应用程序"
|
||||||
|
|
||||||
#: users/models/user.py:130
|
#: users/models/user.py:129
|
||||||
msgid "Auditor"
|
msgid "Auditor"
|
||||||
msgstr "审计员"
|
msgstr "审计员"
|
||||||
|
|
||||||
#: users/models/user.py:288 users/templates/users/user_profile.html:94
|
# #: users/models/user.py:288 users/templates/users/user_profile.html:94
|
||||||
#: users/templates/users/user_profile.html:163
|
# #: users/templates/users/user_profile.html:163
|
||||||
#: users/templates/users/user_profile.html:166
|
# #: users/templates/users/user_profile.html:166
|
||||||
msgid "Disable"
|
# msgid "Disable"
|
||||||
msgstr "禁用"
|
# msgstr "禁用"
|
||||||
|
#
|
||||||
#: users/models/user.py:289 users/templates/users/user_profile.html:92
|
# #: users/models/user.py:289 users/templates/users/user_profile.html:92
|
||||||
#: users/templates/users/user_profile.html:170
|
# #: users/templates/users/user_profile.html:170
|
||||||
msgid "Enable"
|
# msgid "Enable"
|
||||||
msgstr "启用"
|
# msgstr "启用"
|
||||||
|
|
||||||
#: users/models/user.py:290 users/templates/users/user_profile.html:90
|
#: users/models/user.py:290 users/templates/users/user_profile.html:90
|
||||||
msgid "Force enable"
|
msgid "Force enable"
|
||||||
msgstr "强制启用"
|
msgstr "强制启用"
|
||||||
|
@ -4589,39 +4633,39 @@ msgstr "最后更新密码日期"
|
||||||
msgid "Administrator is the super user of system"
|
msgid "Administrator is the super user of system"
|
||||||
msgstr "Administrator是初始的超级管理员"
|
msgstr "Administrator是初始的超级管理员"
|
||||||
|
|
||||||
#: users/serializers/v1.py:41
|
#: users/serializers/v1.py:39
|
||||||
msgid "Groups name"
|
msgid "Groups name"
|
||||||
msgstr "用户组名"
|
msgstr "用户组名"
|
||||||
|
|
||||||
#: users/serializers/v1.py:42
|
#: users/serializers/v1.py:40
|
||||||
msgid "Source name"
|
msgid "Source name"
|
||||||
msgstr "用户来源名"
|
msgstr "用户来源名"
|
||||||
|
|
||||||
#: users/serializers/v1.py:43
|
#: users/serializers/v1.py:41
|
||||||
msgid "Is first login"
|
msgid "Is first login"
|
||||||
msgstr "首次登录"
|
msgstr "首次登录"
|
||||||
|
|
||||||
#: users/serializers/v1.py:44
|
#: users/serializers/v1.py:42
|
||||||
msgid "Role name"
|
msgid "Role name"
|
||||||
msgstr "角色名"
|
msgstr "角色名"
|
||||||
|
|
||||||
#: users/serializers/v1.py:45
|
#: users/serializers/v1.py:43
|
||||||
msgid "Is valid"
|
msgid "Is valid"
|
||||||
msgstr "账户是否有效"
|
msgstr "账户是否有效"
|
||||||
|
|
||||||
#: users/serializers/v1.py:46
|
#: users/serializers/v1.py:44
|
||||||
msgid "Is expired"
|
msgid "Is expired"
|
||||||
msgstr " 是否过期"
|
msgstr " 是否过期"
|
||||||
|
|
||||||
#: users/serializers/v1.py:47
|
#: users/serializers/v1.py:45
|
||||||
msgid "Avatar url"
|
msgid "Avatar url"
|
||||||
msgstr "头像路径"
|
msgstr "头像路径"
|
||||||
|
|
||||||
#: users/serializers/v1.py:55
|
#: users/serializers/v1.py:54
|
||||||
msgid "Role limit to {}"
|
msgid "Role limit to {}"
|
||||||
msgstr "角色只能为 {}"
|
msgstr "角色只能为 {}"
|
||||||
|
|
||||||
#: users/serializers/v1.py:67
|
#: users/serializers/v1.py:66
|
||||||
msgid "Password does not match security rules"
|
msgid "Password does not match security rules"
|
||||||
msgstr "密码不满足安全规则"
|
msgstr "密码不满足安全规则"
|
||||||
|
|
||||||
|
@ -4753,7 +4797,7 @@ msgid "Always young, always with tears in my eyes. Stay foolish Stay hungry"
|
||||||
msgstr "永远年轻,永远热泪盈眶 stay foolish stay hungry"
|
msgstr "永远年轻,永远热泪盈眶 stay foolish stay hungry"
|
||||||
|
|
||||||
#: users/templates/users/reset_password.html:46
|
#: users/templates/users/reset_password.html:46
|
||||||
#: users/templates/users/user_detail.html:377 users/utils.py:88
|
#: users/templates/users/user_detail.html:377 users/utils.py:84
|
||||||
msgid "Reset password"
|
msgid "Reset password"
|
||||||
msgstr "重置密码"
|
msgstr "重置密码"
|
||||||
|
|
||||||
|
@ -5069,7 +5113,7 @@ msgid ""
|
||||||
"corresponding private key."
|
"corresponding private key."
|
||||||
msgstr "新的公钥已设置成功,请下载对应的私钥"
|
msgstr "新的公钥已设置成功,请下载对应的私钥"
|
||||||
|
|
||||||
#: users/utils.py:28
|
#: users/utils.py:24
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
|
@ -5114,16 +5158,16 @@ msgstr ""
|
||||||
" </p>\n"
|
" </p>\n"
|
||||||
" "
|
" "
|
||||||
|
|
||||||
#: users/utils.py:63
|
#: users/utils.py:59
|
||||||
msgid "Create account successfully"
|
msgid "Create account successfully"
|
||||||
msgstr "创建账户成功"
|
msgstr "创建账户成功"
|
||||||
|
|
||||||
#: users/utils.py:67
|
#: users/utils.py:63
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Hello %(name)s"
|
msgid "Hello %(name)s"
|
||||||
msgstr "您好 %(name)s"
|
msgstr "您好 %(name)s"
|
||||||
|
|
||||||
#: users/utils.py:90
|
#: users/utils.py:86
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
|
@ -5167,11 +5211,11 @@ msgstr ""
|
||||||
" <br>\n"
|
" <br>\n"
|
||||||
" "
|
" "
|
||||||
|
|
||||||
#: users/utils.py:121
|
#: users/utils.py:117
|
||||||
msgid "Security notice"
|
msgid "Security notice"
|
||||||
msgstr "安全通知"
|
msgstr "安全通知"
|
||||||
|
|
||||||
#: users/utils.py:123
|
#: users/utils.py:119
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
|
@ -5220,11 +5264,11 @@ msgstr ""
|
||||||
" <br>\n"
|
" <br>\n"
|
||||||
" "
|
" "
|
||||||
|
|
||||||
#: users/utils.py:159
|
#: users/utils.py:155
|
||||||
msgid "Expiration notice"
|
msgid "Expiration notice"
|
||||||
msgstr "过期通知"
|
msgstr "过期通知"
|
||||||
|
|
||||||
#: users/utils.py:161
|
#: users/utils.py:157
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
|
@ -5246,11 +5290,11 @@ msgstr ""
|
||||||
" <br>\n"
|
" <br>\n"
|
||||||
" "
|
" "
|
||||||
|
|
||||||
#: users/utils.py:180
|
#: users/utils.py:176
|
||||||
msgid "SSH Key Reset"
|
msgid "SSH Key Reset"
|
||||||
msgstr "重置ssh密钥"
|
msgstr "重置ssh密钥"
|
||||||
|
|
||||||
#: users/utils.py:182
|
#: users/utils.py:178
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
|
@ -5275,18 +5319,6 @@ msgstr ""
|
||||||
" <br>\n"
|
" <br>\n"
|
||||||
" "
|
" "
|
||||||
|
|
||||||
#: users/utils.py:215
|
|
||||||
msgid "User not exist"
|
|
||||||
msgstr "用户不存在"
|
|
||||||
|
|
||||||
#: users/utils.py:217
|
|
||||||
msgid "Disabled or expired"
|
|
||||||
msgstr "禁用或失效"
|
|
||||||
|
|
||||||
#: users/utils.py:230
|
|
||||||
msgid "Password or SSH public key invalid"
|
|
||||||
msgstr "密码或密钥不合法"
|
|
||||||
|
|
||||||
#: users/views/group.py:29
|
#: users/views/group.py:29
|
||||||
msgid "User group list"
|
msgid "User group list"
|
||||||
msgstr "用户组列表"
|
msgstr "用户组列表"
|
||||||
|
@ -5718,12 +5750,16 @@ msgid "Create account"
|
||||||
msgstr "创建账户"
|
msgstr "创建账户"
|
||||||
|
|
||||||
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:33
|
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:33
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "Instance"
|
||||||
msgid "Region & Instance"
|
msgid "Region & Instance"
|
||||||
msgstr "地域 & 实例"
|
msgstr "实例"
|
||||||
|
|
||||||
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:37
|
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:37
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "Admin user"
|
||||||
msgid "Node & AdminUser"
|
msgid "Node & AdminUser"
|
||||||
msgstr "节点 & 管理用户"
|
msgstr "管理用户"
|
||||||
|
|
||||||
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:66
|
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:66
|
||||||
msgid "Loading..."
|
msgid "Loading..."
|
||||||
|
@ -5791,6 +5827,8 @@ msgstr "执行次数"
|
||||||
msgid "Instance count"
|
msgid "Instance count"
|
||||||
msgstr "实例个数"
|
msgstr "实例个数"
|
||||||
|
|
||||||
|
# msgid "Sync success"
|
||||||
|
# msgstr "同步成功"
|
||||||
#: xpack/plugins/cloud/views.py:63
|
#: xpack/plugins/cloud/views.py:63
|
||||||
msgid "Update account"
|
msgid "Update account"
|
||||||
msgstr "更新账户"
|
msgstr "更新账户"
|
||||||
|
@ -6034,15 +6072,23 @@ msgstr "密码匣子"
|
||||||
msgid "vault create"
|
msgid "vault create"
|
||||||
msgstr "创建"
|
msgstr "创建"
|
||||||
|
|
||||||
|
#~ msgid "User not exist"
|
||||||
|
#~ msgstr "用户不存在"
|
||||||
|
|
||||||
|
# msgid "Disabled or expired"
|
||||||
|
# msgstr "禁用或失效"
|
||||||
|
#~ msgid "Password or SSH public key invalid"
|
||||||
|
#~ msgstr "密码或密钥不合法"
|
||||||
|
|
||||||
|
#~ msgid "Please select assets that need to be updated"
|
||||||
|
#~ msgstr "请选择需要更新的资产"
|
||||||
|
|
||||||
#~ msgid "Interface"
|
#~ msgid "Interface"
|
||||||
#~ msgstr "界面"
|
#~ msgstr "界面"
|
||||||
|
|
||||||
#~ msgid "Orgs"
|
#~ msgid "Orgs"
|
||||||
#~ msgstr "组织管理"
|
#~ msgstr "组织管理"
|
||||||
|
|
||||||
#~ msgid "Org"
|
|
||||||
#~ msgstr "组织"
|
|
||||||
|
|
||||||
#~ msgid "already exists"
|
#~ msgid "already exists"
|
||||||
#~ msgstr "已经存在"
|
#~ msgstr "已经存在"
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from ansible import constants as C
|
from ansible import constants as C
|
||||||
|
@ -41,7 +42,11 @@ class CallbackMixin:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
if display:
|
if display:
|
||||||
self._display = display
|
self._display = display
|
||||||
|
|
||||||
|
cols = os.environ.get("TERM_COLS", None)
|
||||||
self._display.columns = 79
|
self._display.columns = 79
|
||||||
|
if cols and cols.isdigit():
|
||||||
|
self._display.columns = int(cols) - 1
|
||||||
|
|
||||||
def display(self, msg):
|
def display(self, msg):
|
||||||
self._display.display(msg)
|
self._display.display(msg)
|
||||||
|
|
|
@ -6,6 +6,7 @@ from rest_framework import viewsets, generics
|
||||||
from rest_framework.views import Response
|
from rest_framework.views import Response
|
||||||
|
|
||||||
from common.permissions import IsOrgAdmin
|
from common.permissions import IsOrgAdmin
|
||||||
|
from common.serializers import CeleryTaskSerializer
|
||||||
from orgs.utils import current_org
|
from orgs.utils import current_org
|
||||||
from ..models import Task, AdHoc, AdHocRunHistory
|
from ..models import Task, AdHoc, AdHocRunHistory
|
||||||
from ..serializers import TaskSerializer, AdHocSerializer, \
|
from ..serializers import TaskSerializer, AdHocSerializer, \
|
||||||
|
@ -33,7 +34,7 @@ class TaskViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
class TaskRun(generics.RetrieveAPIView):
|
class TaskRun(generics.RetrieveAPIView):
|
||||||
queryset = Task.objects.all()
|
queryset = Task.objects.all()
|
||||||
# serializer_class = TaskViewSet
|
serializer_class = CeleryTaskSerializer
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
|
|
||||||
def retrieve(self, request, *args, **kwargs):
|
def retrieve(self, request, *args, **kwargs):
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
|
from rest_framework.exceptions import ValidationError
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from orgs.mixins import RootOrgViewMixin
|
from orgs.mixins.api import RootOrgViewMixin
|
||||||
from common.permissions import IsValidUser
|
from common.permissions import IsValidUser
|
||||||
|
from perms.utils import AssetPermissionUtilV2
|
||||||
from ..models import CommandExecution
|
from ..models import CommandExecution
|
||||||
from ..serializers import CommandExecutionSerializer
|
from ..serializers import CommandExecutionSerializer
|
||||||
from ..tasks import run_command_execution
|
from ..tasks import run_command_execution
|
||||||
|
@ -20,15 +23,33 @@ class CommandExecutionViewSet(RootOrgViewMixin, viewsets.ModelViewSet):
|
||||||
user_id=str(self.request.user.id)
|
user_id=str(self.request.user.id)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def check_hosts(self, serializer):
|
||||||
|
data = serializer.validated_data
|
||||||
|
assets = data["hosts"]
|
||||||
|
system_user = data["run_as"]
|
||||||
|
util = AssetPermissionUtilV2(self.request.user)
|
||||||
|
util.filter_permissions(system_users=system_user.id)
|
||||||
|
permed_assets = util.get_assets().filter(id__in=[a.id for a in assets])
|
||||||
|
unpermed_assets = set(assets) - set(permed_assets)
|
||||||
|
if unpermed_assets:
|
||||||
|
msg = _("Not has host {} permission").format(
|
||||||
|
[str(a.id) for a in unpermed_assets]
|
||||||
|
)
|
||||||
|
raise ValidationError({"hosts": msg})
|
||||||
|
|
||||||
def check_permissions(self, request):
|
def check_permissions(self, request):
|
||||||
if not settings.SECURITY_COMMAND_EXECUTION and request.user.is_common_user:
|
if not settings.SECURITY_COMMAND_EXECUTION and request.user.is_common_user:
|
||||||
return self.permission_denied(request, "Command execution disabled")
|
return self.permission_denied(request, "Command execution disabled")
|
||||||
return super().check_permissions(request)
|
return super().check_permissions(request)
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
|
self.check_hosts(serializer)
|
||||||
instance = serializer.save()
|
instance = serializer.save()
|
||||||
instance.user = self.request.user
|
instance.user = self.request.user
|
||||||
instance.save()
|
instance.save()
|
||||||
|
cols = self.request.query_params.get("cols", '80')
|
||||||
|
rows = self.request.query_params.get("rows", '24')
|
||||||
transaction.on_commit(lambda: run_command_execution.apply_async(
|
transaction.on_commit(lambda: run_command_execution.apply_async(
|
||||||
args=(instance.id,), task_id=str(instance.id)
|
args=(instance.id,), kwargs={"cols": cols, "rows": rows},
|
||||||
|
task_id=str(instance.id)
|
||||||
))
|
))
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from kombu import Exchange, Queue
|
||||||
from celery import Celery
|
from celery import Celery
|
||||||
|
|
||||||
# set the default Django settings module for the 'celery' program.
|
# set the default Django settings module for the 'celery' program.
|
||||||
|
@ -15,6 +16,14 @@ configs = {k: v for k, v in settings.__dict__.items() if k.startswith('CELERY')}
|
||||||
# Using a string here means the worker will not have to
|
# Using a string here means the worker will not have to
|
||||||
# pickle the object when using Windows.
|
# pickle the object when using Windows.
|
||||||
# app.config_from_object('django.conf:settings', namespace='CELERY')
|
# app.config_from_object('django.conf:settings', namespace='CELERY')
|
||||||
|
configs["CELERY_QUEUES"] = [
|
||||||
|
Queue("celery", Exchange("celery"), routing_key="celery"),
|
||||||
|
Queue("ansible", Exchange("ansible"), routing_key="ansible"),
|
||||||
|
]
|
||||||
|
configs["CELERY_ROUTES"] = {
|
||||||
|
"ops.tasks.run_ansible_task": {'exchange': 'ansible', 'routing_key': 'ansible'},
|
||||||
|
}
|
||||||
|
|
||||||
app.namespace = 'CELERY'
|
app.namespace = 'CELERY'
|
||||||
app.conf.update(configs)
|
app.conf.update(configs)
|
||||||
app.autodiscover_tasks(lambda: [app_config.split('.')[0] for app_config in settings.INSTALLED_APPS])
|
app.autodiscover_tasks(lambda: [app_config.split('.')[0] for app_config in settings.INSTALLED_APPS])
|
||||||
|
|
|
@ -30,8 +30,6 @@ class JMSBaseInventory(BaseInventory):
|
||||||
info.update(asset.get_auth_info())
|
info.update(asset.get_auth_info())
|
||||||
if asset.is_unixlike():
|
if asset.is_unixlike():
|
||||||
info["become"] = asset.admin_user.become_info
|
info["become"] = asset.admin_user.become_info
|
||||||
for node in asset.nodes.all():
|
|
||||||
info["groups"].append(node.value)
|
|
||||||
if asset.is_windows():
|
if asset.is_windows():
|
||||||
info["vars"].update({
|
info["vars"].update({
|
||||||
"ansible_connection": "ssh",
|
"ansible_connection": "ssh",
|
||||||
|
@ -45,7 +43,6 @@ class JMSBaseInventory(BaseInventory):
|
||||||
info["vars"].update({
|
info["vars"].update({
|
||||||
"domain": asset.domain.name,
|
"domain": asset.domain.name,
|
||||||
})
|
})
|
||||||
info["groups"].append("domain_"+asset.domain.name)
|
|
||||||
return info
|
return info
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Generated by Django 2.1.7 on 2019-07-24 12:02
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ops', '0006_auto_20190318_1023'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='adhoc',
|
||||||
|
name='_become',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=1024, verbose_name='Become'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='adhoc',
|
||||||
|
name='created_by',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=64, null=True, verbose_name='Create by'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='adhoc',
|
||||||
|
name='run_as',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=64, null=True, verbose_name='Username'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -161,9 +161,9 @@ class AdHoc(models.Model):
|
||||||
_hosts = models.TextField(blank=True, verbose_name=_('Hosts')) # ['hostname1', 'hostname2']
|
_hosts = models.TextField(blank=True, verbose_name=_('Hosts')) # ['hostname1', 'hostname2']
|
||||||
hosts = models.ManyToManyField('assets.Asset', verbose_name=_("Host"))
|
hosts = models.ManyToManyField('assets.Asset', verbose_name=_("Host"))
|
||||||
run_as_admin = models.BooleanField(default=False, verbose_name=_('Run as admin'))
|
run_as_admin = models.BooleanField(default=False, verbose_name=_('Run as admin'))
|
||||||
run_as = models.CharField(max_length=64, default='', null=True, verbose_name=_('Username'))
|
run_as = models.CharField(max_length=64, default='', blank=True, null=True, verbose_name=_('Username'))
|
||||||
_become = models.CharField(max_length=1024, default='', verbose_name=_("Become"))
|
_become = models.CharField(max_length=1024, default='', blank=True, verbose_name=_("Become"))
|
||||||
created_by = models.CharField(max_length=64, default='', null=True, verbose_name=_('Create by'))
|
created_by = models.CharField(max_length=64, default='', blank=True, null=True, verbose_name=_('Create by'))
|
||||||
date_created = models.DateTimeField(auto_now_add=True, db_index=True)
|
date_created = models.DateTimeField(auto_now_add=True, db_index=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -23,7 +23,7 @@ def rerun_task():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task(queue="ansible")
|
||||||
def run_ansible_task(tid, callback=None, **kwargs):
|
def run_ansible_task(tid, callback=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
:param tid: is the tasks serialized data
|
:param tid: is the tasks serialized data
|
||||||
|
@ -45,6 +45,10 @@ def run_command_execution(cid, **kwargs):
|
||||||
execution = get_object_or_none(CommandExecution, id=cid)
|
execution = get_object_or_none(CommandExecution, id=cid)
|
||||||
if execution:
|
if execution:
|
||||||
try:
|
try:
|
||||||
|
os.environ.update({
|
||||||
|
"TERM_ROWS": kwargs.get("rows", ""),
|
||||||
|
"TERM_COLS": kwargs.get("cols", ""),
|
||||||
|
})
|
||||||
execution.run()
|
execution.run()
|
||||||
except SoftTimeLimitExceeded:
|
except SoftTimeLimitExceeded:
|
||||||
logger.error("Run time out")
|
logger.error("Run time out")
|
||||||
|
@ -98,7 +102,7 @@ def create_or_update_registered_periodic_tasks():
|
||||||
create_or_update_celery_periodic_tasks(task)
|
create_or_update_celery_periodic_tasks(task)
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task(queue="ansible")
|
||||||
def hello(name, callback=None):
|
def hello(name, callback=None):
|
||||||
import time
|
import time
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
|
@ -109,7 +113,9 @@ def hello(name, callback=None):
|
||||||
# @after_app_shutdown_clean_periodic
|
# @after_app_shutdown_clean_periodic
|
||||||
# @register_as_period_task(interval=30)
|
# @register_as_period_task(interval=30)
|
||||||
def hello123():
|
def hello123():
|
||||||
|
p = subprocess.Popen('ls /tmp', shell=True)
|
||||||
print("{} Hello world".format(datetime.datetime.now().strftime("%H:%M:%S")))
|
print("{} Hello world".format(datetime.datetime.now().strftime("%H:%M:%S")))
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<head>
|
<head>
|
||||||
<title>{% trans 'Task log' %}</title>
|
<title>{% trans 'Task log' %}</title>
|
||||||
<script src="{% static 'js/jquery-2.1.1.js' %}"></script>
|
<script src="{% static 'js/jquery-3.1.1.min.js' %}"></script>
|
||||||
<script src="{% static 'js/plugins/xterm/xterm.js' %}"></script>
|
<script src="{% static 'js/plugins/xterm/xterm.js' %}"></script>
|
||||||
<link rel="stylesheet" href="{% static 'js/plugins/xterm/xterm.css' %}" />
|
<link rel="stylesheet" href="{% static 'js/plugins/xterm/xterm.css' %}" />
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -83,9 +83,50 @@
|
||||||
var zTree, show = 0;
|
var zTree, show = 0;
|
||||||
var systemUserId = null;
|
var systemUserId = null;
|
||||||
var url = null;
|
var url = null;
|
||||||
var treeUrl = "{% url 'api-perms:my-nodes-assets-as-tree' %}?cache_policy=1";
|
var treeUrl = "{% url 'api-perms:my-nodes-children-with-assets-as-tree' %}?cache_policy=1";
|
||||||
|
|
||||||
|
function proposeGeometry(term) {
|
||||||
|
if (!term.element.parentElement) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var parentElementStyle = window.getComputedStyle(term.element.parentElement);
|
||||||
|
var parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
|
||||||
|
var parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
|
||||||
|
var elementStyle = window.getComputedStyle(term.element);
|
||||||
|
var elementPadding = {
|
||||||
|
top: parseInt(elementStyle.getPropertyValue('padding-top')),
|
||||||
|
bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),
|
||||||
|
right: parseInt(elementStyle.getPropertyValue('padding-right')),
|
||||||
|
left: parseInt(elementStyle.getPropertyValue('padding-left'))
|
||||||
|
};
|
||||||
|
var elementPaddingVer = elementPadding.top + elementPadding.bottom;
|
||||||
|
var elementPaddingHor = elementPadding.right + elementPadding.left;
|
||||||
|
var availableHeight = parentElementHeight - elementPaddingVer;
|
||||||
|
var availableWidth = parentElementWidth - elementPaddingHor - term._core.viewport.scrollBarWidth;
|
||||||
|
var geometry = {
|
||||||
|
cols: Math.floor(availableWidth / term._core.renderer.dimensions.actualCellWidth),
|
||||||
|
rows: Math.floor(availableHeight / term._core.renderer.dimensions.actualCellHeight)
|
||||||
|
};
|
||||||
|
return geometry;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fit(term) {
|
||||||
|
var geometry = proposeGeometry(term);
|
||||||
|
if (geometry) {
|
||||||
|
if (term.rows !== geometry.rows || term.cols !== geometry.cols) {
|
||||||
|
term._core.renderer.clear();
|
||||||
|
term.resize(geometry.cols, geometry.rows);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function initTree() {
|
function initTree() {
|
||||||
|
if (systemUserId) {
|
||||||
|
url = treeUrl + '&system_user=' + systemUserId
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
url = treeUrl
|
||||||
|
}
|
||||||
var setting = {
|
var setting = {
|
||||||
check: {
|
check: {
|
||||||
enable: true
|
enable: true
|
||||||
|
@ -99,6 +140,12 @@ function initTree() {
|
||||||
enable: true
|
enable: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async: {
|
||||||
|
enable: true,
|
||||||
|
url: url,
|
||||||
|
autoParam: ["id=key", "name=n", "level=lv"],
|
||||||
|
type: 'get'
|
||||||
|
},
|
||||||
edit: {
|
edit: {
|
||||||
enable: true,
|
enable: true,
|
||||||
showRemoveBtn: false,
|
showRemoveBtn: false,
|
||||||
|
@ -112,12 +159,7 @@ function initTree() {
|
||||||
onCheck: onCheck
|
onCheck: onCheck
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (systemUserId) {
|
|
||||||
url = treeUrl + '&system_user=' + systemUserId
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
url = treeUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
$.get(url, function(data, status){
|
$.get(url, function(data, status){
|
||||||
$.fn.zTree.init($("#assetTree"), setting, data);
|
$.fn.zTree.init($("#assetTree"), setting, data);
|
||||||
|
@ -183,6 +225,7 @@ function initResultTerminal() {
|
||||||
screenKeys: false,
|
screenKeys: false,
|
||||||
fontFamily: 'monaco, Consolas, "Lucida Console", monospace',
|
fontFamily: 'monaco, Consolas, "Lucida Console", monospace',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
lineHeight: 1,
|
||||||
rightClickSelectsWord: true,
|
rightClickSelectsWord: true,
|
||||||
disableStdin: true,
|
disableStdin: true,
|
||||||
theme: {
|
theme: {
|
||||||
|
@ -190,7 +233,9 @@ function initResultTerminal() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
term.open(document.getElementById('term'));
|
term.open(document.getElementById('term'));
|
||||||
term.write("{% trans 'Select the left asset, select the running system user, execute command in batch' %}" + "\r\n")
|
var msg = "{% trans 'Select the left asset, select the running system user, execute command in batch' %}" + "\r\n";
|
||||||
|
fit(term);
|
||||||
|
term.write(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
function wrapperError(msg) {
|
function wrapperError(msg) {
|
||||||
|
@ -201,7 +246,8 @@ function execute() {
|
||||||
if (!term) {
|
if (!term) {
|
||||||
initResultTerminal()
|
initResultTerminal()
|
||||||
}
|
}
|
||||||
var url = '{% url "api-ops:command-execution-list" %}';
|
var size = 'rows=' + term.rows + '&cols=' + term.cols;
|
||||||
|
var url = '{% url "api-ops:command-execution-list" %}?' + size;
|
||||||
var run_as = systemUserId;
|
var run_as = systemUserId;
|
||||||
var command = editor.getValue();
|
var command = editor.getValue();
|
||||||
var hosts = getSelectedAssetsNode().map(function (node) {
|
var hosts = getSelectedAssetsNode().map(function (node) {
|
||||||
|
|
|
@ -65,9 +65,9 @@ class CommandExecutionStartView(PermissionsMixin, TemplateView):
|
||||||
return super().get_permissions()
|
return super().get_permissions()
|
||||||
|
|
||||||
def get_user_system_users(self):
|
def get_user_system_users(self):
|
||||||
from perms.utils import AssetPermissionUtil
|
from perms.utils import AssetPermissionUtilV2
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
util = AssetPermissionUtil(user)
|
util = AssetPermissionUtilV2(user)
|
||||||
system_users = [s for s in util.get_system_users() if s.protocol == 'ssh']
|
system_users = [s for s in util.get_system_users() if s.protocol == 'ssh']
|
||||||
return system_users
|
return system_users
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ from assets.models import Asset, Domain, AdminUser, SystemUser, Label
|
||||||
from perms.models import AssetPermission
|
from perms.models import AssetPermission
|
||||||
from orgs.utils import current_org
|
from orgs.utils import current_org
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from .mixins import OrgMembershipModelViewSetMixin
|
from .mixins.api import OrgMembershipModelViewSetMixin
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
from .models import *
|
# from .models import *
|
||||||
from .serializers import *
|
# from .serializers import *
|
||||||
from .forms import *
|
# from .forms import *
|
||||||
from .api import *
|
# from .api import *
|
||||||
|
|
|
@ -34,6 +34,9 @@ class OrgBulkModelViewSet(IDInCacheFilterMixin, BulkModelViewSet):
|
||||||
queryset = self.serializer_class.setup_eager_loading(queryset)
|
queryset = self.serializer_class.setup_eager_loading(queryset)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
def allow_bulk_destroy(self, qs, filtered):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class OrgMembershipModelViewSetMixin:
|
class OrgMembershipModelViewSetMixin:
|
||||||
org = None
|
org = None
|
||||||
|
|
|
@ -8,7 +8,7 @@ from perms.models import AssetPermission
|
||||||
from common.serializers import AdaptedBulkListSerializer
|
from common.serializers import AdaptedBulkListSerializer
|
||||||
from .utils import set_current_org, get_current_org
|
from .utils import set_current_org, get_current_org
|
||||||
from .models import Organization
|
from .models import Organization
|
||||||
from .mixins import OrgMembershipSerializerMixin
|
from .mixins.serializers import OrgMembershipSerializerMixin
|
||||||
|
|
||||||
|
|
||||||
class OrgSerializer(ModelSerializer):
|
class OrgSerializer(ModelSerializer):
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.urls import path
|
from django.urls import re_path
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
|
|
||||||
|
from common import api as capi
|
||||||
from .. import api
|
from .. import api
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,20 +12,18 @@ app_name = 'orgs'
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
|
|
||||||
# 将会删除
|
# 将会删除
|
||||||
router.register(r'org/(?P<org_id>[0-9a-zA-Z\-]{36})/membership/admins',
|
|
||||||
api.OrgMembershipAdminsViewSet, 'membership-admins')
|
|
||||||
router.register(r'org/(?P<org_id>[0-9a-zA-Z\-]{36})/membership/users',
|
|
||||||
api.OrgMembershipUsersViewSet, 'membership-users'),
|
|
||||||
# 替换为这个
|
|
||||||
router.register(r'orgs/(?P<org_id>[0-9a-zA-Z\-]{36})/membership/admins',
|
router.register(r'orgs/(?P<org_id>[0-9a-zA-Z\-]{36})/membership/admins',
|
||||||
api.OrgMembershipAdminsViewSet, 'membership-admins-2')
|
api.OrgMembershipAdminsViewSet, 'membership-admins')
|
||||||
router.register(r'orgs/(?P<org_id>[0-9a-zA-Z\-]{36})/membership/users',
|
router.register(r'orgs/(?P<org_id>[0-9a-zA-Z\-]{36})/membership/users',
|
||||||
api.OrgMembershipUsersViewSet, 'membership-users-2'),
|
api.OrgMembershipUsersViewSet, 'membership-users'),
|
||||||
|
|
||||||
router.register(r'orgs', api.OrgViewSet, 'org')
|
router.register(r'orgs', api.OrgViewSet, 'org')
|
||||||
|
|
||||||
|
old_version_urlpatterns = [
|
||||||
|
re_path('(?P<resource>org)/.*', capi.redirect_plural_name_api)
|
||||||
|
]
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns += router.urls
|
urlpatterns += router.urls + old_version_urlpatterns
|
||||||
|
|
|
@ -7,7 +7,6 @@ from rest_framework.views import Response
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework.generics import RetrieveUpdateAPIView, ListAPIView
|
from rest_framework.generics import RetrieveUpdateAPIView, ListAPIView
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
from rest_framework.pagination import LimitOffsetPagination
|
|
||||||
|
|
||||||
from common.permissions import IsOrgAdmin
|
from common.permissions import IsOrgAdmin
|
||||||
from common.utils import get_object_or_none
|
from common.utils import get_object_or_none
|
||||||
|
@ -31,7 +30,6 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
queryset = AssetPermission.objects.all()
|
queryset = AssetPermission.objects.all()
|
||||||
serializer_class = serializers.AssetPermissionCreateUpdateSerializer
|
serializer_class = serializers.AssetPermissionCreateUpdateSerializer
|
||||||
pagination_class = LimitOffsetPagination
|
|
||||||
filter_fields = ['name']
|
filter_fields = ['name']
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
|
|
||||||
|
@ -247,7 +245,6 @@ class AssetPermissionAddAssetApi(RetrieveUpdateAPIView):
|
||||||
|
|
||||||
class AssetPermissionAssetsApi(ListAPIView):
|
class AssetPermissionAssetsApi(ListAPIView):
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
pagination_class = LimitOffsetPagination
|
|
||||||
serializer_class = serializers.AssetPermissionAssetsSerializer
|
serializer_class = serializers.AssetPermissionAssetsSerializer
|
||||||
filter_fields = ("hostname", "ip")
|
filter_fields = ("hostname", "ip")
|
||||||
search_fields = filter_fields
|
search_fields = filter_fields
|
||||||
|
|
|
@ -12,9 +12,6 @@ from django.views.decorators.http import condition
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from assets.utils import LabelFilterMixin
|
from assets.utils import LabelFilterMixin
|
||||||
from ..utils import (
|
|
||||||
AssetPermissionUtil
|
|
||||||
)
|
|
||||||
from .. import const
|
from .. import const
|
||||||
from ..hands import Asset, Node, SystemUser
|
from ..hands import Asset, Node, SystemUser
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
|
@ -24,119 +21,120 @@ logger = get_logger(__name__)
|
||||||
__all__ = ['UserPermissionCacheMixin', 'GrantAssetsMixin', 'NodesWithUngroupMixin']
|
__all__ = ['UserPermissionCacheMixin', 'GrantAssetsMixin', 'NodesWithUngroupMixin']
|
||||||
|
|
||||||
|
|
||||||
def get_etag(request, *args, **kwargs):
|
# def get_etag(request, *args, **kwargs):
|
||||||
cache_policy = request.GET.get("cache_policy")
|
# cache_policy = request.GET.get("cache_policy")
|
||||||
if cache_policy != '1':
|
# if cache_policy != '1':
|
||||||
return None
|
# return None
|
||||||
if not UserPermissionCacheMixin.CACHE_ENABLE:
|
# if not UserPermissionCacheMixin.CACHE_ENABLE:
|
||||||
return None
|
# return None
|
||||||
view = request.parser_context.get("view")
|
# view = request.parser_context.get("view")
|
||||||
if not view:
|
# if not view:
|
||||||
return None
|
# return None
|
||||||
etag = view.get_meta_cache_id()
|
# etag = view.get_meta_cache_id()
|
||||||
return etag
|
# return etag
|
||||||
|
|
||||||
|
|
||||||
class UserPermissionCacheMixin:
|
class UserPermissionCacheMixin:
|
||||||
cache_policy = '0'
|
pass
|
||||||
RESP_CACHE_KEY = '_PERMISSION_RESPONSE_CACHE_V2_{}'
|
# cache_policy = '0'
|
||||||
CACHE_ENABLE = settings.ASSETS_PERM_CACHE_ENABLE
|
# RESP_CACHE_KEY = '_PERMISSION_RESPONSE_CACHE_V2_{}'
|
||||||
CACHE_TIME = settings.ASSETS_PERM_CACHE_TIME
|
# CACHE_ENABLE = settings.ASSETS_PERM_CACHE_ENABLE
|
||||||
_object = None
|
# CACHE_TIME = settings.ASSETS_PERM_CACHE_TIME
|
||||||
|
# _object = None
|
||||||
def get_object(self):
|
#
|
||||||
return None
|
# def get_object(self):
|
||||||
|
# return None
|
||||||
# 内部使用可控制缓存
|
#
|
||||||
def _get_object(self):
|
# # 内部使用可控制缓存
|
||||||
if not self._object:
|
# def _get_object(self):
|
||||||
self._object = self.get_object()
|
# if not self._object:
|
||||||
return self._object
|
# self._object = self.get_object()
|
||||||
|
# return self._object
|
||||||
def get_object_id(self):
|
#
|
||||||
obj = self._get_object()
|
# def get_object_id(self):
|
||||||
if obj:
|
# obj = self._get_object()
|
||||||
return str(obj.id)
|
# if obj:
|
||||||
return None
|
# return str(obj.id)
|
||||||
|
# return None
|
||||||
def get_request_md5(self):
|
#
|
||||||
path = self.request.path
|
# def get_request_md5(self):
|
||||||
query = {k: v for k, v in self.request.GET.items()}
|
# path = self.request.path
|
||||||
query.pop("_", None)
|
# query = {k: v for k, v in self.request.GET.items()}
|
||||||
query = "&".join(["{}={}".format(k, v) for k, v in query.items()])
|
# query.pop("_", None)
|
||||||
full_path = "{}?{}".format(path, query)
|
# query = "&".join(["{}={}".format(k, v) for k, v in query.items()])
|
||||||
return md5(full_path.encode()).hexdigest()
|
# full_path = "{}?{}".format(path, query)
|
||||||
|
# return md5(full_path.encode()).hexdigest()
|
||||||
def get_meta_cache_id(self):
|
#
|
||||||
obj = self._get_object()
|
# def get_meta_cache_id(self):
|
||||||
util = AssetPermissionUtil(obj, cache_policy=self.cache_policy)
|
# obj = self._get_object()
|
||||||
meta_cache_id = util.cache_meta.get('id')
|
# util = AssetPermissionUtil(obj, cache_policy=self.cache_policy)
|
||||||
return meta_cache_id
|
# meta_cache_id = util.cache_meta.get('id')
|
||||||
|
# return meta_cache_id
|
||||||
def get_response_cache_id(self):
|
#
|
||||||
obj_id = self.get_object_id()
|
# def get_response_cache_id(self):
|
||||||
request_md5 = self.get_request_md5()
|
# obj_id = self.get_object_id()
|
||||||
meta_cache_id = self.get_meta_cache_id()
|
# request_md5 = self.get_request_md5()
|
||||||
resp_cache_id = '{}_{}_{}'.format(obj_id, request_md5, meta_cache_id)
|
# meta_cache_id = self.get_meta_cache_id()
|
||||||
return resp_cache_id
|
# resp_cache_id = '{}_{}_{}'.format(obj_id, request_md5, meta_cache_id)
|
||||||
|
# return resp_cache_id
|
||||||
def get_response_from_cache(self):
|
#
|
||||||
# 没有数据缓冲
|
# def get_response_from_cache(self):
|
||||||
meta_cache_id = self.get_meta_cache_id()
|
# # 没有数据缓冲
|
||||||
if not meta_cache_id:
|
# meta_cache_id = self.get_meta_cache_id()
|
||||||
logger.debug("Not get meta id: {}".format(meta_cache_id))
|
# if not meta_cache_id:
|
||||||
return None
|
# logger.debug("Not get meta id: {}".format(meta_cache_id))
|
||||||
# 从响应缓冲里获取响应
|
# return None
|
||||||
key = self.get_response_key()
|
# # 从响应缓冲里获取响应
|
||||||
data = cache.get(key)
|
# key = self.get_response_key()
|
||||||
if not data:
|
# data = cache.get(key)
|
||||||
logger.debug("Not get response from cache: {}".format(key))
|
# if not data:
|
||||||
return None
|
# logger.debug("Not get response from cache: {}".format(key))
|
||||||
logger.debug("Get user permission from cache: {}".format(self.get_object()))
|
# return None
|
||||||
response = Response(data)
|
# logger.debug("Get user permission from cache: {}".format(self.get_object()))
|
||||||
return response
|
# response = Response(data)
|
||||||
|
# return response
|
||||||
def expire_response_cache(self):
|
#
|
||||||
obj_id = self.get_object_id()
|
# def expire_response_cache(self):
|
||||||
expire_cache_id = '{}_{}'.format(obj_id, '*')
|
# obj_id = self.get_object_id()
|
||||||
key = self.RESP_CACHE_KEY.format(expire_cache_id)
|
# expire_cache_id = '{}_{}'.format(obj_id, '*')
|
||||||
cache.delete_pattern(key)
|
# 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()
|
# def get_response_key(self):
|
||||||
key = self.RESP_CACHE_KEY.format(resp_cache_id)
|
# resp_cache_id = self.get_response_cache_id()
|
||||||
return key
|
# key = self.RESP_CACHE_KEY.format(resp_cache_id)
|
||||||
|
# return key
|
||||||
def set_response_to_cache(self, response):
|
#
|
||||||
key = self.get_response_key()
|
# def set_response_to_cache(self, response):
|
||||||
cache.set(key, response.data, self.CACHE_TIME)
|
# key = self.get_response_key()
|
||||||
logger.debug("Set response to cache: {}".format(key))
|
# cache.set(key, response.data, self.CACHE_TIME)
|
||||||
|
# logger.debug("Set response to cache: {}".format(key))
|
||||||
@method_decorator(condition(etag_func=get_etag))
|
#
|
||||||
def get(self, request, *args, **kwargs):
|
# @method_decorator(condition(etag_func=get_etag))
|
||||||
if not self.CACHE_ENABLE:
|
# def get(self, request, *args, **kwargs):
|
||||||
self.cache_policy = '0'
|
# if not self.CACHE_ENABLE:
|
||||||
else:
|
# self.cache_policy = '0'
|
||||||
self.cache_policy = request.GET.get('cache_policy', '0')
|
# else:
|
||||||
|
# self.cache_policy = request.GET.get('cache_policy', '0')
|
||||||
obj = self._get_object()
|
#
|
||||||
if obj is None:
|
# obj = self._get_object()
|
||||||
logger.debug("Not get response from cache: obj is none")
|
# if obj is None:
|
||||||
return super().get(request, *args, **kwargs)
|
# 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))
|
# if AssetPermissionUtil.is_not_using_cache(self.cache_policy):
|
||||||
return super().get(request, *args, **kwargs)
|
# logger.debug("Not get resp from cache: {}".format(self.cache_policy))
|
||||||
elif AssetPermissionUtil.is_refresh_cache(self.cache_policy):
|
# return super().get(request, *args, **kwargs)
|
||||||
logger.debug("Not get resp from cache: {}".format(self.cache_policy))
|
# elif AssetPermissionUtil.is_refresh_cache(self.cache_policy):
|
||||||
self.expire_response_cache()
|
# 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()
|
# logger.debug("Try get response from cache")
|
||||||
if not resp:
|
# resp = self.get_response_from_cache()
|
||||||
resp = super().get(request, *args, **kwargs)
|
# if not resp:
|
||||||
self.set_response_to_cache(resp)
|
# resp = super().get(request, *args, **kwargs)
|
||||||
return resp
|
# self.set_response_to_cache(resp)
|
||||||
|
# return resp
|
||||||
|
|
||||||
|
|
||||||
class NodesWithUngroupMixin:
|
class NodesWithUngroupMixin:
|
||||||
|
@ -202,9 +200,11 @@ class GrantAssetsMixin(LabelFilterMixin):
|
||||||
data.append(asset)
|
data.append(asset)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_serializer(self, queryset_list, many=True):
|
def get_serializer(self, assets_items=None, many=True):
|
||||||
data = self.get_serializer_queryset(queryset_list)
|
if assets_items is None:
|
||||||
return super().get_serializer(data, many=True)
|
assets_items = []
|
||||||
|
assets_items = self.get_serializer_queryset(assets_items)
|
||||||
|
return super().get_serializer(assets_items, many=many)
|
||||||
|
|
||||||
def filter_queryset_by_id(self, assets_items):
|
def filter_queryset_by_id(self, assets_items):
|
||||||
i = self.request.query_params.get("id")
|
i = self.request.query_params.get("id")
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
from rest_framework import viewsets, generics
|
from rest_framework import viewsets, generics
|
||||||
from rest_framework.pagination import LimitOffsetPagination
|
|
||||||
from rest_framework.views import Response
|
from rest_framework.views import Response
|
||||||
|
|
||||||
from common.permissions import IsOrgAdmin
|
from common.permissions import IsOrgAdmin
|
||||||
|
|
||||||
from ..models import RemoteAppPermission
|
from ..models import RemoteAppPermission
|
||||||
from ..serializers import (
|
from ..serializers import (
|
||||||
RemoteAppPermissionSerializer,
|
RemoteAppPermissionSerializer,
|
||||||
|
@ -28,7 +25,6 @@ class RemoteAppPermissionViewSet(viewsets.ModelViewSet):
|
||||||
search_fields = filter_fields
|
search_fields = filter_fields
|
||||||
queryset = RemoteAppPermission.objects.all()
|
queryset = RemoteAppPermission.objects.all()
|
||||||
serializer_class = RemoteAppPermissionSerializer
|
serializer_class = RemoteAppPermissionSerializer
|
||||||
pagination_class = LimitOffsetPagination
|
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
|
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue