mirror of https://github.com/jumpserver/jumpserver
Dev (#1646)
* [Update] 添加org * [Update] 修改url * [Update] 完成基本框架 * [Update] 修改一些逻辑 * [Update] 修改用户view * [Update] 修改资产 * [Update] 修改asset api * [Update] 修改协议小问题 * [Update] stash it * [Update] 修改约束 * [Update] 修改外键为org_id * [Update] 删掉Premiddleware * [Update] 修改Node * [Update] 修改get_current_org 为 proxy对象 current_org * [Bugfix] 解决Node.root() 死循环,移动AdminRequired到permission中 (#1571) * [Update] 修改permission (#1574) * Tmp org (#1579) * [Update] 添加org api, 升级到django 2.0 * [Update] fix some bug * [Update] 修改一些bug * [Update] 添加授权规则org (#1580) * [Update] 修复创建授权规则,显示org_name不是有效UUID的bug * [Update] 更新org之间隔离授权规则,解决QuerySet与Manager问题;修复创建用户,显示org_name不是有效UUID之bug; * Tmp org (#1583) * [Update] 修改一些内容 * [Update] 修改datatable 支持process * [Bugfix] 修复asset queryset 没有valid方法的bug * [Update] 在线/历史/命令model添加org;修复命令记录保存org失败bug (#1584) * [Update] 修复创建授权规则,显示org_name不是有效UUID的bug * [Update] 更新org之间隔离授权规则,解决QuerySet与Manager问题;修复创建用户,显示org_name不是有效UUID之bug; * [Update] 在线/历史/命令model添加org * [Bugfix] 修复命令记录,保存org不成功bug * [Update] Org功能修改 * [Bugfix] 修复merge带来的问题 * [Update] org admin显示资产详情右侧选项卡;修复资产授权添加用户,会显示其他org用户的bug (#1594) * [Bugfix] 修复资产授权添加用户,显示其他org的用户bug * [Update] org admin 显示资产详情右侧选项卡 * Tmp org (#1596) * [Update] 修改index view * [Update] 修改nav * [Update] 修改profile * [Bugfix] 修复org下普通用户打开web终端看不到已被授权的资产和节点bug * [Update] 修改get_all_assets * [Bugfix] 修复节点前面有个空目录 * [Bugfix] 修复merge引起的bug * [Update] Add init * [Update] Node get_all_assets 过滤游离资产,条件nodes_key=None -> nodes=None * [Update] 恢复原来的api地址 * [Update] 修改api * [Bugfix] 修复org下用户查看我的资产不显示已授权节点/资产的bug * [Bugfix] Fix perm name unique * [Bugfix] 修复校验失败api * [Update] Merge with org * [Merge] 修改一下bug * [Update] 暂时修改一些url * [Update] 修改url 为django 2.0 path * [Update] 优化datatable 和显示组织优化 * [Update] 升级url * [Bugfix] 修复coco启动失败(load_config_from_server)、硬件刷新,测试连接,str 没有 decode(… (#1613) * [Bugfix] 修复coco启动失败(load_config_from_server)、硬件刷新,测试连接,str 没有 decode() method的bug * [Bugfix] (task任务系统)修复资产连接性测试、硬件刷新和系统用户连接性测试失败等bug * [Bugfix] 修复一些bug * [Bugfix] 修复一些bug * [Update] 更新org下普通用户的资产详情 (#1619) * [Update] 更新org下普通用户查看资产详情,只显示数据 * [Update] 优化org下普通用户查看资产详情前端代码 * [Update] 创建/更新用户的role选项;密码强度提示信息中英文; (#1623) * [Update] 修改 超级管理员/组织管理员 在 创建/更新 用户时role的选项 问题 * [Update] 用户密码强度提示信息支持中英文 * [Update] 修改token返回 * [Update] Asset返回org name * [Update] 修改支持xpack * [Update] 修改url * [Bugfix] 修复不登录就能查看资产的bug * [Update] 用户修改 * [Bugfix] ... * [Bugfix] 修复跳转错误的问题 * [Update] xpack/orgs组织添加删除功能-js; 修复Label继承Org后bug; (#1644) * [Update] 更新xpack下orgs的翻译信息 * [Update] 更新model Label,继承OrgModelMixin; * [Update] xpack/orgs组织添加删除功能-js; 修复Label继承Org后bug; * [Bugfix] 修复小bug * [Update] 优化一些api * [Update] 优化用户资产页面 * [Update] 更新 xpack/orgs 删除功能:限制在当前org下删除当前org (#1645) * [Update] 修改版本号pull/1651/head^2 1.4.0
parent
dded4e10fb
commit
d5451a482a
|
@ -32,3 +32,4 @@ django.db
|
|||
celerybeat-schedule.db
|
||||
data/static
|
||||
docs/_build/
|
||||
xpack
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
__version__ = "1.3.3"
|
||||
__version__ = "1.4.0"
|
||||
|
|
|
@ -20,7 +20,7 @@ from rest_framework_bulk import BulkModelViewSet
|
|||
|
||||
from common.mixins import IDInFilterMixin
|
||||
from common.utils import get_logger
|
||||
from ..hands import IsSuperUser
|
||||
from ..hands import IsOrgAdmin
|
||||
from ..models import AdminUser, Asset
|
||||
from .. import serializers
|
||||
from ..tasks import test_admin_user_connectability_manual
|
||||
|
@ -39,19 +39,19 @@ class AdminUserViewSet(IDInFilterMixin, BulkModelViewSet):
|
|||
"""
|
||||
queryset = AdminUser.objects.all()
|
||||
serializer_class = serializers.AdminUserSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
|
||||
class AdminUserAuthApi(generics.UpdateAPIView):
|
||||
queryset = AdminUser.objects.all()
|
||||
serializer_class = serializers.AdminUserAuthSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
|
||||
class ReplaceNodesAdminUserApi(generics.UpdateAPIView):
|
||||
queryset = AdminUser.objects.all()
|
||||
serializer_class = serializers.ReplaceNodeAdminUserSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
admin_user = self.get_object()
|
||||
|
@ -75,7 +75,7 @@ class AdminUserTestConnectiveApi(generics.RetrieveAPIView):
|
|||
Test asset admin user connectivity
|
||||
"""
|
||||
queryset = AdminUser.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
admin_user = self.get_object()
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
#
|
||||
|
||||
import random
|
||||
import time
|
||||
|
||||
from rest_framework import generics
|
||||
from rest_framework import generics, permissions
|
||||
from rest_framework.response import Response
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
|
||||
|
@ -13,7 +14,7 @@ from django.db.models import Q
|
|||
|
||||
from common.mixins import IDInFilterMixin
|
||||
from common.utils import get_logger
|
||||
from ..hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser
|
||||
from common.permissions import IsOrgAdmin, IsAppUser, IsOrgAdminOrAppUser
|
||||
from ..models import Asset, SystemUser, AdminUser, Node
|
||||
from .. import serializers
|
||||
from ..tasks import update_asset_hardware_info_manual, \
|
||||
|
@ -39,38 +40,42 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
|
|||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()\
|
||||
.prefetch_related('labels', 'nodes')\
|
||||
.select_related('admin_user')
|
||||
admin_user_id = self.request.query_params.get('admin_user_id')
|
||||
def filter_node(self):
|
||||
node_id = self.request.query_params.get("node_id")
|
||||
if not node_id:
|
||||
return
|
||||
|
||||
node = get_object_or_404(Node, id=node_id)
|
||||
show_current_asset = self.request.query_params.get("show_current_asset")
|
||||
|
||||
if admin_user_id:
|
||||
admin_user = get_object_or_404(AdminUser, id=admin_user_id)
|
||||
queryset = queryset.filter(admin_user=admin_user)
|
||||
|
||||
if node_id and show_current_asset:
|
||||
node = get_object_or_404(Node, id=node_id)
|
||||
if node.is_root():
|
||||
queryset = queryset.filter(
|
||||
if node.is_root():
|
||||
if show_current_asset:
|
||||
self.queryset = self.queryset.filter(
|
||||
Q(nodes=node_id) | Q(nodes__isnull=True)
|
||||
).distinct()
|
||||
else:
|
||||
queryset = queryset.filter(nodes=node).distinct()
|
||||
return
|
||||
if show_current_asset:
|
||||
self.queryset = self.queryset.filter(nodes=node).distinct()
|
||||
else:
|
||||
self.queryset = self.queryset.filter(
|
||||
nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key),
|
||||
).distinct()
|
||||
|
||||
if node_id and not show_current_asset:
|
||||
node = get_object_or_404(Node, id=node_id)
|
||||
if node.is_root():
|
||||
queryset = Asset.objects.all()
|
||||
else:
|
||||
queryset = queryset.filter(
|
||||
nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key),
|
||||
).distinct()
|
||||
return queryset
|
||||
def filter_admin_user_id(self):
|
||||
admin_user_id = self.request.query_params.get('admin_user_id')
|
||||
if admin_user_id:
|
||||
admin_user = get_object_or_404(AdminUser, id=admin_user_id)
|
||||
self.queryset = self.queryset.filter(admin_user=admin_user)
|
||||
|
||||
def get_queryset(self):
|
||||
self.queryset = super().get_queryset()\
|
||||
.prefetch_related('labels', 'nodes')\
|
||||
.select_related('admin_user')
|
||||
self.filter_admin_user_id()
|
||||
self.filter_node()
|
||||
return self.queryset
|
||||
|
||||
|
||||
class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||
|
@ -79,7 +84,7 @@ class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
|
|||
"""
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
|
||||
class AssetRefreshHardwareApi(generics.RetrieveAPIView):
|
||||
|
@ -88,7 +93,7 @@ class AssetRefreshHardwareApi(generics.RetrieveAPIView):
|
|||
"""
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
asset_id = kwargs.get('pk')
|
||||
|
@ -102,7 +107,7 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView):
|
|||
Test asset admin user connectivity
|
||||
"""
|
||||
queryset = Asset.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
asset_id = kwargs.get('pk')
|
||||
|
@ -113,7 +118,7 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView):
|
|||
|
||||
class AssetGatewayApi(generics.RetrieveAPIView):
|
||||
queryset = Asset.objects.all()
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
asset_id = kwargs.get('pk')
|
||||
|
|
|
@ -2,12 +2,11 @@
|
|||
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from rest_framework.views import APIView, Response
|
||||
from rest_framework.generics import RetrieveAPIView
|
||||
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
|
||||
from common.utils import get_logger
|
||||
from ..hands import IsSuperUser, IsSuperUserOrAppUser
|
||||
from common.permissions import IsOrgAdmin, IsAppUser, IsOrgAdminOrAppUser
|
||||
from ..models import Domain, Gateway
|
||||
from ..utils import test_gateway_connectability
|
||||
from .. import serializers
|
||||
|
@ -19,7 +18,7 @@ __all__ = ['DomainViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"]
|
|||
|
||||
class DomainViewSet(BulkModelViewSet):
|
||||
queryset = Domain.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.DomainSerializer
|
||||
|
||||
def get_serializer_class(self):
|
||||
|
@ -29,7 +28,7 @@ class DomainViewSet(BulkModelViewSet):
|
|||
|
||||
def get_permissions(self):
|
||||
if self.request.query_params.get('gateway'):
|
||||
self.permission_classes = (IsSuperUserOrAppUser,)
|
||||
self.permission_classes = (IsOrgAdminOrAppUser,)
|
||||
return super().get_permissions()
|
||||
|
||||
|
||||
|
@ -37,12 +36,12 @@ class GatewayViewSet(BulkModelViewSet):
|
|||
filter_fields = ("domain",)
|
||||
search_fields = filter_fields
|
||||
queryset = Gateway.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.GatewaySerializer
|
||||
|
||||
|
||||
class GatewayTestConnectionApi(SingleObjectMixin, APIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
model = Gateway
|
||||
object = None
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ from rest_framework_bulk import BulkModelViewSet
|
|||
from django.db.models import Count
|
||||
|
||||
from common.utils import get_logger
|
||||
from ..hands import IsSuperUser
|
||||
from ..hands import IsOrgAdmin
|
||||
from ..models import Label
|
||||
from .. import serializers
|
||||
|
||||
|
@ -27,8 +27,7 @@ __all__ = ['LabelViewSet']
|
|||
|
||||
|
||||
class LabelViewSet(BulkModelViewSet):
|
||||
queryset = Label.objects.annotate(asset_count=Count("assets"))
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.LabelSerializer
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
|
@ -36,3 +35,7 @@ class LabelViewSet(BulkModelViewSet):
|
|||
self.serializer_class = serializers.LabelDistinctSerializer
|
||||
self.queryset = self.queryset.values("name").distinct()
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
self.queryset = Label.objects.annotate(asset_count=Count("assets"))
|
||||
return self.queryset
|
||||
|
|
|
@ -13,16 +13,17 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from rest_framework import generics, mixins
|
||||
from rest_framework import generics, mixins, viewsets
|
||||
from rest_framework.serializers import ValidationError
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.db.models import Count
|
||||
|
||||
from common.utils import get_logger, get_object_or_none
|
||||
from ..hands import IsSuperUser
|
||||
from ..hands import IsOrgAdmin
|
||||
from ..models import Node
|
||||
from ..tasks import update_assets_hardware_info_util, test_asset_connectability_util
|
||||
from .. import serializers
|
||||
|
@ -30,57 +31,31 @@ from .. import serializers
|
|||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'NodeViewSet', 'NodeChildrenApi',
|
||||
'NodeAssetsApi',
|
||||
'NodeAddAssetsApi', 'NodeRemoveAssetsApi',
|
||||
'NodeReplaceAssetsApi',
|
||||
'NodeViewSet', 'NodeChildrenApi', 'NodeAssetsApi',
|
||||
'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'NodeReplaceAssetsApi',
|
||||
'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi',
|
||||
'TestNodeConnectiveApi'
|
||||
]
|
||||
|
||||
|
||||
class NodeViewSet(BulkModelViewSet):
|
||||
class NodeViewSet(viewsets.ModelViewSet):
|
||||
queryset = Node.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.NodeSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset().annotate(Count('assets'))
|
||||
return queryset
|
||||
|
||||
def perform_create(self, serializer):
|
||||
child_key = Node.root().get_next_child_key()
|
||||
serializer.validated_data["key"] = child_key
|
||||
serializer.save()
|
||||
|
||||
|
||||
# class NodeWithAssetsApi(generics.ListAPIView):
|
||||
# permission_classes = (IsSuperUser,)
|
||||
# serializers = serializers.NodeSerializer
|
||||
#
|
||||
# def get_node(self):
|
||||
# pk = self.kwargs.get('pk') or self.request.query_params.get('node')
|
||||
# if not pk:
|
||||
# node = Node.root()
|
||||
# else:
|
||||
# node = get_object_or_404(Node, pk)
|
||||
# return node
|
||||
#
|
||||
# def get_queryset(self):
|
||||
# queryset = []
|
||||
# node = self.get_node()
|
||||
# children = node.get_children()
|
||||
# assets = node.get_assets()
|
||||
# queryset.extend(list(children))
|
||||
#
|
||||
# for asset in assets:
|
||||
# node = Node()
|
||||
# node.id = asset.id
|
||||
# node.parent = node.id
|
||||
# node.value = asset.hostname
|
||||
# queryset.append(node)
|
||||
# return queryset
|
||||
|
||||
|
||||
class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
||||
queryset = Node.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.NodeSerializer
|
||||
instance = None
|
||||
|
||||
|
@ -126,22 +101,26 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
|||
query_all = self.request.query_params.get("all")
|
||||
query_assets = self.request.query_params.get('assets')
|
||||
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()
|
||||
|
||||
if query_all:
|
||||
children = node.get_all_children().annotate(Count("assets"))
|
||||
else:
|
||||
children = node.get_children().annotate(Count("assets"))
|
||||
queryset.extend(list(children))
|
||||
|
||||
if query_assets:
|
||||
assets = node.get_assets()
|
||||
for asset in assets:
|
||||
node_fake = Node()
|
||||
node_fake.assets__count = 0
|
||||
node_fake.id = asset.id
|
||||
node_fake.is_node = False
|
||||
node_fake.parent_id = node.id
|
||||
node_fake.key = node.key + ':0'
|
||||
node_fake.value = asset.hostname
|
||||
queryset.append(node_fake)
|
||||
queryset = sorted(queryset, key=lambda x: x.is_node, reverse=True)
|
||||
|
@ -152,7 +131,7 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
|||
|
||||
|
||||
class NodeAssetsApi(generics.ListAPIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.AssetSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
|
@ -167,7 +146,7 @@ class NodeAssetsApi(generics.ListAPIView):
|
|||
|
||||
class NodeAddChildrenApi(generics.UpdateAPIView):
|
||||
queryset = Node.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.NodeAddChildrenSerializer
|
||||
instance = None
|
||||
|
||||
|
@ -185,7 +164,7 @@ class NodeAddChildrenApi(generics.UpdateAPIView):
|
|||
class NodeAddAssetsApi(generics.UpdateAPIView):
|
||||
serializer_class = serializers.NodeAssetsSerializer
|
||||
queryset = Node.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
instance = None
|
||||
|
||||
def perform_update(self, serializer):
|
||||
|
@ -197,7 +176,7 @@ class NodeAddAssetsApi(generics.UpdateAPIView):
|
|||
class NodeRemoveAssetsApi(generics.UpdateAPIView):
|
||||
serializer_class = serializers.NodeAssetsSerializer
|
||||
queryset = Node.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
instance = None
|
||||
|
||||
def perform_update(self, serializer):
|
||||
|
@ -213,7 +192,7 @@ class NodeRemoveAssetsApi(generics.UpdateAPIView):
|
|||
class NodeReplaceAssetsApi(generics.UpdateAPIView):
|
||||
serializer_class = serializers.NodeAssetsSerializer
|
||||
queryset = Node.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
instance = None
|
||||
|
||||
def perform_update(self, serializer):
|
||||
|
@ -224,7 +203,7 @@ class NodeReplaceAssetsApi(generics.UpdateAPIView):
|
|||
|
||||
|
||||
class RefreshNodeHardwareInfoApi(APIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
model = Node
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
@ -237,7 +216,7 @@ class RefreshNodeHardwareInfoApi(APIView):
|
|||
|
||||
|
||||
class TestNodeConnectiveApi(APIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
model = Node
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
|
|
@ -16,8 +16,9 @@
|
|||
from rest_framework import generics
|
||||
from rest_framework.response import Response
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
|
||||
from common.utils import get_logger
|
||||
from ..hands import IsSuperUser, IsSuperUserOrAppUser
|
||||
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
||||
from ..models import SystemUser
|
||||
from .. import serializers
|
||||
from ..tasks import push_system_user_to_assets_manual, \
|
||||
|
@ -37,7 +38,7 @@ class SystemUserViewSet(BulkModelViewSet):
|
|||
"""
|
||||
queryset = SystemUser.objects.all()
|
||||
serializer_class = serializers.SystemUserSerializer
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
|
||||
class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
|
||||
|
@ -45,7 +46,7 @@ class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
|
|||
Get system user auth info
|
||||
"""
|
||||
queryset = SystemUser.objects.all()
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.SystemUserAuthSerializer
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
|
@ -59,7 +60,7 @@ class SystemUserPushApi(generics.RetrieveAPIView):
|
|||
Push system user to cluster assets api
|
||||
"""
|
||||
queryset = SystemUser.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
system_user = self.get_object()
|
||||
|
@ -75,7 +76,7 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView):
|
|||
Push system user to cluster assets api
|
||||
"""
|
||||
queryset = SystemUser.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
system_user = self.get_object()
|
||||
|
|
|
@ -3,14 +3,17 @@
|
|||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from ..models import Asset, AdminUser
|
||||
from common.utils import get_logger
|
||||
from orgs.mixins import OrgModelForm
|
||||
|
||||
from ..models import Asset, AdminUser
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = ['AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm']
|
||||
|
||||
|
||||
class AssetCreateForm(forms.ModelForm):
|
||||
class AssetCreateForm(OrgModelForm):
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
|
@ -50,7 +53,7 @@ class AssetCreateForm(forms.ModelForm):
|
|||
}
|
||||
|
||||
|
||||
class AssetUpdateForm(forms.ModelForm):
|
||||
class AssetUpdateForm(OrgModelForm):
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
|
@ -90,7 +93,7 @@ class AssetUpdateForm(forms.ModelForm):
|
|||
}
|
||||
|
||||
|
||||
class AssetBulkUpdateForm(forms.ModelForm):
|
||||
class AssetBulkUpdateForm(OrgModelForm):
|
||||
assets = forms.ModelMultipleChoiceField(
|
||||
required=True, help_text='* required',
|
||||
label=_('Select assets'), queryset=Asset.objects.all(),
|
||||
|
@ -105,7 +108,7 @@ class AssetBulkUpdateForm(forms.ModelForm):
|
|||
label=_('Port'), required=False, min_value=1, max_value=65535,
|
||||
)
|
||||
admin_user = forms.ModelChoiceField(
|
||||
required=False, queryset=AdminUser.objects.all(),
|
||||
required=False, queryset=AdminUser.objects,
|
||||
label=_("Admin user"),
|
||||
widget=forms.Select(
|
||||
attrs={
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from orgs.mixins import OrgModelForm
|
||||
from ..models import Domain, Asset, Gateway
|
||||
from .user import PasswordAndKeyAuthForm
|
||||
|
||||
|
@ -34,7 +35,7 @@ class DomainForm(forms.ModelForm):
|
|||
return instance
|
||||
|
||||
|
||||
class GatewayForm(PasswordAndKeyAuthForm):
|
||||
class GatewayForm(PasswordAndKeyAuthForm, OrgModelForm):
|
||||
|
||||
def save(self, commit=True):
|
||||
# Because we define custom field, so we need rewrite :method: `save`
|
||||
|
|
|
@ -11,6 +11,6 @@
|
|||
"""
|
||||
|
||||
|
||||
from common.mixins import AdminUserRequiredMixin
|
||||
from common.permissions import IsAppUser, IsSuperUser, IsValidUser, IsSuperUserOrAppUser
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser
|
||||
from users.models import User, UserGroup
|
||||
|
|
|
@ -13,6 +13,7 @@ from django.core.cache import cache
|
|||
|
||||
from ..const import ASSET_ADMIN_CONN_CACHE_KEY
|
||||
from .user import AdminUser, SystemUser
|
||||
from orgs.mixins import OrgModelMixin,OrgManager
|
||||
|
||||
__all__ = ['Asset']
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -44,12 +45,7 @@ class AssetQuerySet(models.QuerySet):
|
|||
return self.active()
|
||||
|
||||
|
||||
class AssetManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
return AssetQuerySet(self.model, using=self._db)
|
||||
|
||||
|
||||
class Asset(models.Model):
|
||||
class Asset(OrgModelMixin):
|
||||
# Important
|
||||
PLATFORM_CHOICES = (
|
||||
('Linux', 'Linux'),
|
||||
|
@ -71,16 +67,11 @@ class Asset(models.Model):
|
|||
)
|
||||
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'),
|
||||
db_index=True)
|
||||
hostname = models.CharField(max_length=128, unique=True,
|
||||
verbose_name=_('Hostname'))
|
||||
protocol = models.CharField(max_length=128, default=SSH_PROTOCOL,
|
||||
choices=PROTOCOL_CHOICES,
|
||||
verbose_name=_('Protocol'))
|
||||
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
|
||||
hostname = models.CharField(max_length=128, verbose_name=_('Hostname'))
|
||||
protocol = models.CharField(max_length=128, default=SSH_PROTOCOL, choices=PROTOCOL_CHOICES, verbose_name=_('Protocol'))
|
||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES,
|
||||
default='Linux', verbose_name=_('Platform'))
|
||||
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform'))
|
||||
domain = models.ForeignKey("assets.Domain", null=True, blank=True,
|
||||
related_name='assets', verbose_name=_("Domain"),
|
||||
on_delete=models.SET_NULL)
|
||||
|
@ -94,11 +85,8 @@ class Asset(models.Model):
|
|||
null=True, verbose_name=_("Admin user"))
|
||||
|
||||
# Some information
|
||||
public_ip = models.GenericIPAddressField(max_length=32, blank=True,
|
||||
null=True,
|
||||
verbose_name=_('Public IP'))
|
||||
number = models.CharField(max_length=32, null=True, blank=True,
|
||||
verbose_name=_('Asset number'))
|
||||
public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP'))
|
||||
number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
|
||||
|
||||
# Collect
|
||||
vendor = models.CharField(max_length=64, null=True, blank=True,
|
||||
|
@ -139,7 +127,7 @@ class Asset(models.Model):
|
|||
comment = models.TextField(max_length=128, default='', blank=True,
|
||||
verbose_name=_('Comment'))
|
||||
|
||||
objects = AssetManager()
|
||||
objects = OrgManager.from_queryset(AssetQuerySet)()
|
||||
|
||||
def __str__(self):
|
||||
return '{0.hostname}({0.ip})'.format(self)
|
||||
|
@ -173,6 +161,12 @@ class Asset(models.Model):
|
|||
nodes = list(reduce(lambda x, y: set(x) | set(y), nodes))
|
||||
return nodes
|
||||
|
||||
@property
|
||||
def org_name(self):
|
||||
from orgs.models import Organization
|
||||
org = Organization.get_instance(self.org_id)
|
||||
return org.name
|
||||
|
||||
@property
|
||||
def hardware_info(self):
|
||||
if self.cpu_count:
|
||||
|
@ -233,7 +227,7 @@ class Asset(models.Model):
|
|||
return data
|
||||
|
||||
class Meta:
|
||||
unique_together = ('ip', 'port')
|
||||
unique_together = [('org_id', 'hostname')]
|
||||
verbose_name = _("Asset")
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -11,14 +11,15 @@ from django.conf import settings
|
|||
|
||||
from common.utils import get_signer, ssh_key_string_to_obj, ssh_key_gen
|
||||
from common.validators import alphanumeric
|
||||
from orgs.mixins import OrgModelMixin
|
||||
from .utils import private_key_validator
|
||||
|
||||
signer = get_signer()
|
||||
|
||||
|
||||
class AssetUser(models.Model):
|
||||
class AssetUser(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||
username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric])
|
||||
_password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
|
||||
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])
|
||||
|
|
|
@ -7,12 +7,13 @@ import random
|
|||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins import OrgModelMixin
|
||||
from .base import AssetUser
|
||||
|
||||
__all__ = ['Domain', 'Gateway']
|
||||
|
||||
|
||||
class Domain(models.Model):
|
||||
class Domain(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
|
@ -43,10 +44,12 @@ class Gateway(AssetUser):
|
|||
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
|
||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||
protocol = models.CharField(choices=PROTOCOL_CHOICES, max_length=16, default=SSH_PROTOCOL, verbose_name=_("Protocol"))
|
||||
domain = models.ForeignKey(Domain, verbose_name=_("Domain"), on_delete=models.CASCADE)
|
||||
domain = models.ForeignKey(Domain, on_delete=models.CASCADE, verbose_name=_("Domain"))
|
||||
comment = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Comment"))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
unique_together = [('name', 'org_id')]
|
||||
|
|
|
@ -4,9 +4,10 @@
|
|||
import uuid
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from orgs.mixins import OrgModelMixin
|
||||
|
||||
|
||||
class Label(models.Model):
|
||||
class Label(OrgModelMixin):
|
||||
SYSTEM_CATEGORY = "S"
|
||||
USER_CATEGORY = "U"
|
||||
CATEGORY_CHOICES = (
|
||||
|
@ -34,4 +35,4 @@ class Label(models.Model):
|
|||
|
||||
class Meta:
|
||||
db_table = "assets_label"
|
||||
unique_together = ('name', 'value')
|
||||
unique_together = [('name', 'value')]
|
||||
|
|
|
@ -5,12 +5,15 @@ import uuid
|
|||
from django.db import models, transaction
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from common.utils import with_cache
|
||||
|
||||
from orgs.mixins import OrgModelMixin
|
||||
from orgs.utils import current_org, set_current_org, get_current_org
|
||||
from orgs.models import Organization
|
||||
|
||||
__all__ = ['Node']
|
||||
|
||||
|
||||
class Node(models.Model):
|
||||
class Node(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1'
|
||||
value = models.CharField(max_length=128, verbose_name=_("Value"))
|
||||
|
@ -20,7 +23,8 @@ class Node(models.Model):
|
|||
is_node = True
|
||||
|
||||
def __str__(self):
|
||||
return self.full_value
|
||||
return self.value
|
||||
# return self.full_value
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.key == other.key
|
||||
|
@ -93,12 +97,10 @@ class Node(models.Model):
|
|||
|
||||
def get_assets(self):
|
||||
from .asset import Asset
|
||||
if self.is_root():
|
||||
assets = Asset.objects.filter(
|
||||
Q(nodes__id=self.id) | Q(nodes__isnull=True)
|
||||
)
|
||||
if self.is_default_node():
|
||||
assets = Asset.objects.filter(nodes__isnull=True)
|
||||
else:
|
||||
assets = self.assets.all()
|
||||
assets = Asset.objects.filter(nodes__id=self.id)
|
||||
return assets
|
||||
|
||||
def get_valid_assets(self):
|
||||
|
@ -106,49 +108,61 @@ class Node(models.Model):
|
|||
|
||||
def get_all_assets(self):
|
||||
from .asset import Asset
|
||||
if self.is_root():
|
||||
assets = Asset.objects.all()
|
||||
pattern = r'^{0}$|^{0}:'.format(self.key)
|
||||
args = []
|
||||
kwargs = {}
|
||||
if self.is_default_node():
|
||||
args.append(Q(nodes__key__regex=pattern) | Q(nodes=None))
|
||||
else:
|
||||
pattern = r'^{0}$|^{0}:'.format(self.key)
|
||||
assets = Asset.objects.filter(nodes__key__regex=pattern)
|
||||
kwargs['nodes__key__regex'] = pattern
|
||||
assets = Asset.objects.filter(*args, **kwargs)
|
||||
return assets
|
||||
|
||||
def get_all_valid_assets(self):
|
||||
return self.get_all_assets().valid()
|
||||
|
||||
def is_default_node(self):
|
||||
return self.is_root() and self.key == '0'
|
||||
|
||||
def is_root(self):
|
||||
return self.key == '0'
|
||||
if self.key.isdigit():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@property
|
||||
def parent_key(self):
|
||||
parent_key = ":".join(self.key.split(":")[:-1])
|
||||
return parent_key
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
if self.key == "0" or not self.key.startswith("0"):
|
||||
return self.__class__.root()
|
||||
parent_key = ":".join(self.key.split(":")[:-1])
|
||||
if self.is_root():
|
||||
return self
|
||||
try:
|
||||
parent = self.__class__.objects.get(key=parent_key)
|
||||
parent = self.__class__.objects.get(key=self.parent_key)
|
||||
return parent
|
||||
except Node.DoesNotExist:
|
||||
return self.__class__.root()
|
||||
|
||||
@parent.setter
|
||||
def parent(self, parent):
|
||||
if self.is_node:
|
||||
children = self.get_all_children()
|
||||
old_key = self.key
|
||||
with transaction.atomic():
|
||||
self.key = parent.get_next_child_key()
|
||||
for child in children:
|
||||
child.key = child.key.replace(old_key, self.key, 1)
|
||||
child.save()
|
||||
self.save()
|
||||
else:
|
||||
self.key = parent.key+':fake'
|
||||
if not self.is_node:
|
||||
self.key = parent.key + ':fake'
|
||||
return
|
||||
children = self.get_all_children()
|
||||
old_key = self.key
|
||||
with transaction.atomic():
|
||||
self.key = parent.get_next_child_key()
|
||||
for child in children:
|
||||
child.key = child.key.replace(old_key, self.key, 1)
|
||||
child.save()
|
||||
self.save()
|
||||
|
||||
def get_ancestor(self, with_self=False):
|
||||
if self.is_root():
|
||||
ancestor = self.__class__.objects.filter(key='0')
|
||||
return ancestor
|
||||
|
||||
root = self.__class__.root()
|
||||
return [root]
|
||||
_key = self.key.split(':')
|
||||
if not with_self:
|
||||
_key.pop()
|
||||
|
@ -162,10 +176,35 @@ class Node(models.Model):
|
|||
return ancestor
|
||||
|
||||
@classmethod
|
||||
def root(cls):
|
||||
obj, created = cls.objects.get_or_create(
|
||||
key='0', defaults={"key": '0', 'value': "ROOT"}
|
||||
)
|
||||
print(obj)
|
||||
return obj
|
||||
def create_root_node(cls):
|
||||
# 如果使用current_org 在set_current_org时会死循环
|
||||
_current_org = get_current_org()
|
||||
with transaction.atomic():
|
||||
if _current_org.is_default():
|
||||
key = '0'
|
||||
else:
|
||||
set_current_org(Organization.root())
|
||||
org_nodes_roots = cls.objects.filter(key__regex=r'^[0-9]+$')
|
||||
org_nodes_roots_keys = org_nodes_roots.values_list('key', flat=True)
|
||||
key = max([int(k) for k in org_nodes_roots_keys]) + 1
|
||||
set_current_org(_current_org)
|
||||
root = cls.objects.create(key=key, value=_current_org.name)
|
||||
return root
|
||||
|
||||
@classmethod
|
||||
def root(cls):
|
||||
root = cls.objects.filter(key__regex=r'^[0-9]+$')
|
||||
if root:
|
||||
return root[0]
|
||||
else:
|
||||
return cls.create_root_node()
|
||||
|
||||
@classmethod
|
||||
def generate_fake(cls, count=100):
|
||||
import random
|
||||
for i in range(count):
|
||||
node = random.choice(cls.objects.all())
|
||||
node.create_child('Node {}'.format(i))
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -69,6 +69,7 @@ class AdminUser(AssetUser):
|
|||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
unique_together = [('name', 'org_id')]
|
||||
verbose_name = _("Admin user")
|
||||
|
||||
@classmethod
|
||||
|
@ -176,6 +177,7 @@ class SystemUser(AssetUser):
|
|||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
unique_together = [('name', 'org_id')]
|
||||
verbose_name = _("System user")
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -58,7 +58,7 @@ class ReplaceNodeAdminUserSerializer(serializers.ModelSerializer):
|
|||
管理用户更新关联到的集群
|
||||
"""
|
||||
nodes = serializers.PrimaryKeyRelatedField(
|
||||
many=True, queryset=Node.objects.all()
|
||||
many=True, queryset = Node.objects.all()
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -20,12 +20,12 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
|||
model = Asset
|
||||
list_serializer_class = BulkListSerializer
|
||||
fields = '__all__'
|
||||
validators = [] # If not set to [], partial bulk update will be error
|
||||
# validators = [] # If not set to [], partial bulk update will be error
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super().get_field_names(declared_fields, info)
|
||||
fields.extend([
|
||||
'hardware_info', 'is_connective',
|
||||
'hardware_info', 'is_connective', 'org_name'
|
||||
])
|
||||
return fields
|
||||
|
||||
|
@ -43,7 +43,7 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
|
|||
fields = (
|
||||
"id", "hostname", "ip", "port", "system_users_granted",
|
||||
"is_active", "system_users_join", "os", 'domain',
|
||||
"platform", "comment", "protocol",
|
||||
"platform", "comment", "protocol", "org_id", "org_name",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -61,6 +61,6 @@ class MyAssetGrantedSerializer(AssetGrantedSerializer):
|
|||
model = Asset
|
||||
fields = (
|
||||
"id", "hostname", "system_users_granted",
|
||||
"is_active", "system_users_join",
|
||||
"os", "platform", "comment",
|
||||
"is_active", "system_users_join", "org_name",
|
||||
"os", "platform", "comment", "org_id", "protocol"
|
||||
)
|
||||
|
|
|
@ -26,7 +26,7 @@ class NodeGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
|||
model = Node
|
||||
fields = [
|
||||
'id', 'key', 'name', 'value', 'parent',
|
||||
'assets_granted', 'assets_amount',
|
||||
'assets_granted', 'assets_amount', 'org_id',
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
|
@ -43,12 +43,16 @@ class NodeGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
|||
|
||||
|
||||
class NodeSerializer(serializers.ModelSerializer):
|
||||
parent = serializers.SerializerMethodField()
|
||||
assets_amount = serializers.SerializerMethodField()
|
||||
tree_id = serializers.SerializerMethodField()
|
||||
tree_parent = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Node
|
||||
fields = ['id', 'key', 'value', 'parent', 'assets_amount', 'is_node']
|
||||
fields = [
|
||||
'id', 'key', 'value', 'assets_amount',
|
||||
'is_node', 'org_id', 'tree_id', 'tree_parent',
|
||||
]
|
||||
list_serializer_class = BulkListSerializer
|
||||
|
||||
def validate(self, data):
|
||||
|
@ -63,12 +67,16 @@ class NodeSerializer(serializers.ModelSerializer):
|
|||
return data
|
||||
|
||||
@staticmethod
|
||||
def get_parent(obj):
|
||||
return obj.parent.id if obj.is_node else obj.parent_id
|
||||
def get_assets_amount(obj):
|
||||
return obj.assets__count if hasattr(obj, 'assets__count') else 0
|
||||
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
return obj.get_all_assets().count() if obj.is_node else 0
|
||||
def get_tree_id(obj):
|
||||
return obj.key
|
||||
|
||||
@staticmethod
|
||||
def get_tree_parent(obj):
|
||||
return obj.parent_key
|
||||
|
||||
def get_fields(self):
|
||||
fields = super().get_fields()
|
||||
|
@ -78,7 +86,7 @@ class NodeSerializer(serializers.ModelSerializer):
|
|||
|
||||
|
||||
class NodeAssetsSerializer(serializers.ModelSerializer):
|
||||
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
|
||||
assets = serializers.PrimaryKeyRelatedField(many=True, queryset = Asset.objects.all())
|
||||
|
||||
class Meta:
|
||||
model = Node
|
||||
|
|
|
@ -71,7 +71,7 @@ function initTable2() {
|
|||
|
||||
function onSelected2(event, treeNode) {
|
||||
var url = asset_table2.ajax.url();
|
||||
url = setUrlParam(url, "node_id", treeNode.id);
|
||||
url = setUrlParam(url, "node_id", treeNode.node_id);
|
||||
setCookie('node_selected', treeNode.id);
|
||||
asset_table2.ajax.url(url);
|
||||
asset_table2.ajax.reload();
|
||||
|
@ -97,17 +97,20 @@ function initTree2() {
|
|||
var zNodes = [];
|
||||
$.get("{% url 'api-assets:node-list' %}", function(data, status){
|
||||
$.each(data, function (index, value) {
|
||||
value["pId"] = value["parent"];
|
||||
value["node_id"] = value["id"];
|
||||
value["id"] = value["tree_id"];
|
||||
value["pId"] = value["tree_parent"];
|
||||
{#value["open"] = true;#}
|
||||
if (value["key"] === "0") {
|
||||
value["open"] = true;
|
||||
}
|
||||
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
|
||||
value['value'] = value['value'];
|
||||
});
|
||||
zNodes = data;
|
||||
$.fn.zTree.init($("#assetTree2"), setting, zNodes);
|
||||
zTree2 = $.fn.zTree.getZTreeObj("assetTree2");
|
||||
var root = zTree2.getNodes()[0];
|
||||
zTree2.expandNode(root);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
{% extends '_modal.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
<style>
|
||||
.modal-body {
|
||||
background-color: white !important;
|
||||
}
|
||||
</style>
|
||||
{% block modal_id %}user_asset_detail_modal{% endblock %}
|
||||
|
||||
{% block modal_title %}{% trans "Asset detail" %}{% endblock %}
|
||||
|
||||
{% block modal_body %}
|
||||
<div class="ibox-content" style="background-color: inherit">
|
||||
<table class="table">
|
||||
<tbody id="asset_detail_tbody">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal_button %}
|
||||
<button data-dismiss="modal" class="btn btn-white" type="button">{% trans "Close" %}</button>
|
||||
{% endblock %}
|
|
@ -130,7 +130,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if user.is_superuser %}
|
||||
{% if user.is_superuser or user.is_org_admin %}
|
||||
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">
|
||||
{# <link href="https://cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css" rel="stylesheet">#}
|
||||
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
|
||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||
<style type="text/css">
|
||||
|
@ -27,6 +28,10 @@
|
|||
list-style: none;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
.dataTables_wrapper .dataTables_processing {
|
||||
opacity: .9;
|
||||
border: none;
|
||||
}
|
||||
div#rMenu li{
|
||||
margin: 1px 0;
|
||||
cursor: pointer;
|
||||
|
@ -161,16 +166,6 @@ function initTable() {
|
|||
}
|
||||
}},
|
||||
|
||||
{#{targets: 5, createdCell: function (td, cellData) {#}
|
||||
{# if (cellData === 'Unknown'){#}
|
||||
{# $(td).html('<i class="fa fa-circle text-warning"></i>')#}
|
||||
{# } else if (!cellData) {#}
|
||||
{# $(td).html('<i class="fa fa-circle text-danger"></i>')#}
|
||||
{# } else {#}
|
||||
{# $(td).html('<i class="fa fa-circle text-navy"></i>')#}
|
||||
{# }#}
|
||||
{# }},#}
|
||||
|
||||
{targets: 5, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:asset-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
|
@ -178,13 +173,6 @@ function initTable() {
|
|||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-assets:asset-list" %}',
|
||||
|
||||
{#columns: [#}
|
||||
{# {data: "id"}, {data: "hostname" }, {data: "ip" },#}
|
||||
{# {data: "cpu_cores"}, {data: "is_active", orderable: false },#}
|
||||
{# {data: "is_connective", orderable: false}, {data: "id", orderable: false }#}
|
||||
{#],#}
|
||||
|
||||
columns: [
|
||||
{data: "id"}, {data: "hostname" }, {data: "ip" },
|
||||
{data: "cpu_cores"}, {data: "is_active", orderable: false },
|
||||
|
@ -202,17 +190,17 @@ function addTreeNode() {
|
|||
if (!parentNode){
|
||||
return
|
||||
}
|
||||
var url = "{% url 'api-assets:node-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", parentNode.id );
|
||||
var url = "{% url 'api-assets:node-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", parentNode.node_id );
|
||||
$.post(url, {}, function (data, status){
|
||||
if (status === "success") {
|
||||
var newNode = {
|
||||
name: data["value"],
|
||||
id: data["id"],
|
||||
pId: parentNode.id
|
||||
pId: parentNode.node_id
|
||||
};
|
||||
newNode.checked = zTree.getSelectedNodes()[0].checked;
|
||||
zTree.addNodes(parentNode, 0, newNode);
|
||||
var node = zTree.getNodeByParam('id', newNode.id, parentNode)
|
||||
var node = zTree.getNodeByParam('id', newNode.node_id, parentNode);
|
||||
zTree.editName(node);
|
||||
} else {
|
||||
alert("{% trans 'Create node failed' %}")
|
||||
|
@ -231,7 +219,7 @@ function removeTreeNode() {
|
|||
} else if (current_node.assets_amount !== 0) {
|
||||
toastr.error("{% trans 'Have assets, cancel' %}");
|
||||
} else {
|
||||
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.node_id );
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: "DELETE",
|
||||
|
@ -291,7 +279,7 @@ function onBodyMouseDown(event){
|
|||
|
||||
|
||||
function onRename(event, treeId, treeNode, isCancel){
|
||||
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", treeNode.id);
|
||||
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", treeNode.node_id);
|
||||
var data = {"value": treeNode.name};
|
||||
if (isCancel){
|
||||
return
|
||||
|
@ -305,9 +293,9 @@ function onRename(event, treeId, treeNode, isCancel){
|
|||
|
||||
function onSelected(event, treeNode) {
|
||||
var url = asset_table.ajax.url();
|
||||
url = setUrlParam(url, "node_id", treeNode.id);
|
||||
url = setUrlParam(url, "node_id", treeNode.node_id);
|
||||
url = setUrlParam(url, "show_current_asset", getCookie('show_current_asset'));
|
||||
setCookie('node_selected', treeNode.id);
|
||||
setCookie('node_selected', treeNode.node_id);
|
||||
asset_table.ajax.url(url);
|
||||
asset_table.ajax.reload();
|
||||
}
|
||||
|
@ -324,7 +312,7 @@ function selectQueryNode() {
|
|||
node_id = cookie_node_id;
|
||||
}
|
||||
|
||||
node = zTree.getNodesByParam("id", node_id, null);
|
||||
node = zTree.getNodesByParam("node_id", node_id, null);
|
||||
if (node){
|
||||
zTree.selectNode(node[0]);
|
||||
}
|
||||
|
@ -341,11 +329,7 @@ function beforeDrop(treeId, treeNodes, targetNode, moveType) {
|
|||
});
|
||||
|
||||
var msg = "你想移动节点: `" + treeNodesNames.join(",") + "` 到 `" + targetNode.value + "` 下吗?";
|
||||
if (confirm(msg)){
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return confirm(msg);
|
||||
}
|
||||
|
||||
function onDrag(event, treeId, treeNodes) {
|
||||
|
@ -354,10 +338,10 @@ function onDrag(event, treeId, treeNodes) {
|
|||
function onDrop(event, treeId, treeNodes, targetNode, moveType) {
|
||||
var treeNodesIds = [];
|
||||
$.each(treeNodes, function (index, value) {
|
||||
treeNodesIds.push(value.id);
|
||||
treeNodesIds.push(value.node_id);
|
||||
});
|
||||
|
||||
var the_url = "{% url 'api-assets:node-add-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", targetNode.id);
|
||||
var the_url = "{% url 'api-assets:node-add-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", targetNode.node_id);
|
||||
var body = {nodes: treeNodesIds};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
|
@ -401,16 +385,21 @@ function initTree() {
|
|||
var zNodes = [];
|
||||
$.get("{% url 'api-assets:node-list' %}", function(data, status){
|
||||
$.each(data, function (index, value) {
|
||||
value["pId"] = value["parent"];
|
||||
if (value["key"] === "0") {
|
||||
value["open"] = true;
|
||||
}
|
||||
value["node_id"] = value["id"];
|
||||
value["id"] = value["tree_id"];
|
||||
if (value["tree_id"] !== value["tree_parent"]){
|
||||
value["pId"] = value["tree_parent"];
|
||||
} else {
|
||||
value["isParent"] = true;
|
||||
}
|
||||
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
|
||||
value['value'] = value['value'];
|
||||
});
|
||||
zNodes = data;
|
||||
$.fn.zTree.init($("#assetTree"), setting, zNodes);
|
||||
zTree = $.fn.zTree.getZTreeObj("assetTree");
|
||||
var root = zTree.getNodes()[0];
|
||||
zTree.expandNode(root);
|
||||
rMenu = $("#rMenu");
|
||||
selectQueryNode();
|
||||
});
|
||||
|
@ -462,7 +451,7 @@ $(document).ready(function(){
|
|||
$.ajax({
|
||||
url: "{% url "assets:asset-export" %}",
|
||||
method: 'POST',
|
||||
data: JSON.stringify({assets_id: assets, node_id: current_node.id}),
|
||||
data: JSON.stringify({assets_id: assets, node_id: current_node.node_id}),
|
||||
dataType: "json",
|
||||
success: function (data, textStatus) {
|
||||
window.open(data.redirect)
|
||||
|
@ -479,8 +468,8 @@ $(document).ready(function(){
|
|||
var current_node;
|
||||
if (nodes && nodes.length ===1 ){
|
||||
current_node = nodes[0];
|
||||
action = setUrlParam(action, 'node_id', current_node.id);
|
||||
{#action += "?node_id=" + current_node.id;#}
|
||||
action = setUrlParam(action, 'node_id', current_node.node_id);
|
||||
{#action += "?node_id=" + current_node.node_id;#}
|
||||
$form.attr("action", action)
|
||||
}
|
||||
$form.find('.help-block').remove();
|
||||
|
@ -506,7 +495,7 @@ $(document).ready(function(){
|
|||
var current_node;
|
||||
if (nodes && nodes.length ===1 ){
|
||||
current_node = nodes[0];
|
||||
url += "?node_id=" + current_node.id;
|
||||
url += "?node_id=" + current_node.node_id;
|
||||
}
|
||||
window.open(url, '_self');
|
||||
})
|
||||
|
@ -520,7 +509,7 @@ $(document).ready(function(){
|
|||
return null;
|
||||
}
|
||||
|
||||
var the_url = url.replace("{{ DEFAULT_PK }}", current_node.id);
|
||||
var the_url = url.replace("{{ DEFAULT_PK }}", current_node.node_id);
|
||||
function success(data) {
|
||||
rMenu.css({"visibility" : "hidden"});
|
||||
var task_id = data.task;
|
||||
|
@ -545,7 +534,7 @@ $(document).ready(function(){
|
|||
return null;
|
||||
}
|
||||
|
||||
var the_url = url.replace("{{ DEFAULT_PK }}", current_node.id);
|
||||
var the_url = url.replace("{{ DEFAULT_PK }}", current_node.node_id);
|
||||
function success(data) {
|
||||
rMenu.css({"visibility" : "hidden"});
|
||||
var task_id = data.task;
|
||||
|
@ -682,7 +671,7 @@ $(document).ready(function(){
|
|||
};
|
||||
|
||||
APIUpdateAttr({
|
||||
'url': '/api/assets/v1/nodes/' + current_node.id + '/assets/remove/',
|
||||
'url': '/api/assets/v1/nodes/' + current_node.node_id + '/assets/remove/',
|
||||
'method': 'PUT',
|
||||
'body': JSON.stringify(data),
|
||||
'success': success
|
||||
|
@ -719,9 +708,7 @@ $(document).ready(function(){
|
|||
return
|
||||
}
|
||||
|
||||
var data = {
|
||||
'assets': assets_selected
|
||||
};
|
||||
var data = {'assets': assets_selected};
|
||||
var success = function () {
|
||||
asset_table2.selected = [];
|
||||
asset_table2.ajax.reload()
|
||||
|
@ -729,9 +716,9 @@ $(document).ready(function(){
|
|||
|
||||
var url = '';
|
||||
if (update_node_action === "move") {
|
||||
url = "{% url 'api-assets:node-replace-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.id);
|
||||
url = "{% url 'api-assets:node-replace-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.node_id);
|
||||
} else {
|
||||
url = "{% url 'api-assets:node-add-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.id);
|
||||
url = "{% url 'api-assets:node-add-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.node_id);
|
||||
}
|
||||
|
||||
APIUpdateAttr({
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
{% block help_message %}
|
||||
<div class="alert alert-info help-message">
|
||||
网域功能是为了解决部分环境(如:混合云)无法直接连接而新增的功能,原理是通过网关服务器进行跳转登
|
||||
录。
|
||||
网域功能是为了解决部分环境(如:混合云)无法直接连接而新增的功能,原理是通过网关服务器进行跳转登录。<br>
|
||||
JMS => 网域网关 => 目标资产
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -55,11 +55,14 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include 'assets/_user_asset_detail_modal.html' %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var zTree, rMenu, asset_table;
|
||||
var zTree, asset_table;
|
||||
var inited = false;
|
||||
var url;
|
||||
function initTable() {
|
||||
|
@ -68,14 +71,15 @@ function initTable() {
|
|||
} else {
|
||||
inited = true;
|
||||
}
|
||||
console.log("init table")
|
||||
url = "{% url 'api-perms:my-assets' %}";
|
||||
var options = {
|
||||
ele: $('#user_assets_table'),
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
{% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %}
|
||||
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
var detail_btn = '<a class="asset_detail" asset-id="rowData_id" data-toggle="modal" data-target="#user_asset_detail_modal" tabindex="0">'+ cellData +'</a>'
|
||||
$(td).html(detail_btn.replace("rowData_id", rowData.id));
|
||||
}},
|
||||
{targets: 3, createdCell: function (td, cellData) {
|
||||
if (!cellData) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
|
@ -103,33 +107,13 @@ function initTable() {
|
|||
}
|
||||
|
||||
function onSelected(event, treeNode) {
|
||||
console.log("select");
|
||||
url = '{% url "api-perms:my-node-assets" node_id=DEFAULT_PK %}';
|
||||
url = url.replace("{{ DEFAULT_PK }}", treeNode.id);
|
||||
initTable();
|
||||
url = url.replace("{{ DEFAULT_PK }}", treeNode.node_id);
|
||||
setCookie('node_selected', treeNode.id);
|
||||
asset_table.ajax.url(url);
|
||||
asset_table.ajax.reload();
|
||||
}
|
||||
|
||||
function selectQueryNode() {
|
||||
var query_node_id = $.getUrlParam("node");
|
||||
var cookie_node_id = getCookie('node_selected');
|
||||
var node;
|
||||
var node_id;
|
||||
|
||||
if (query_node_id !== null) {
|
||||
node_id = query_node_id
|
||||
} else if (cookie_node_id !== null) {
|
||||
node_id = cookie_node_id;
|
||||
}
|
||||
|
||||
node = zTree.getNodesByParam("id", node_id, null);
|
||||
if (node){
|
||||
zTree.selectNode(node[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function initTree() {
|
||||
var setting = {
|
||||
view: {
|
||||
|
@ -149,23 +133,56 @@ function initTree() {
|
|||
var zNodes = [];
|
||||
$.get("{% url 'api-perms:my-nodes' %}", function(data, status){
|
||||
$.each(data, function (index, value) {
|
||||
value["pId"] = value["parent"];
|
||||
if (value["key"] === "0") {
|
||||
value["open"] = true;
|
||||
value["node_id"] = value["id"];
|
||||
value["id"] = value["tree_id"];
|
||||
if (value["tree_id"] !== value["tree_parent"]) {
|
||||
value["pId"] = value["tree_parent"];
|
||||
}
|
||||
value["name"] = value["value"]
|
||||
value["isParent"] = value["is_node"];
|
||||
value['name'] = value['value'];
|
||||
});
|
||||
zNodes = data;
|
||||
$.fn.zTree.init($("#assetTree"), setting, zNodes);
|
||||
zTree = $.fn.zTree.getZTreeObj("assetTree");
|
||||
rMenu = $("#rMenu");
|
||||
selectQueryNode();
|
||||
var root = zTree.getNodes()[0];
|
||||
zTree.expandNode(root);
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
initTree();
|
||||
initTable();
|
||||
})
|
||||
.on('click', '.asset_detail', function() {
|
||||
var data = asset_table.ajax.json();
|
||||
var asset_id = this.getAttribute("asset-id");
|
||||
var trs = '';
|
||||
var desc = {
|
||||
'hostname': "{% trans 'Hostname' %}",
|
||||
'ip': "{% trans 'IP' %}",
|
||||
'port': "{% trans 'Port' %}",
|
||||
'protocol': "{% trans 'Protocol' %}",
|
||||
'platform': "{% trans 'Platform' %}",
|
||||
'os': "{% trans 'OS' %}",
|
||||
'system_users_join': "{% trans 'System user' %}",
|
||||
'domain': "{% trans 'Domain' %}",
|
||||
'is_active': "{% trans 'Is active' %}",
|
||||
'comment': "{% trans 'Comment' %}"
|
||||
{#'date_joined': "{% trans 'Date joined' %}",#}
|
||||
};
|
||||
$.each(data, function(index, value){
|
||||
if(value.id === asset_id){
|
||||
for(var i in desc){
|
||||
trs += "<tr class='no-borders-tr'>\n" +
|
||||
"<td>"+ desc[i] + ":</td>"+
|
||||
"<td><b>"+ (value[i] === null?'':value[i]) + "</b></td>\n" +
|
||||
"</tr>";
|
||||
}
|
||||
}
|
||||
});
|
||||
$('#asset_detail_tbody').html(trs)
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
|
@ -1,5 +1,5 @@
|
|||
# coding:utf-8
|
||||
from django.conf.urls import url
|
||||
from django.urls import path
|
||||
from .. import api
|
||||
from rest_framework_bulk.routes import BulkRouter
|
||||
|
||||
|
@ -7,54 +7,54 @@ app_name = 'assets'
|
|||
|
||||
|
||||
router = BulkRouter()
|
||||
router.register(r'v1/assets', api.AssetViewSet, 'asset')
|
||||
router.register(r'v1/admin-user', api.AdminUserViewSet, 'admin-user')
|
||||
router.register(r'v1/system-user', api.SystemUserViewSet, 'system-user')
|
||||
router.register(r'v1/labels', api.LabelViewSet, 'label')
|
||||
router.register(r'v1/nodes', api.NodeViewSet, 'node')
|
||||
router.register(r'v1/domain', api.DomainViewSet, 'domain')
|
||||
router.register(r'v1/gateway', api.GatewayViewSet, 'gateway')
|
||||
router.register(r'assets', api.AssetViewSet, 'asset')
|
||||
router.register(r'admin-user', api.AdminUserViewSet, 'admin-user')
|
||||
router.register(r'system-user', api.SystemUserViewSet, 'system-user')
|
||||
router.register(r'labels', api.LabelViewSet, 'label')
|
||||
router.register(r'nodes', api.NodeViewSet, 'node')
|
||||
router.register(r'domain', api.DomainViewSet, 'domain')
|
||||
router.register(r'gateway', api.GatewayViewSet, 'gateway')
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^v1/assets-bulk/$', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'),
|
||||
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/auth-info/', api.SystemUserAuthInfoApi.as_view(),
|
||||
name='system-user-auth-info'),
|
||||
url(r'^v1/assets/(?P<pk>[0-9a-zA-Z\-]{36})/refresh/$',
|
||||
api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'),
|
||||
url(r'^v1/assets/(?P<pk>[0-9a-zA-Z\-]{36})/alive/$',
|
||||
api.AssetAdminUserTestApi.as_view(), name='asset-alive-test'),
|
||||
url(r'^v1/assets/(?P<pk>[0-9a-zA-Z\-]{36})/gateway/$',
|
||||
api.AssetGatewayApi.as_view(), name='asset-gateway'),
|
||||
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/$',
|
||||
api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'),
|
||||
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/auth/$',
|
||||
api.AdminUserAuthApi.as_view(), name='admin-user-auth'),
|
||||
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/connective/$',
|
||||
api.AdminUserTestConnectiveApi.as_view(), name='admin-user-connective'),
|
||||
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/push/$',
|
||||
api.SystemUserPushApi.as_view(), name='system-user-push'),
|
||||
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/connective/$',
|
||||
api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'),
|
||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/children/$',
|
||||
api.NodeChildrenApi.as_view(), name='node-children'),
|
||||
url(r'^v1/nodes/children/$', api.NodeChildrenApi.as_view(), name='node-children-2'),
|
||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/children/add/$',
|
||||
api.NodeAddChildrenApi.as_view(), name='node-add-children'),
|
||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$',
|
||||
api.NodeAssetsApi.as_view(), name='node-assets'),
|
||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/add/$',
|
||||
api.NodeAddAssetsApi.as_view(), name='node-add-assets'),
|
||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/replace/$',
|
||||
api.NodeReplaceAssetsApi.as_view(), name='node-replace-assets'),
|
||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/remove/$',
|
||||
api.NodeRemoveAssetsApi.as_view(), name='node-remove-assets'),
|
||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/refresh-hardware-info/$',
|
||||
api.RefreshNodeHardwareInfoApi.as_view(), name='node-refresh-hardware-info'),
|
||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/test-connective/$',
|
||||
api.TestNodeConnectiveApi.as_view(), name='node-test-connective'),
|
||||
path('assets-bulk/', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'),
|
||||
path('system-user/<uuid:pk>/auth-info/',
|
||||
api.SystemUserAuthInfoApi.as_view(), name='system-user-auth-info'),
|
||||
path('assets/<uuid:pk>/refresh/',
|
||||
api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'),
|
||||
path('assets/<uuid:pk>/alive/',
|
||||
api.AssetAdminUserTestApi.as_view(), name='asset-alive-test'),
|
||||
path('assets/<uuid:pk>/gateway/',
|
||||
api.AssetGatewayApi.as_view(), name='asset-gateway'),
|
||||
path('admin-user/<uuid:pk>/nodes/',
|
||||
api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'),
|
||||
path('admin-user/<uuid:pk>/auth/',
|
||||
api.AdminUserAuthApi.as_view(), name='admin-user-auth'),
|
||||
path('admin-user/<uuid:pk>/connective/',
|
||||
api.AdminUserTestConnectiveApi.as_view(), name='admin-user-connective'),
|
||||
path('system-user/<uuid:pk>/push/',
|
||||
api.SystemUserPushApi.as_view(), name='system-user-push'),
|
||||
path('system-user/<uuid:pk>/connective/',
|
||||
api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'),
|
||||
path('nodes/<uuid:pk>/children/',
|
||||
api.NodeChildrenApi.as_view(), name='node-children'),
|
||||
path('nodes/children/', api.NodeChildrenApi.as_view(), name='node-children-2'),
|
||||
path('nodes/<uuid:pk>/children/add/',
|
||||
api.NodeAddChildrenApi.as_view(), name='node-add-children'),
|
||||
path('nodes/<uuid:pk>/assets/',
|
||||
api.NodeAssetsApi.as_view(), name='node-assets'),
|
||||
path('nodes/<uuid:pk>/assets/add/',
|
||||
api.NodeAddAssetsApi.as_view(), name='node-add-assets'),
|
||||
path('nodes/<uuid:pk>/assets/replace/',
|
||||
api.NodeReplaceAssetsApi.as_view(), name='node-replace-assets'),
|
||||
path('nodes/<uuid:pk>/assets/remove/',
|
||||
api.NodeRemoveAssetsApi.as_view(), name='node-remove-assets'),
|
||||
path('nodes/<uuid:pk>/refresh-hardware-info/',
|
||||
api.RefreshNodeHardwareInfoApi.as_view(), name='node-refresh-hardware-info'),
|
||||
path('nodes/<uuid:pk>/test-connective/',
|
||||
api.TestNodeConnectiveApi.as_view(), name='node-test-connective'),
|
||||
|
||||
url(r'^v1/gateway/(?P<pk>[0-9a-zA-Z\-]{36})/test-connective/$',
|
||||
api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'),
|
||||
path('gateway/<uuid:pk>/test-connective/',
|
||||
api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'),
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
||||
|
|
|
@ -1,52 +1,52 @@
|
|||
# coding:utf-8
|
||||
from django.conf.urls import url
|
||||
from django.urls import path
|
||||
from .. import views
|
||||
|
||||
app_name = 'assets'
|
||||
|
||||
urlpatterns = [
|
||||
# Resource asset url
|
||||
url(r'^$', views.AssetListView.as_view(), name='asset-index'),
|
||||
url(r'^asset/$', views.AssetListView.as_view(), name='asset-list'),
|
||||
url(r'^asset/create/$', views.AssetCreateView.as_view(), name='asset-create'),
|
||||
url(r'^asset/export/$', views.AssetExportView.as_view(), name='asset-export'),
|
||||
url(r'^asset/import/$', views.BulkImportAssetView.as_view(), name='asset-import'),
|
||||
url(r'^asset/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.AssetDetailView.as_view(), name='asset-detail'),
|
||||
url(r'^asset/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.AssetUpdateView.as_view(), name='asset-update'),
|
||||
url(r'^asset/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.AssetDeleteView.as_view(), name='asset-delete'),
|
||||
url(r'^asset/update/$', views.AssetBulkUpdateView.as_view(), name='asset-bulk-update'),
|
||||
path('', views.AssetListView.as_view(), name='asset-index'),
|
||||
path('asset/', views.AssetListView.as_view(), name='asset-list'),
|
||||
path('asset/create/', views.AssetCreateView.as_view(), name='asset-create'),
|
||||
path('asset/export/', views.AssetExportView.as_view(), name='asset-export'),
|
||||
path('asset/import/', views.BulkImportAssetView.as_view(), name='asset-import'),
|
||||
path('asset/<uuid:pk>/', views.AssetDetailView.as_view(), name='asset-detail'),
|
||||
path('asset/<uuid:pk>/update/', views.AssetUpdateView.as_view(), name='asset-update'),
|
||||
path('asset/<uuid:pk>/delete/', views.AssetDeleteView.as_view(), name='asset-delete'),
|
||||
path('asset/update/', views.AssetBulkUpdateView.as_view(), name='asset-bulk-update'),
|
||||
|
||||
# User asset view
|
||||
url(r'^user-asset/$', views.UserAssetListView.as_view(), name='user-asset-list'),
|
||||
path('user-asset/', views.UserAssetListView.as_view(), name='user-asset-list'),
|
||||
|
||||
# Resource admin user url
|
||||
url(r'^admin-user/$', views.AdminUserListView.as_view(), name='admin-user-list'),
|
||||
url(r'^admin-user/create/$', views.AdminUserCreateView.as_view(), name='admin-user-create'),
|
||||
url(r'^admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.AdminUserDetailView.as_view(), name='admin-user-detail'),
|
||||
url(r'^admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.AdminUserUpdateView.as_view(), name='admin-user-update'),
|
||||
url(r'^admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.AdminUserDeleteView.as_view(), name='admin-user-delete'),
|
||||
url(r'^admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$', views.AdminUserAssetsView.as_view(), name='admin-user-assets'),
|
||||
path('admin-user/', views.AdminUserListView.as_view(), name='admin-user-list'),
|
||||
path('admin-user/create/', views.AdminUserCreateView.as_view(), name='admin-user-create'),
|
||||
path('admin-user/<uuid:pk>/', views.AdminUserDetailView.as_view(), name='admin-user-detail'),
|
||||
path('admin-user/<uuid:pk>/update/', views.AdminUserUpdateView.as_view(), name='admin-user-update'),
|
||||
path('admin-user/<uuid:pk>/delete/', views.AdminUserDeleteView.as_view(), name='admin-user-delete'),
|
||||
path('admin-user/<uuid:pk>/assets/', views.AdminUserAssetsView.as_view(), name='admin-user-assets'),
|
||||
|
||||
# Resource system user url
|
||||
url(r'^system-user/$', views.SystemUserListView.as_view(), name='system-user-list'),
|
||||
url(r'^system-user/create/$', views.SystemUserCreateView.as_view(), name='system-user-create'),
|
||||
url(r'^system-user/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.SystemUserDetailView.as_view(), name='system-user-detail'),
|
||||
url(r'^system-user/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.SystemUserUpdateView.as_view(), name='system-user-update'),
|
||||
url(r'^system-user/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.SystemUserDeleteView.as_view(), name='system-user-delete'),
|
||||
url(r'^system-user/(?P<pk>[0-9a-zA-Z\-]{36})/asset/$', views.SystemUserAssetView.as_view(), name='system-user-asset'),
|
||||
path('system-user/', views.SystemUserListView.as_view(), name='system-user-list'),
|
||||
path('system-user/create/', views.SystemUserCreateView.as_view(), name='system-user-create'),
|
||||
path('system-user/<uuid:pk>/', views.SystemUserDetailView.as_view(), name='system-user-detail'),
|
||||
path('system-user/<uuid:pk>/update/', views.SystemUserUpdateView.as_view(), name='system-user-update'),
|
||||
path('system-user/<uuid:pk>/delete/', views.SystemUserDeleteView.as_view(), name='system-user-delete'),
|
||||
path('system-user/<uuid:pk>/asset/', views.SystemUserAssetView.as_view(), name='system-user-asset'),
|
||||
|
||||
url(r'^label/$', views.LabelListView.as_view(), name='label-list'),
|
||||
url(r'^label/create/$', views.LabelCreateView.as_view(), name='label-create'),
|
||||
url(r'^label/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.LabelUpdateView.as_view(), name='label-update'),
|
||||
url(r'^label/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.LabelDeleteView.as_view(), name='label-delete'),
|
||||
path('label/', views.LabelListView.as_view(), name='label-list'),
|
||||
path('label/create/', views.LabelCreateView.as_view(), name='label-create'),
|
||||
path('label/<uuid:pk>/update/', views.LabelUpdateView.as_view(), name='label-update'),
|
||||
path('label/<uuid:pk>/delete/', views.LabelDeleteView.as_view(), name='label-delete'),
|
||||
|
||||
url(r'^domain/$', views.DomainListView.as_view(), name='domain-list'),
|
||||
url(r'^domain/create/$', views.DomainCreateView.as_view(), name='domain-create'),
|
||||
url(r'^domain/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.DomainDetailView.as_view(), name='domain-detail'),
|
||||
url(r'^domain/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.DomainUpdateView.as_view(), name='domain-update'),
|
||||
url(r'^domain/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.DomainDeleteView.as_view(), name='domain-delete'),
|
||||
url(r'^domain/(?P<pk>[0-9a-zA-Z\-]{36})/gateway/$', views.DomainGatewayListView.as_view(), name='domain-gateway-list'),
|
||||
path('domain/', views.DomainListView.as_view(), name='domain-list'),
|
||||
path('domain/create/', views.DomainCreateView.as_view(), name='domain-create'),
|
||||
path('domain/<uuid:pk>/', views.DomainDetailView.as_view(), name='domain-detail'),
|
||||
path('domain/<uuid:pk>/update/', views.DomainUpdateView.as_view(), name='domain-update'),
|
||||
path('domain/<uuid:pk>/delete/', views.DomainDeleteView.as_view(), name='domain-delete'),
|
||||
path('domain/<uuid:pk>/gateway/', views.DomainGatewayListView.as_view(), name='domain-gateway-list'),
|
||||
|
||||
url(r'^domain/(?P<pk>[0-9a-zA-Z\-]{36})/gateway/create/$', views.DomainGatewayCreateView.as_view(), name='domain-gateway-create'),
|
||||
url(r'^domain/gateway/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.DomainGatewayUpdateView.as_view(), name='domain-gateway-update'),
|
||||
path('domain/<uuid:pk>/gateway/create/', views.DomainGatewayCreateView.as_view(), name='domain-gateway-create'),
|
||||
path('domain/gateway/<uuid:pk>/update/', views.DomainGatewayUpdateView.as_view(), name='domain-gateway-update'),
|
||||
]
|
||||
|
|
|
@ -11,7 +11,7 @@ from django.views.generic.detail import DetailView, SingleObjectMixin
|
|||
from common.const import create_success_msg, update_success_msg
|
||||
from .. import forms
|
||||
from ..models import AdminUser, Node
|
||||
from ..hands import AdminUserRequiredMixin
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
|
||||
__all__ = [
|
||||
'AdminUserCreateView', 'AdminUserDetailView',
|
||||
|
|
|
@ -29,7 +29,7 @@ from common.utils import get_object_or_none, get_logger, is_uuid
|
|||
from common.const import create_success_msg, update_success_msg
|
||||
from .. import forms
|
||||
from ..models import Asset, AdminUser, SystemUser, Label, Node, Domain
|
||||
from ..hands import AdminUserRequiredMixin
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
|
||||
|
||||
__all__ = [
|
||||
|
@ -186,7 +186,7 @@ class AssetDeleteView(AdminUserRequiredMixin, DeleteView):
|
|||
success_url = reverse_lazy('assets:asset-list')
|
||||
|
||||
|
||||
class AssetDetailView(DetailView):
|
||||
class AssetDetailView(LoginRequiredMixin, DetailView):
|
||||
model = Asset
|
||||
context_object_name = 'asset'
|
||||
template_name = 'assets/asset_detail.html'
|
||||
|
@ -203,7 +203,7 @@ class AssetDetailView(DetailView):
|
|||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class AssetExportView(View):
|
||||
class AssetExportView(LoginRequiredMixin, View):
|
||||
def get(self, request):
|
||||
spm = request.GET.get('spm', '')
|
||||
assets_id_default = [Asset.objects.first().id] if Asset.objects.first() else []
|
||||
|
@ -211,7 +211,7 @@ class AssetExportView(View):
|
|||
fields = [
|
||||
field for field in Asset._meta.fields
|
||||
if field.name not in [
|
||||
'date_created'
|
||||
'date_created', 'org_id'
|
||||
]
|
||||
]
|
||||
filename = 'assets-{}.csv'.format(
|
||||
|
|
|
@ -7,7 +7,7 @@ from django.views.generic.detail import SingleObjectMixin
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.urls import reverse_lazy, reverse
|
||||
|
||||
from common.mixins import AdminUserRequiredMixin
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from common.utils import get_object_or_none
|
||||
from ..models import Domain, Gateway
|
||||
|
|
|
@ -6,7 +6,7 @@ from django.views.generic import TemplateView, CreateView, \
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
from common.mixins import AdminUserRequiredMixin
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from ..models import Label
|
||||
from ..forms import LabelForm
|
||||
|
|
|
@ -10,7 +10,7 @@ from django.views.generic.detail import DetailView
|
|||
from common.const import create_success_msg, update_success_msg
|
||||
from ..forms import SystemUserForm
|
||||
from ..models import SystemUser, Node
|
||||
from ..hands import AdminUserRequiredMixin
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
|
||||
|
||||
__all__ = [
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
from rest_framework import viewsets
|
||||
|
||||
from common.permissions import IsSuperUserOrAppUser
|
||||
from common.permissions import IsOrgAdminOrAppUser
|
||||
from .models import FTPLog
|
||||
from .serializers import FTPLogSerializer
|
||||
|
||||
|
@ -11,4 +11,4 @@ from .serializers import FTPLogSerializer
|
|||
class FTPLogViewSet(viewsets.ModelViewSet):
|
||||
queryset = FTPLog.objects.all()
|
||||
serializer_class = FTPLogSerializer
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
|
|
@ -3,8 +3,10 @@ import uuid
|
|||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins import OrgModelMixin
|
||||
|
||||
class FTPLog(models.Model):
|
||||
|
||||
class FTPLog(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
user = models.CharField(max_length=128, verbose_name=_('User'))
|
||||
remote_addr = models.CharField(max_length=15, verbose_name=_("Remote addr"), blank=True, null=True)
|
||||
|
|
|
@ -9,10 +9,9 @@ from .. import api
|
|||
app_name = "audits"
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'v1/ftp-log', api.FTPLogViewSet, 'ftp-log')
|
||||
router.register(r'ftp-log', api.FTPLogViewSet, 'ftp-log')
|
||||
|
||||
urlpatterns = [
|
||||
# url(r'^v1/celery/task/(?P<pk>[0-9a-zA-Z\-]{36})/log/$', api.CeleryTaskLogApi.as_view(), name='celery-task-log'),
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from django.conf.urls import url
|
||||
from django.urls import path
|
||||
from .. import views
|
||||
|
||||
__all__ = ["urlpatterns"]
|
||||
|
@ -10,5 +9,5 @@ __all__ = ["urlpatterns"]
|
|||
app_name = "audits"
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^ftp-log/$', views.FTPLogListView.as_view(), name='ftp-log-list'),
|
||||
path('ftp-log/', views.FTPLogListView.as_view(), name='ftp-log-list'),
|
||||
]
|
||||
|
|
|
@ -2,7 +2,8 @@ from django.conf import settings
|
|||
from django.views.generic import ListView
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from common.mixins import AdminUserRequiredMixin, DatetimeSearchMixin
|
||||
from common.mixins import DatetimeSearchMixin
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
|
||||
from .models import FTPLog
|
||||
|
||||
|
|
|
@ -8,12 +8,12 @@ from django.core.mail import get_connection, send_mail
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings
|
||||
|
||||
from .permissions import IsSuperUser
|
||||
from .permissions import IsOrgAdmin
|
||||
from .serializers import MailTestSerializer, LDAPTestSerializer
|
||||
|
||||
|
||||
class MailTestingAPI(APIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = MailTestSerializer
|
||||
success_message = _("Test mail sent to {}, please check")
|
||||
|
||||
|
@ -37,7 +37,7 @@ class MailTestingAPI(APIView):
|
|||
|
||||
|
||||
class LDAPTestingAPI(APIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = LDAPTestSerializer
|
||||
success_message = _("Test ldap success")
|
||||
|
||||
|
@ -86,7 +86,18 @@ class LDAPTestingAPI(APIView):
|
|||
|
||||
class DjangoSettingsAPI(APIView):
|
||||
def get(self, request):
|
||||
return Response('Danger, Close now')
|
||||
if not settings.DEBUG:
|
||||
return Response("Not in debug mode")
|
||||
|
||||
data = {}
|
||||
for k, v in settings.__dict__.items():
|
||||
if k and k.isupper():
|
||||
try:
|
||||
json.dumps(v)
|
||||
data[k] = v
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
data[k] = str(v)
|
||||
return Response(data)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ class EncryptMixin:
|
|||
def get_prep_value(self, value):
|
||||
if value is None:
|
||||
return value
|
||||
return signer.sign(value).decode('utf-8')
|
||||
return signer.sign(value)
|
||||
|
||||
|
||||
class EncryptTextField(EncryptMixin, models.TextField):
|
||||
|
|
|
@ -4,7 +4,6 @@ from django.db import models
|
|||
from django.http import JsonResponse
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||
|
||||
|
||||
class NoDeleteQuerySet(models.query.QuerySet):
|
||||
|
@ -119,11 +118,4 @@ class DatetimeSearchMixin:
|
|||
return super().get(request, *args, **kwargs)
|
||||
|
||||
|
||||
class AdminUserRequiredMixin(UserPassesTestMixin):
|
||||
def test_func(self):
|
||||
if not self.request.user.is_authenticated:
|
||||
return False
|
||||
elif not self.request.user.is_superuser:
|
||||
self.raise_exception = True
|
||||
return False
|
||||
return True
|
||||
|
||||
|
|
|
@ -2,6 +2,11 @@
|
|||
#
|
||||
|
||||
from rest_framework import permissions
|
||||
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||
from django.shortcuts import redirect
|
||||
from django.http.response import HttpResponseForbidden
|
||||
|
||||
from orgs.utils import current_org
|
||||
|
||||
|
||||
class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission):
|
||||
|
@ -21,28 +26,40 @@ class IsAppUser(IsValidUser):
|
|||
|
||||
|
||||
class IsSuperUser(IsValidUser):
|
||||
"""Allows access only to superuser"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
return super(IsSuperUser, self).has_permission(request, view) \
|
||||
and request.user.is_superuser
|
||||
and request.user.is_superuser
|
||||
|
||||
|
||||
class IsSuperUserOrAppUser(IsValidUser):
|
||||
"""Allows access between superuser and app user"""
|
||||
|
||||
class IsSuperUserOrAppUser(IsSuperUser):
|
||||
def has_permission(self, request, view):
|
||||
return super(IsSuperUserOrAppUser, self).has_permission(request, view) \
|
||||
and (request.user.is_superuser or request.user.is_app)
|
||||
|
||||
|
||||
class IsSuperUserOrAppUserOrUserReadonly(IsSuperUserOrAppUser):
|
||||
class IsOrgAdmin(IsValidUser):
|
||||
"""Allows access only to superuser"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
return super(IsOrgAdmin, self).has_permission(request, view) \
|
||||
and current_org.can_admin_by(request.user)
|
||||
|
||||
|
||||
class IsOrgAdminOrAppUser(IsValidUser):
|
||||
"""Allows access between superuser and app user"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
return super(IsOrgAdminOrAppUser, self).has_permission(request, view) \
|
||||
and (current_org.can_admin_by(request.user) or request.user.is_app)
|
||||
|
||||
|
||||
class IsOrgAdminOrAppUserOrUserReadonly(IsOrgAdminOrAppUser):
|
||||
def has_permission(self, request, view):
|
||||
if IsValidUser.has_permission(self, request, view) \
|
||||
and request.method in permissions.SAFE_METHODS:
|
||||
return True
|
||||
else:
|
||||
return IsSuperUserOrAppUser.has_permission(self, request, view)
|
||||
return IsOrgAdminOrAppUser.has_permission(self, request, view)
|
||||
|
||||
|
||||
class IsCurrentUserOrReadOnly(permissions.BasePermission):
|
||||
|
@ -50,3 +67,31 @@ class IsCurrentUserOrReadOnly(permissions.BasePermission):
|
|||
if request.method in permissions.SAFE_METHODS:
|
||||
return True
|
||||
return obj == request.user
|
||||
|
||||
|
||||
class AdminUserRequiredMixin(UserPassesTestMixin):
|
||||
def test_func(self):
|
||||
if not self.request.user.is_authenticated:
|
||||
return False
|
||||
elif not current_org.can_admin_by(self.request.user):
|
||||
self.raise_exception = True
|
||||
return False
|
||||
return True
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
print("Current org: {}".format(current_org))
|
||||
if not request.user.is_authenticated:
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
if not current_org:
|
||||
return redirect('orgs:switch-a-org')
|
||||
|
||||
if not current_org.can_admin_by(request.user):
|
||||
print("{} cannot admin {}".format(request.user, current_org))
|
||||
if request.user.is_org_admin:
|
||||
print("Is org admin")
|
||||
return redirect('orgs:switch-a-org')
|
||||
return HttpResponseForbidden()
|
||||
else:
|
||||
print(current_org.can_admin_by(request.user))
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from django.conf.urls import url
|
||||
from django.urls import path
|
||||
|
||||
from .. import api
|
||||
|
||||
app_name = 'common'
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^v1/mail/testing/$', api.MailTestingAPI.as_view(), name='mail-testing'),
|
||||
url(r'^v1/ldap/testing/$', api.LDAPTestingAPI.as_view(), name='ldap-testing'),
|
||||
url(r'^v1/django-settings/$', api.DjangoSettingsAPI.as_view(), name='django-settings'),
|
||||
path('mail/testing/', api.MailTestingAPI.as_view(), name='mail-testing'),
|
||||
path('ldap/testing/', api.LDAPTestingAPI.as_view(), name='ldap-testing'),
|
||||
# path('django-settings/', api.DjangoSettingsAPI.as_view(), name='django-settings'),
|
||||
]
|
||||
|
|
|
@ -17,6 +17,7 @@ import threading
|
|||
from io import StringIO
|
||||
import uuid
|
||||
from functools import wraps
|
||||
import copy
|
||||
|
||||
import paramiko
|
||||
import sshpubkeys
|
||||
|
@ -67,10 +68,8 @@ class Signer(metaclass=Singleton):
|
|||
self.secret_key = secret_key
|
||||
|
||||
def sign(self, value):
|
||||
if isinstance(value, bytes):
|
||||
value = value.decode("utf-8")
|
||||
s = JSONWebSignatureSerializer(self.secret_key)
|
||||
return s.dumps(value)
|
||||
return s.dumps(value).decode()
|
||||
|
||||
def unsign(self, value):
|
||||
if value is None:
|
||||
|
@ -410,3 +409,122 @@ def with_cache(func):
|
|||
cache[key] = res
|
||||
return res
|
||||
return wrapper
|
||||
|
||||
|
||||
class LocalProxy(object):
|
||||
|
||||
"""
|
||||
Copy from werkzeug.local.LocalProxy
|
||||
"""
|
||||
__slots__ = ('__local', '__dict__', '__name__', '__wrapped__')
|
||||
|
||||
def __init__(self, local, name=None):
|
||||
object.__setattr__(self, '_LocalProxy__local', local)
|
||||
object.__setattr__(self, '__name__', name)
|
||||
if callable(local) and not hasattr(local, '__release_local__'):
|
||||
# "local" is a callable that is not an instance of Local or
|
||||
# LocalManager: mark it as a wrapped function.
|
||||
object.__setattr__(self, '__wrapped__', local)
|
||||
|
||||
def _get_current_object(self):
|
||||
"""Return the current object. This is useful if you want the real
|
||||
object behind the proxy at a time for performance reasons or because
|
||||
you want to pass the object into a different context.
|
||||
"""
|
||||
if not hasattr(self.__local, '__release_local__'):
|
||||
return self.__local()
|
||||
try:
|
||||
return getattr(self.__local, self.__name__)
|
||||
except AttributeError:
|
||||
raise RuntimeError('no object bound to %s' % self.__name__)
|
||||
|
||||
@property
|
||||
def __dict__(self):
|
||||
try:
|
||||
return self._get_current_object().__dict__
|
||||
except RuntimeError:
|
||||
raise AttributeError('__dict__')
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
obj = self._get_current_object()
|
||||
except RuntimeError:
|
||||
return '<%s unbound>' % self.__class__.__name__
|
||||
return repr(obj)
|
||||
|
||||
def __bool__(self):
|
||||
try:
|
||||
return bool(self._get_current_object())
|
||||
except RuntimeError:
|
||||
return False
|
||||
|
||||
def __dir__(self):
|
||||
try:
|
||||
return dir(self._get_current_object())
|
||||
except RuntimeError:
|
||||
return []
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name == '__members__':
|
||||
return dir(self._get_current_object())
|
||||
return getattr(self._get_current_object(), name)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._get_current_object()[key] = value
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self._get_current_object()[key]
|
||||
|
||||
__setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
|
||||
__delattr__ = lambda x, n: delattr(x._get_current_object(), n)
|
||||
__str__ = lambda x: str(x._get_current_object())
|
||||
__lt__ = lambda x, o: x._get_current_object() < o
|
||||
__le__ = lambda x, o: x._get_current_object() <= o
|
||||
__eq__ = lambda x, o: x._get_current_object() == o
|
||||
__ne__ = lambda x, o: x._get_current_object() != o
|
||||
__gt__ = lambda x, o: x._get_current_object() > o
|
||||
__ge__ = lambda x, o: x._get_current_object() >= o
|
||||
__cmp__ = lambda x, o: cmp(x._get_current_object(), o) # noqa
|
||||
__hash__ = lambda x: hash(x._get_current_object())
|
||||
__call__ = lambda x, *a, **kw: x._get_current_object()(*a, **kw)
|
||||
__len__ = lambda x: len(x._get_current_object())
|
||||
__getitem__ = lambda x, i: x._get_current_object()[i]
|
||||
__iter__ = lambda x: iter(x._get_current_object())
|
||||
__contains__ = lambda x, i: i in x._get_current_object()
|
||||
__add__ = lambda x, o: x._get_current_object() + o
|
||||
__sub__ = lambda x, o: x._get_current_object() - o
|
||||
__mul__ = lambda x, o: x._get_current_object() * o
|
||||
__floordiv__ = lambda x, o: x._get_current_object() // o
|
||||
__mod__ = lambda x, o: x._get_current_object() % o
|
||||
__divmod__ = lambda x, o: x._get_current_object().__divmod__(o)
|
||||
__pow__ = lambda x, o: x._get_current_object() ** o
|
||||
__lshift__ = lambda x, o: x._get_current_object() << o
|
||||
__rshift__ = lambda x, o: x._get_current_object() >> o
|
||||
__and__ = lambda x, o: x._get_current_object() & o
|
||||
__xor__ = lambda x, o: x._get_current_object() ^ o
|
||||
__or__ = lambda x, o: x._get_current_object() | o
|
||||
__div__ = lambda x, o: x._get_current_object().__div__(o)
|
||||
__truediv__ = lambda x, o: x._get_current_object().__truediv__(o)
|
||||
__neg__ = lambda x: -(x._get_current_object())
|
||||
__pos__ = lambda x: +(x._get_current_object())
|
||||
__abs__ = lambda x: abs(x._get_current_object())
|
||||
__invert__ = lambda x: ~(x._get_current_object())
|
||||
__complex__ = lambda x: complex(x._get_current_object())
|
||||
__int__ = lambda x: int(x._get_current_object())
|
||||
__float__ = lambda x: float(x._get_current_object())
|
||||
__oct__ = lambda x: oct(x._get_current_object())
|
||||
__hex__ = lambda x: hex(x._get_current_object())
|
||||
__index__ = lambda x: x._get_current_object().__index__()
|
||||
__coerce__ = lambda x, o: x._get_current_object().__coerce__(x, o)
|
||||
__enter__ = lambda x: x._get_current_object().__enter__()
|
||||
__exit__ = lambda x, *a, **kw: x._get_current_object().__exit__(*a, **kw)
|
||||
__radd__ = lambda x, o: o + x._get_current_object()
|
||||
__rsub__ = lambda x, o: o - x._get_current_object()
|
||||
__rmul__ = lambda x, o: o * x._get_current_object()
|
||||
__rdiv__ = lambda x, o: o / x._get_current_object()
|
||||
__rtruediv__ = __rdiv__
|
||||
__rfloordiv__ = lambda x, o: o // x._get_current_object()
|
||||
__rmod__ = lambda x, o: o % x._get_current_object()
|
||||
__rdivmod__ = lambda x, o: x._get_current_object().__rdivmod__(o)
|
||||
__copy__ = lambda x: copy.copy(x._get_current_object())
|
||||
__deepcopy__ = lambda x, memo: copy.deepcopy(x._get_current_object(), memo)
|
|
@ -1,14 +1,12 @@
|
|||
|
||||
from django.core.cache import cache
|
||||
from django.views.generic import TemplateView, View, DetailView
|
||||
from django.shortcuts import render, redirect, Http404, reverse
|
||||
from django.views.generic import TemplateView
|
||||
from django.shortcuts import render, redirect
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.conf import settings
|
||||
|
||||
from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \
|
||||
TerminalSettingForm, SecuritySettingForm
|
||||
from .mixins import AdminUserRequiredMixin
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from .signals import ldap_auth_enable
|
||||
|
||||
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -56,6 +56,7 @@ ALLOWED_HOSTS = CONFIG.ALLOWED_HOSTS or []
|
|||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'orgs.apps.OrgsConfig',
|
||||
'users.apps.UsersConfig',
|
||||
'assets.apps.AssetsConfig',
|
||||
'perms.apps.PermsConfig',
|
||||
|
@ -65,6 +66,7 @@ INSTALLED_APPS = [
|
|||
'audits.apps.AuditsConfig',
|
||||
'rest_framework',
|
||||
'rest_framework_swagger',
|
||||
'drf_yasg',
|
||||
'django_filters',
|
||||
'bootstrap3',
|
||||
'captcha',
|
||||
|
@ -76,6 +78,12 @@ INSTALLED_APPS = [
|
|||
'django.contrib.staticfiles',
|
||||
]
|
||||
|
||||
|
||||
XPACK_DIR = os.path.join(BASE_DIR, 'xpack')
|
||||
XPACK_ENABLED = os.path.isdir(XPACK_DIR)
|
||||
if XPACK_ENABLED:
|
||||
INSTALLED_APPS.append('xpack.apps.XpackConfig')
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
|
@ -87,14 +95,35 @@ MIDDLEWARE = [
|
|||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'jumpserver.middleware.TimezoneMiddleware',
|
||||
'jumpserver.middleware.DemoMiddleware',
|
||||
'orgs.middleware.OrgMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'jumpserver.urls'
|
||||
|
||||
|
||||
def get_xpack_context_processor():
|
||||
if XPACK_ENABLED:
|
||||
return ['xpack.context_processor.xpack_processor']
|
||||
return []
|
||||
|
||||
|
||||
def get_xpack_templates_dir():
|
||||
if XPACK_ENABLED:
|
||||
dirs = []
|
||||
from xpack.utils import find_enabled_plugins
|
||||
for i in find_enabled_plugins():
|
||||
template_dir = os.path.join(BASE_DIR, 'xpack', 'plugins', i, 'templates')
|
||||
if os.path.isdir(template_dir):
|
||||
dirs.append(template_dir)
|
||||
return dirs
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [os.path.join(BASE_DIR, 'templates'), ],
|
||||
'DIRS': [os.path.join(BASE_DIR, 'templates'), *get_xpack_templates_dir()],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
|
@ -107,6 +136,8 @@ TEMPLATES = [
|
|||
'django.template.context_processors.static',
|
||||
'django.template.context_processors.request',
|
||||
'django.template.context_processors.media',
|
||||
'orgs.context_processor.org_processor',
|
||||
*get_xpack_context_processor(),
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -227,13 +258,13 @@ LOGGING = {
|
|||
'level': LOG_LEVEL,
|
||||
},
|
||||
'django_auth_ldap': {
|
||||
'handlers': ['console', 'ansible_logs'],
|
||||
'handlers': ['console', 'file'],
|
||||
'level': "INFO",
|
||||
},
|
||||
# 'django.db': {
|
||||
# 'handlers': ['console', 'file'],
|
||||
# 'level': 'DEBUG'
|
||||
# }
|
||||
'django.db': {
|
||||
'handlers': ['console', 'file'],
|
||||
'level': 'DEBUG'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -288,9 +319,10 @@ REST_FRAMEWORK = {
|
|||
# Use Django's standard `django.contrib.auth` permissions,
|
||||
# or allow read-only access for unauthenticated users.
|
||||
'DEFAULT_PERMISSION_CLASSES': (
|
||||
'users.permissions.IsSuperUser',
|
||||
'common.permissions.IsOrgAdmin',
|
||||
),
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
'rest_framework.authentication.BasicAuthentication',
|
||||
'users.authentication.AccessKeyAuthentication',
|
||||
'users.authentication.AccessTokenAuthentication',
|
||||
'users.authentication.PrivateTokenAuthentication',
|
||||
|
@ -373,7 +405,7 @@ CACHES = {
|
|||
'password': CONFIG.REDIS_PASSWORD if CONFIG.REDIS_PASSWORD else '',
|
||||
'host': CONFIG.REDIS_HOST or '127.0.0.1',
|
||||
'port': CONFIG.REDIS_PORT or 6379,
|
||||
'db':CONFIG.REDIS_DB_CACHE or 4,
|
||||
'db': CONFIG.REDIS_DB_CACHE or 4,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -423,3 +455,12 @@ TOKEN_EXPIRATION = CONFIG.TOKEN_EXPIRATION or 3600
|
|||
DISPLAY_PER_PAGE = CONFIG.DISPLAY_PER_PAGE or 25
|
||||
DEFAULT_EXPIRED_YEARS = 70
|
||||
USER_GUIDE_URL = ""
|
||||
|
||||
|
||||
SWAGGER_SETTINGS = {
|
||||
'SECURITY_DEFINITIONS': {
|
||||
'basic': {
|
||||
'type': 'basic'
|
||||
}
|
||||
},
|
||||
}
|
|
@ -1,45 +1,102 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
from __future__ import unicode_literals
|
||||
import re
|
||||
import os
|
||||
|
||||
from django.conf.urls import url, include
|
||||
from django.urls import path, include, re_path
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
|
||||
from rest_framework.schemas import get_schema_view
|
||||
from rest_framework_swagger.renderers import SwaggerUIRenderer, OpenAPIRenderer
|
||||
from rest_framework.response import Response
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.http import HttpResponse
|
||||
from django.utils.encoding import iri_to_uri
|
||||
from rest_framework import permissions
|
||||
from drf_yasg.views import get_schema_view
|
||||
from drf_yasg import openapi
|
||||
|
||||
from .views import IndexView, LunaView
|
||||
|
||||
schema_view = get_schema_view(title='Users API', renderer_classes=[OpenAPIRenderer, SwaggerUIRenderer])
|
||||
urlpatterns = [
|
||||
url(r'^$', IndexView.as_view(), name='index'),
|
||||
url(r'^luna/', LunaView.as_view(), name='luna-error'),
|
||||
url(r'^users/', include('users.urls.views_urls', namespace='users')),
|
||||
url(r'^assets/', include('assets.urls.views_urls', namespace='assets')),
|
||||
url(r'^perms/', include('perms.urls.views_urls', namespace='perms')),
|
||||
url(r'^terminal/', include('terminal.urls.views_urls', namespace='terminal')),
|
||||
url(r'^ops/', include('ops.urls.view_urls', namespace='ops')),
|
||||
url(r'^audits/', include('audits.urls.view_urls', namespace='audits')),
|
||||
url(r'^settings/', include('common.urls.view_urls', namespace='settings')),
|
||||
url(r'^common/', include('common.urls.view_urls', namespace='common')),
|
||||
schema_view = get_schema_view(
|
||||
openapi.Info(
|
||||
title="Jumpserver API Docs",
|
||||
default_version='v1',
|
||||
description="Jumpserver Restful api docs",
|
||||
terms_of_service="https://www.jumpserver.org",
|
||||
contact=openapi.Contact(email="support@fit2cloud.com"),
|
||||
license=openapi.License(name="GPLv2 License"),
|
||||
),
|
||||
public=True,
|
||||
permission_classes=(permissions.AllowAny,),
|
||||
)
|
||||
api_url_pattern = re.compile(r'^/api/(?P<version>\w+)/(?P<app>\w+)/(?P<extra>.*)$')
|
||||
|
||||
# Api url view map
|
||||
url(r'^api/users/', include('users.urls.api_urls', namespace='api-users')),
|
||||
url(r'^api/assets/', include('assets.urls.api_urls', namespace='api-assets')),
|
||||
url(r'^api/perms/', include('perms.urls.api_urls', namespace='api-perms')),
|
||||
url(r'^api/terminal/', include('terminal.urls.api_urls', namespace='api-terminal')),
|
||||
url(r'^api/ops/', include('ops.urls.api_urls', namespace='api-ops')),
|
||||
url(r'^api/audits/', include('audits.urls.api_urls', namespace='api-audits')),
|
||||
url(r'^api/common/', include('common.urls.api_urls', namespace='api-common')),
|
||||
|
||||
# External apps url
|
||||
url(r'^captcha/', include('captcha.urls')),
|
||||
class HttpResponseTemporaryRedirect(HttpResponse):
|
||||
status_code = 307
|
||||
|
||||
def __init__(self, redirect_to):
|
||||
HttpResponse.__init__(self)
|
||||
self['Location'] = iri_to_uri(redirect_to)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def redirect_format_api(request, *args, **kwargs):
|
||||
_path, query = request.path, request.GET.urlencode()
|
||||
matched = api_url_pattern.match(_path)
|
||||
if matched:
|
||||
version, app, extra = matched.groups()
|
||||
_path = '/api/{app}/{version}/{extra}?{query}'.format(**{
|
||||
"app": app, "version": version, "extra": extra,
|
||||
"query": query
|
||||
})
|
||||
return HttpResponseTemporaryRedirect(_path)
|
||||
else:
|
||||
return Response({"msg": "Redirect url failed: {}".format(_path)}, status=404)
|
||||
|
||||
|
||||
v1_api_patterns = [
|
||||
path('users/v1/', include('users.urls.api_urls', namespace='api-users')),
|
||||
path('assets/v1/', include('assets.urls.api_urls', namespace='api-assets')),
|
||||
path('perms/v1/', include('perms.urls.api_urls', namespace='api-perms')),
|
||||
path('terminal/v1/', include('terminal.urls.api_urls', namespace='api-terminal')),
|
||||
path('ops/v1/', include('ops.urls.api_urls', namespace='api-ops')),
|
||||
path('audits/v1/', include('audits.urls.api_urls', namespace='api-audits')),
|
||||
path('orgs/v1/', include('orgs.urls.api_urls', namespace='api-orgs')),
|
||||
path('common/v1/', include('common.urls.api_urls', namespace='api-common')),
|
||||
]
|
||||
|
||||
app_view_patterns = [
|
||||
path('users/', include('users.urls.views_urls', namespace='users')),
|
||||
path('assets/', include('assets.urls.views_urls', namespace='assets')),
|
||||
path('perms/', include('perms.urls.views_urls', namespace='perms')),
|
||||
path('terminal/', include('terminal.urls.views_urls', namespace='terminal')),
|
||||
path('ops/', include('ops.urls.view_urls', namespace='ops')),
|
||||
path('audits/', include('audits.urls.view_urls', namespace='audits')),
|
||||
path('orgs/', include('orgs.urls.views_urls', namespace='orgs')),
|
||||
]
|
||||
|
||||
if settings.XPACK_ENABLED:
|
||||
app_view_patterns.append(path('xpack/', include('xpack.urls', namespace='xpack')))
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('', IndexView.as_view(), name='index'),
|
||||
path('luna/', LunaView.as_view(), name='luna-error'),
|
||||
path('settings/', include('common.urls.view_urls', namespace='settings')),
|
||||
path('common/', include('common.urls.view_urls', namespace='common')),
|
||||
path('api/v1/', redirect_format_api),
|
||||
path('api/', include(v1_api_patterns)),
|
||||
|
||||
# External apps url
|
||||
path('captcha/', include('captcha.urls')),
|
||||
]
|
||||
urlpatterns += app_view_patterns
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \
|
||||
+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += [
|
||||
url(r'^docs/', schema_view, name="docs"),
|
||||
re_path('swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=None), name='schema-json'),
|
||||
path('docs/', schema_view.with_ui('swagger', cache_timeout=None), name="docs"),
|
||||
path('redoc/', schema_view.with_ui('redoc', cache_timeout=None), name='redoc'),
|
||||
]
|
||||
|
|
|
@ -4,12 +4,13 @@ from django.http import HttpResponse
|
|||
from django.views.generic import TemplateView, View
|
||||
from django.utils import timezone
|
||||
from django.db.models import Count
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.shortcuts import redirect
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
|
||||
from users.models import User
|
||||
from assets.models import Asset
|
||||
from terminal.models import Session
|
||||
from orgs.utils import current_org
|
||||
|
||||
|
||||
class IndexView(LoginRequiredMixin, TemplateView):
|
||||
|
@ -20,14 +21,16 @@ class IndexView(LoginRequiredMixin, TemplateView):
|
|||
session_month_dates = []
|
||||
session_month_dates_archive = []
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if not request.user.is_superuser:
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_authenticated:
|
||||
return self.handle_no_permission()
|
||||
if not request.user.is_org_admin:
|
||||
return redirect('assets:user-asset-list')
|
||||
return super(IndexView, self).get(request, *args, **kwargs)
|
||||
return super(IndexView, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def get_user_count():
|
||||
return User.objects.filter(role__in=('Admin', 'User')).count()
|
||||
return current_org.get_org_users().count()
|
||||
|
||||
@staticmethod
|
||||
def get_asset_count():
|
||||
|
@ -49,7 +52,6 @@ class IndexView(LoginRequiredMixin, TemplateView):
|
|||
|
||||
def get_week_login_asset_count(self):
|
||||
return self.session_week.count()
|
||||
# return self.session_week.values('asset').distinct().count()
|
||||
|
||||
def get_month_day_metrics(self):
|
||||
month_str = [d.strftime('%m-%d') for d in self.session_month_dates] or ['0']
|
||||
|
@ -175,4 +177,7 @@ class LunaView(View):
|
|||
Luna是单独部署的一个程序,你需要部署luna,coco,配置nginx做url分发,
|
||||
如果你看到了这个页面,证明你访问的不是nginx监听的端口,祝你好运
|
||||
"""
|
||||
return HttpResponse(msg)
|
||||
return HttpResponse(msg)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ from django.utils.translation import ugettext as _
|
|||
from rest_framework import viewsets, generics
|
||||
from rest_framework.views import Response
|
||||
|
||||
from .hands import IsSuperUser
|
||||
from common.permissions import IsOrgAdmin
|
||||
from .models import Task, AdHoc, AdHocRunHistory, CeleryTask
|
||||
from .serializers import TaskSerializer, AdHocSerializer, \
|
||||
AdHocRunHistorySerializer
|
||||
|
@ -18,13 +18,15 @@ from .tasks import run_ansible_task
|
|||
class TaskViewSet(viewsets.ModelViewSet):
|
||||
queryset = Task.objects.all()
|
||||
serializer_class = TaskSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
label = None
|
||||
help_text = ''
|
||||
|
||||
|
||||
class TaskRun(generics.RetrieveAPIView):
|
||||
queryset = Task.objects.all()
|
||||
serializer_class = TaskViewSet
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
task = self.get_object()
|
||||
|
@ -35,7 +37,7 @@ class TaskRun(generics.RetrieveAPIView):
|
|||
class AdHocViewSet(viewsets.ModelViewSet):
|
||||
queryset = AdHoc.objects.all()
|
||||
serializer_class = AdHocSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def get_queryset(self):
|
||||
task_id = self.request.query_params.get('task')
|
||||
|
@ -48,7 +50,7 @@ class AdHocViewSet(viewsets.ModelViewSet):
|
|||
class AdHocRunHistorySet(viewsets.ModelViewSet):
|
||||
queryset = AdHocRunHistory.objects.all()
|
||||
serializer_class = AdHocRunHistorySerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def get_queryset(self):
|
||||
task_id = self.request.query_params.get('task')
|
||||
|
@ -65,7 +67,7 @@ class AdHocRunHistorySet(viewsets.ModelViewSet):
|
|||
|
||||
|
||||
class CeleryTaskLogApi(generics.RetrieveAPIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
buff_size = 1024 * 10
|
||||
end = False
|
||||
queryset = CeleryTask.objects.all()
|
||||
|
|
|
@ -7,5 +7,9 @@ class OpsConfig(AppConfig):
|
|||
name = 'ops'
|
||||
|
||||
def ready(self):
|
||||
from orgs.models import Organization
|
||||
from orgs.utils import set_current_org
|
||||
set_current_org(Organization.root())
|
||||
|
||||
super().ready()
|
||||
from .celery import signal_handler
|
||||
|
|
|
@ -1,4 +1,2 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
from users.permissions import IsSuperUser
|
||||
from users.utils import AdminUserRequiredMixin
|
|
@ -263,7 +263,8 @@ class AdHoc(models.Model):
|
|||
}
|
||||
:return:
|
||||
"""
|
||||
self._become = signer.sign(json.dumps(item)).decode('utf-8')
|
||||
# self._become = signer.sign(json.dumps(item)).decode('utf-8')
|
||||
self._become = signer.sign(json.dumps(item))
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf.urls import url
|
||||
from django.urls import path
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .. import api
|
||||
|
||||
|
@ -9,13 +9,13 @@ from .. import api
|
|||
app_name = "ops"
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'v1/tasks', api.TaskViewSet, 'task')
|
||||
router.register(r'v1/adhoc', api.AdHocViewSet, 'adhoc')
|
||||
router.register(r'v1/history', api.AdHocRunHistorySet, 'history')
|
||||
router.register(r'tasks', api.TaskViewSet, 'task')
|
||||
router.register(r'adhoc', api.AdHocViewSet, 'adhoc')
|
||||
router.register(r'history', api.AdHocRunHistorySet, 'history')
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^v1/tasks/(?P<pk>[0-9a-zA-Z\-]{36})/run/$', api.TaskRun.as_view(), name='task-run'),
|
||||
url(r'^v1/celery/task/(?P<pk>[0-9a-zA-Z\-]{36})/log/$', api.CeleryTaskLogApi.as_view(), name='celery-task-log'),
|
||||
path('tasks/<uuid:pk>/run/', api.TaskRun.as_view(), name='task-run'),
|
||||
path('celery/task/<uuid:pk>/log/', api.CeleryTaskLogApi.as_view(), name='celery-task-log'),
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
from __future__ import unicode_literals
|
||||
from django.urls import path
|
||||
|
||||
|
||||
from django.conf.urls import url
|
||||
from .. import views
|
||||
|
||||
__all__ = ["urlpatterns"]
|
||||
|
@ -10,13 +9,13 @@ __all__ = ["urlpatterns"]
|
|||
app_name = "ops"
|
||||
|
||||
urlpatterns = [
|
||||
# TResource Task url
|
||||
url(r'^task/$', views.TaskListView.as_view(), name='task-list'),
|
||||
url(r'^task/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.TaskDetailView.as_view(), name='task-detail'),
|
||||
url(r'^task/(?P<pk>[0-9a-zA-Z\-]{36})/adhoc/$', views.TaskAdhocView.as_view(), name='task-adhoc'),
|
||||
url(r'^task/(?P<pk>[0-9a-zA-Z\-]{36})/history/$', views.TaskHistoryView.as_view(), name='task-history'),
|
||||
url(r'^adhoc/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.AdHocDetailView.as_view(), name='adhoc-detail'),
|
||||
url(r'^adhoc/(?P<pk>[0-9a-zA-Z\-]{36})/history/$', views.AdHocHistoryView.as_view(), name='adhoc-history'),
|
||||
url(r'^adhoc/history/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.AdHocHistoryDetailView.as_view(), name='adhoc-history-detail'),
|
||||
url(r'^celery/task/(?P<pk>[0-9a-zA-Z\-]{36})/log/$', views.CeleryTaskLogView.as_view(), name='celery-task-log'),
|
||||
# Resource Task url
|
||||
path('task/', views.TaskListView.as_view(), name='task-list'),
|
||||
path('task/<uuid:pk>/', views.TaskDetailView.as_view(), name='task-detail'),
|
||||
path('task/<uuid:pk>/adhoc/', views.TaskAdhocView.as_view(), name='task-adhoc'),
|
||||
path('task/<uuid:pk>/history/', views.TaskHistoryView.as_view(), name='task-history'),
|
||||
path('adhoc/<uuid:pk>/', views.AdHocDetailView.as_view(), name='adhoc-detail'),
|
||||
path('adhoc/<uuid:pk>/history/', views.AdHocHistoryView.as_view(), name='adhoc-history'),
|
||||
path('adhoc/history/<uuid:pk>/', views.AdHocHistoryDetailView.as_view(), name='adhoc-history-detail'),
|
||||
path('celery/task/<uuid:pk>/log/', views.CeleryTaskLogView.as_view(), name='celery-task-log'),
|
||||
]
|
||||
|
|
|
@ -6,7 +6,7 @@ from django.views.generic import ListView, DetailView, TemplateView
|
|||
|
||||
from common.mixins import DatetimeSearchMixin
|
||||
from .models import Task, AdHoc, AdHocRunHistory, CeleryTask
|
||||
from .hands import AdminUserRequiredMixin
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
|
||||
|
||||
class TaskListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
|
@ -0,0 +1,14 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from rest_framework import viewsets
|
||||
|
||||
from common.permissions import IsSuperUserOrAppUser
|
||||
from .models import Organization
|
||||
from .serializers import OrgSerializer
|
||||
|
||||
|
||||
class OrgViewSet(viewsets.ModelViewSet):
|
||||
queryset = Organization.objects.all()
|
||||
serializer_class = OrgSerializer
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class OrgsConfig(AppConfig):
|
||||
name = 'orgs'
|
|
@ -0,0 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from .utils import current_org, get_current_org
|
||||
from .models import Organization
|
||||
|
||||
|
||||
def org_processor(request):
|
||||
context = {
|
||||
'ADMIN_ORGS': Organization.get_user_admin_orgs(request.user),
|
||||
'CURRENT_ORG': get_current_org(),
|
||||
'HAS_ORG_PERM': current_org.can_admin_by(request.user),
|
||||
}
|
||||
return context
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from .utils import get_org_from_request, set_current_org
|
||||
|
||||
|
||||
class OrgMiddleware:
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
org = get_org_from_request(request)
|
||||
request.current_org = org
|
||||
set_current_org(org)
|
||||
response = self.get_response(request)
|
||||
return response
|
|
@ -0,0 +1,104 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from threading import local
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import redirect
|
||||
import warnings
|
||||
from django.forms import ModelForm
|
||||
from django.http.response import HttpResponseForbidden
|
||||
|
||||
from common.utils import get_logger
|
||||
from .utils import current_org, set_current_org, set_to_root_org
|
||||
from .models import Organization
|
||||
|
||||
logger = get_logger(__file__)
|
||||
tl = local()
|
||||
|
||||
__all__ = [
|
||||
'OrgManager', 'OrgViewGenericMixin', 'OrgModelMixin', 'OrgModelForm',
|
||||
'RootOrgViewMixin',
|
||||
]
|
||||
|
||||
|
||||
class OrgManager(models.Manager):
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super(OrgManager, self).get_queryset()
|
||||
kwargs = {}
|
||||
if not hasattr(tl, 'times'):
|
||||
tl.times = 0
|
||||
# logger.debug("[{}]>>>>>>>>>> Get query set".format(tl.times))
|
||||
if not current_org:
|
||||
kwargs['id'] = None
|
||||
elif current_org.is_real():
|
||||
kwargs['org_id'] = current_org.id
|
||||
elif current_org.is_default():
|
||||
queryset = queryset.filter(Q(org_id="") | Q(org_id__isnull=True))
|
||||
queryset = queryset.filter(**kwargs)
|
||||
tl.times += 1
|
||||
return queryset
|
||||
|
||||
def all(self):
|
||||
if not current_org:
|
||||
msg = 'You can `objects.set_current_org(org).all()` then run it'
|
||||
warnings.warn(msg)
|
||||
return self
|
||||
else:
|
||||
return super(OrgManager, self).all()
|
||||
|
||||
def set_current_org(self, org):
|
||||
if isinstance(org, str):
|
||||
org = Organization.objects.get(name=org)
|
||||
set_current_org(org)
|
||||
return self
|
||||
|
||||
|
||||
class OrgModelMixin(models.Model):
|
||||
org_id = models.CharField(max_length=36, null=True, blank=True)
|
||||
objects = OrgManager()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if current_org and current_org.is_real():
|
||||
self.org_id = current_org.id
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class OrgViewGenericMixin:
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
print("Current org: {}".format(current_org))
|
||||
if not current_org:
|
||||
return redirect('orgs:switch-a-org')
|
||||
|
||||
if not current_org.can_admin_by(request.user):
|
||||
print("{} cannot admin {}".format(request.user, current_org))
|
||||
if request.user.is_org_admin:
|
||||
print("Is org admin")
|
||||
return redirect('orgs:switch-a-org')
|
||||
return HttpResponseForbidden()
|
||||
else:
|
||||
print(current_org.can_admin_by(request.user))
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
class RootOrgViewMixin:
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
set_to_root_org()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
class OrgModelForm(ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if 'initial' not in kwargs:
|
||||
return
|
||||
for name, field in self.fields.items():
|
||||
if not hasattr(field, 'queryset'):
|
||||
continue
|
||||
model = field.queryset.model
|
||||
field.queryset = model.objects.all()
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
import uuid
|
||||
|
||||
from django.db import models
|
||||
from django.core.cache import cache
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class Organization(models.Model):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, unique=True, verbose_name=_("Name"))
|
||||
users = models.ManyToManyField('users.User', related_name='orgs', blank=True)
|
||||
admins = models.ManyToManyField('users.User', related_name='admin_orgs', blank=True)
|
||||
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
|
||||
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
|
||||
|
||||
CACHE_PREFIX = 'JMS_ORG_{}'
|
||||
ROOT_ID = 'ROOT'
|
||||
DEFAULT_ID = 'DEFAULT'
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def set_to_cache(self):
|
||||
key = self.CACHE_PREFIX.format(self.id)
|
||||
cache.set(key, self, 3600)
|
||||
|
||||
def expire_cache(self):
|
||||
key = self.CACHE_PREFIX.format(self.id)
|
||||
cache.set(key, self, 0)
|
||||
|
||||
@classmethod
|
||||
def get_instance_from_cache(cls, oid):
|
||||
key = cls.CACHE_PREFIX.format(oid)
|
||||
return cache.get(key, None)
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls, oid, default=True):
|
||||
cached = cls.get_instance_from_cache(oid)
|
||||
if cached:
|
||||
return cached
|
||||
|
||||
if oid == cls.DEFAULT_ID:
|
||||
return cls.default()
|
||||
elif oid == cls.ROOT_ID:
|
||||
return cls.root()
|
||||
|
||||
try:
|
||||
org = cls.objects.get(id=oid)
|
||||
org.set_to_cache()
|
||||
except cls.DoesNotExist:
|
||||
org = cls.default() if default else None
|
||||
return org
|
||||
|
||||
def get_org_users(self):
|
||||
from users.models import User
|
||||
if self.is_default():
|
||||
users = User.objects.filter(orgs__isnull=True)
|
||||
else:
|
||||
users = self.users.all()
|
||||
users = users.exclude(role=User.ROLE_APP)
|
||||
return users
|
||||
|
||||
def get_org_admins(self):
|
||||
if self.is_real():
|
||||
return self.admins.all()
|
||||
return []
|
||||
|
||||
def can_admin_by(self, user):
|
||||
if user.is_superuser:
|
||||
return True
|
||||
if user in list(self.get_org_admins()):
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_real(self):
|
||||
return len(str(self.id)) == 36
|
||||
|
||||
@classmethod
|
||||
def get_user_admin_orgs(cls, user):
|
||||
admin_orgs = []
|
||||
if user.is_anonymous:
|
||||
return admin_orgs
|
||||
elif user.is_superuser:
|
||||
admin_orgs = list(cls.objects.all())
|
||||
admin_orgs.append(cls.default())
|
||||
elif user.is_org_admin:
|
||||
admin_orgs = user.admin_orgs.all()
|
||||
return admin_orgs
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
return cls(id=cls.DEFAULT_ID, name="Default")
|
||||
|
||||
@classmethod
|
||||
def root(cls):
|
||||
return cls(id=cls.ROOT_ID, name='Root')
|
||||
|
||||
def is_default(self):
|
||||
if self.id is self.DEFAULT_ID:
|
||||
return True
|
||||
else:
|
||||
return False
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from .models import Organization
|
||||
|
||||
|
||||
class OrgSerializer(ModelSerializer):
|
||||
class Meta:
|
||||
model = Organization
|
||||
fields = '__all__'
|
||||
read_only_fields = ['id', 'created_by', 'date_created']
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
|
@ -0,0 +1,2 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
|
@ -0,0 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .. import api
|
||||
|
||||
|
||||
app_name = 'orgs'
|
||||
router = DefaultRouter()
|
||||
router.register(r'orgs', api.OrgViewSet, 'org')
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
|
@ -0,0 +1,14 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.urls import path
|
||||
|
||||
from .. import views
|
||||
|
||||
app_name = 'orgs'
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('<str:pk>/switch/', views.SwitchOrgView.as_view(), name='org-switch'),
|
||||
path('switch-a-org/', views.SwitchToAOrgView.as_view(), name='switch-a-org')
|
||||
]
|
|
@ -0,0 +1,47 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from functools import partial
|
||||
|
||||
from common.utils import LocalProxy
|
||||
from .models import Organization
|
||||
|
||||
try:
|
||||
from threading import local
|
||||
except ImportError:
|
||||
from django.utils._threading_local import local
|
||||
|
||||
_thread_locals = local()
|
||||
|
||||
|
||||
def get_org_from_request(request):
|
||||
oid = request.session.get("oid")
|
||||
if not oid:
|
||||
oid = request.META.get("HTTP_X_JMS_ORG")
|
||||
org = Organization.get_instance(oid)
|
||||
return org
|
||||
|
||||
|
||||
def set_current_org(org):
|
||||
setattr(_thread_locals, 'current_org', org)
|
||||
|
||||
|
||||
def set_to_default_org():
|
||||
set_current_org(Organization.default())
|
||||
|
||||
|
||||
def set_to_root_org():
|
||||
set_current_org(Organization.root())
|
||||
|
||||
|
||||
def _find(attr):
|
||||
return getattr(_thread_locals, attr, None)
|
||||
|
||||
|
||||
def get_current_org():
|
||||
return _find('current_org')
|
||||
|
||||
|
||||
current_org = LocalProxy(partial(_find, 'current_org'))
|
||||
current_user = LocalProxy(partial(_find, 'current_user'))
|
||||
current_request = LocalProxy(partial(_find, 'current_request'))
|
|
@ -0,0 +1,30 @@
|
|||
from django.shortcuts import redirect, reverse
|
||||
from django.http import HttpResponseForbidden
|
||||
|
||||
from django.views.generic import DetailView, View
|
||||
|
||||
from .models import Organization
|
||||
|
||||
|
||||
class SwitchOrgView(DetailView):
|
||||
model = Organization
|
||||
object = None
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
pk = kwargs.get('pk')
|
||||
self.object = Organization.get_instance(pk)
|
||||
request.session['oid'] = self.object.id.__str__()
|
||||
return redirect('index')
|
||||
|
||||
|
||||
class SwitchToAOrgView(View):
|
||||
def get(self, request, *args, **kwargs):
|
||||
admin_orgs = Organization.get_user_admin_orgs(request.user)
|
||||
if not admin_orgs:
|
||||
return HttpResponseForbidden()
|
||||
default_org = Organization.default()
|
||||
if default_org in admin_orgs:
|
||||
redirect_org = default_org
|
||||
else:
|
||||
redirect_org = admin_orgs[0]
|
||||
return redirect(reverse('orgs:org-switch', kwargs={'pk': redirect_org.id}))
|
|
@ -7,7 +7,8 @@ from rest_framework.generics import ListAPIView, get_object_or_404, RetrieveUpda
|
|||
from rest_framework import viewsets
|
||||
|
||||
from common.utils import set_or_append_attr_bulk, get_object_or_none
|
||||
from users.permissions import IsValidUser, IsSuperUser, IsSuperUserOrAppUser
|
||||
from common.permissions import IsValidUser, IsOrgAdmin, IsOrgAdminOrAppUser
|
||||
from orgs.mixins import RootOrgViewMixin
|
||||
from .utils import AssetPermissionUtil
|
||||
from .models import AssetPermission
|
||||
from .hands import AssetGrantedSerializer, User, UserGroup, Asset, Node, \
|
||||
|
@ -21,7 +22,7 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
|
|||
"""
|
||||
queryset = AssetPermission.objects.all()
|
||||
serializer_class = serializers.AssetPermissionCreateUpdateSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action in ("list", 'retrieve'):
|
||||
|
@ -54,11 +55,11 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
|
|||
return permissions
|
||||
|
||||
|
||||
class UserGrantedAssetsApi(ListAPIView):
|
||||
class UserGrantedAssetsApi(RootOrgViewMixin, ListAPIView):
|
||||
"""
|
||||
用户授权的所有资产
|
||||
"""
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = AssetGrantedSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
|
@ -83,8 +84,8 @@ class UserGrantedAssetsApi(ListAPIView):
|
|||
return super().get_permissions()
|
||||
|
||||
|
||||
class UserGrantedNodesApi(ListAPIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
class UserGrantedNodesApi(RootOrgViewMixin, ListAPIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = NodeSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
|
@ -103,8 +104,8 @@ class UserGrantedNodesApi(ListAPIView):
|
|||
return super().get_permissions()
|
||||
|
||||
|
||||
class UserGrantedNodesWithAssetsApi(ListAPIView):
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
class UserGrantedNodesWithAssetsApi(RootOrgViewMixin, ListAPIView):
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = NodeGrantedSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
|
@ -132,8 +133,8 @@ class UserGrantedNodesWithAssetsApi(ListAPIView):
|
|||
return super().get_permissions()
|
||||
|
||||
|
||||
class UserGrantedNodeAssetsApi(ListAPIView):
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
class UserGrantedNodeAssetsApi(RootOrgViewMixin, ListAPIView):
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = AssetGrantedSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
|
@ -159,7 +160,7 @@ class UserGrantedNodeAssetsApi(ListAPIView):
|
|||
|
||||
|
||||
class UserGroupGrantedAssetsApi(ListAPIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = AssetGrantedSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
|
@ -179,7 +180,7 @@ class UserGroupGrantedAssetsApi(ListAPIView):
|
|||
|
||||
|
||||
class UserGroupGrantedNodesApi(ListAPIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = NodeSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
|
@ -195,7 +196,7 @@ class UserGroupGrantedNodesApi(ListAPIView):
|
|||
|
||||
|
||||
class UserGroupGrantedNodesWithAssetsApi(ListAPIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = NodeGrantedSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
|
@ -218,7 +219,7 @@ class UserGroupGrantedNodesWithAssetsApi(ListAPIView):
|
|||
|
||||
|
||||
class UserGroupGrantedNodeAssetsApi(ListAPIView):
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = AssetGrantedSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
|
@ -235,8 +236,8 @@ class UserGroupGrantedNodeAssetsApi(ListAPIView):
|
|||
return assets
|
||||
|
||||
|
||||
class ValidateUserAssetPermissionView(APIView):
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
class ValidateUserAssetPermissionView(RootOrgViewMixin, APIView):
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
@staticmethod
|
||||
def get(request):
|
||||
|
@ -260,7 +261,7 @@ class AssetPermissionRemoveUserApi(RetrieveUpdateAPIView):
|
|||
"""
|
||||
将用户从授权中移除,Detail页面会调用
|
||||
"""
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.AssetPermissionUpdateUserSerializer
|
||||
queryset = AssetPermission.objects.all()
|
||||
|
||||
|
@ -277,7 +278,7 @@ class AssetPermissionRemoveUserApi(RetrieveUpdateAPIView):
|
|||
|
||||
|
||||
class AssetPermissionAddUserApi(RetrieveUpdateAPIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.AssetPermissionUpdateUserSerializer
|
||||
queryset = AssetPermission.objects.all()
|
||||
|
||||
|
@ -297,7 +298,7 @@ class AssetPermissionRemoveAssetApi(RetrieveUpdateAPIView):
|
|||
"""
|
||||
将用户从授权中移除,Detail页面会调用
|
||||
"""
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.AssetPermissionUpdateAssetSerializer
|
||||
queryset = AssetPermission.objects.all()
|
||||
|
||||
|
@ -314,7 +315,7 @@ class AssetPermissionRemoveAssetApi(RetrieveUpdateAPIView):
|
|||
|
||||
|
||||
class AssetPermissionAddAssetApi(RetrieveUpdateAPIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.AssetPermissionUpdateAssetSerializer
|
||||
queryset = AssetPermission.objects.all()
|
||||
|
||||
|
|
|
@ -4,11 +4,13 @@ from __future__ import absolute_import, unicode_literals
|
|||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins import OrgModelForm
|
||||
from orgs.utils import current_org
|
||||
from .hands import User
|
||||
from .models import AssetPermission
|
||||
|
||||
|
||||
class AssetPermissionForm(forms.ModelForm):
|
||||
class AssetPermissionForm(OrgModelForm):
|
||||
users = forms.ModelMultipleChoiceField(
|
||||
queryset=User.objects.exclude(role=User.ROLE_APP),
|
||||
label=_("User"),
|
||||
|
@ -21,10 +23,18 @@ class AssetPermissionForm(forms.ModelForm):
|
|||
required=False,
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if 'initial' not in kwargs:
|
||||
return
|
||||
users_field = self.fields.get('users')
|
||||
if hasattr(users_field, 'queryset'):
|
||||
users_field.queryset = current_org.get_org_users()
|
||||
|
||||
class Meta:
|
||||
model = AssetPermission
|
||||
exclude = (
|
||||
'id', 'date_created', 'created_by'
|
||||
'id', 'date_created', 'created_by', 'org_id'
|
||||
)
|
||||
widgets = {
|
||||
'users': forms.SelectMultiple(
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
#
|
||||
|
||||
from users.utils import AdminUserRequiredMixin
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from users.models import User, UserGroup
|
||||
from assets.models import Asset, SystemUser, Node
|
||||
from assets.serializers import AssetGrantedSerializer, NodeGrantedSerializer, NodeSerializer
|
||||
|
|
|
@ -6,6 +6,8 @@ from django.utils import timezone
|
|||
|
||||
from common.utils import date_expired_default, set_or_append_attr_bulk
|
||||
|
||||
from orgs.mixins import OrgModelMixin, OrgManager
|
||||
|
||||
|
||||
class AssetPermissionQuerySet(models.QuerySet):
|
||||
def active(self):
|
||||
|
@ -16,17 +18,14 @@ class AssetPermissionQuerySet(models.QuerySet):
|
|||
.filter(date_expired__gt=timezone.now())
|
||||
|
||||
|
||||
class AssetPermissionManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
return AssetPermissionQuerySet(self.model, using=self._db)
|
||||
|
||||
class AssetPermissionManager(OrgManager):
|
||||
def valid(self):
|
||||
return self.get_queryset().valid()
|
||||
|
||||
|
||||
class AssetPermission(models.Model):
|
||||
class AssetPermission(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||
users = models.ManyToManyField('users.User', related_name='asset_permissions', blank=True, verbose_name=_("User"))
|
||||
user_groups = models.ManyToManyField('users.UserGroup', related_name='asset_permissions', blank=True, verbose_name=_("User group"))
|
||||
assets = models.ManyToManyField('assets.Asset', related_name='granted_by_permissions', blank=True, verbose_name=_("Asset"))
|
||||
|
@ -39,7 +38,10 @@ class AssetPermission(models.Model):
|
|||
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created'))
|
||||
comment = models.TextField(verbose_name=_('Comment'), blank=True)
|
||||
|
||||
objects = AssetPermissionManager()
|
||||
objects = AssetPermissionManager.from_queryset(AssetPermissionQuerySet)()
|
||||
|
||||
class Meta:
|
||||
unique_together = [('org_id', 'name')]
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
@ -71,7 +73,7 @@ class AssetPermission(models.Model):
|
|||
return assets
|
||||
|
||||
|
||||
class NodePermission(models.Model):
|
||||
class NodePermission(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
node = models.ForeignKey('assets.Node', on_delete=models.CASCADE, verbose_name=_("Node"))
|
||||
user_group = models.ForeignKey('users.UserGroup', on_delete=models.CASCADE, verbose_name=_("User group"))
|
||||
|
|
|
@ -80,12 +80,12 @@ function onSelected(event, treeNode) {
|
|||
var url = table.ajax.url();
|
||||
if (treeNode.is_node) {
|
||||
url = setUrlParam(url, 'asset', "");
|
||||
url = setUrlParam(url, 'node', treeNode.id)
|
||||
url = setUrlParam(url, 'node', treeNode.node_id)
|
||||
} else {
|
||||
url = setUrlParam(url, 'node', "");
|
||||
url = setUrlParam(url, 'asset', treeNode.id)
|
||||
url = setUrlParam(url, 'asset', treeNode.node_id)
|
||||
}
|
||||
setCookie('node_selected', treeNode.id);
|
||||
setCookie('node_selected', treeNode.node_id);
|
||||
table.ajax.url(url);
|
||||
table.ajax.reload();
|
||||
}
|
||||
|
@ -111,10 +111,19 @@ function selectQueryNode() {
|
|||
|
||||
function filter(treeId, parentNode, childNodes) {
|
||||
$.each(childNodes, function (index, value) {
|
||||
value["pId"] = value["parent"];
|
||||
value["name"] = value["value"];
|
||||
value["isParent"] = value["is_node"];
|
||||
value["node_id"] = value["id"];
|
||||
value["id"] = value["tree_id"];
|
||||
if (value["tree_id"] !== value["tree_parent"]) {
|
||||
value["pId"] = value["tree_parent"];
|
||||
} else {
|
||||
value["isParent"] = true;
|
||||
}
|
||||
value['name'] = value['value'];
|
||||
value["iconSkin"] = value["is_node"] ? null : 'file';
|
||||
|
||||
{#value["pId"] = value["parent"];#}
|
||||
{#value["name"] = value["value"];#}
|
||||
value["isParent"] = value["is_node"];
|
||||
});
|
||||
return childNodes;
|
||||
}
|
||||
|
@ -227,7 +236,7 @@ function initTree() {
|
|||
async: {
|
||||
enable: true,
|
||||
url: "{% url 'api-assets:node-children-2' %}?assets=1&all=",
|
||||
autoParam:["id", "name=n", "level=lv"],
|
||||
autoParam:["node_id=id", "name=n", "level=lv"],
|
||||
dataFilter: filter,
|
||||
type: 'get'
|
||||
},
|
||||
|
@ -238,19 +247,22 @@ function initTree() {
|
|||
};
|
||||
|
||||
var zNodes = [];
|
||||
$.get("{% url 'api-assets:node-children-2' %}?assets=1&all=", function(data, status){
|
||||
$.get("{% url 'api-assets:node-children-2' %}?assets=1", function(data, status){
|
||||
$.each(data, function (index, value) {
|
||||
value["pId"] = value["parent"];
|
||||
value["name"] = value["value"];
|
||||
value["open"] = value["key"] === "0";
|
||||
value["node_id"] = value["id"];
|
||||
value["id"] = value["tree_id"];
|
||||
if (value["tree_id"] !== value["tree_parent"]) {
|
||||
value["pId"] = value["tree_parent"];
|
||||
}
|
||||
value["isParent"] = value["is_node"];
|
||||
value['name'] = value['value'];
|
||||
value["iconSkin"] = value["is_node"] ? null : 'file';
|
||||
});
|
||||
zNodes = data;
|
||||
{#$.fn.zTree.init($("#assetTree"), setting);#}
|
||||
$.fn.zTree.init($("#assetTree"), setting, zNodes);
|
||||
zTree = $.fn.zTree.getZTreeObj("assetTree");
|
||||
{#selectQueryNode();#}
|
||||
var root = zTree.getNodes()[0];
|
||||
zTree.expandNode(root);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -288,9 +300,9 @@ $(document).ready(function(){
|
|||
var _assets = [];
|
||||
$.each(nodes, function (id, node) {
|
||||
if (node.is_node) {
|
||||
_nodes.push(node.id)
|
||||
_nodes.push(node.node_id)
|
||||
} else {
|
||||
_assets.push(node.id)
|
||||
_assets.push(node.node_id)
|
||||
}
|
||||
});
|
||||
url += "?assets=" + _assets.join(",") + "&nodes=" + _nodes.join(",");
|
||||
|
|
|
@ -1,63 +1,62 @@
|
|||
# coding:utf-8
|
||||
|
||||
from django.conf.urls import url
|
||||
from django.urls import path
|
||||
from rest_framework import routers
|
||||
from .. import api
|
||||
|
||||
app_name = 'perms'
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
router.register('v1/asset-permissions', api.AssetPermissionViewSet, 'asset-permission')
|
||||
router.register('asset-permissions', api.AssetPermissionViewSet, 'asset-permission')
|
||||
|
||||
urlpatterns = [
|
||||
# 查询某个用户授权的资产和资产组
|
||||
url(r'^v1/user/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$',
|
||||
api.UserGrantedAssetsApi.as_view(), name='user-assets'),
|
||||
url(r'^v1/user/assets/$', api.UserGrantedAssetsApi.as_view(),
|
||||
name='my-assets'),
|
||||
url(r'^v1/user/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/$',
|
||||
api.UserGrantedNodesApi.as_view(), name='user-nodes'),
|
||||
url(r'^v1/user/nodes/$', api.UserGrantedNodesApi.as_view(),
|
||||
name='my-nodes'),
|
||||
url(
|
||||
r'^v1/user/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/(?P<node_id>[0-9a-zA-Z\-]{36})/assets/$',
|
||||
api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'),
|
||||
url(r'^v1/user/nodes/(?P<node_id>[0-9a-zA-Z\-]{36})/assets/$',
|
||||
api.UserGrantedNodeAssetsApi.as_view(), name='my-node-assets'),
|
||||
url(r'^v1/user/(?P<pk>[0-9a-zA-Z\-]{36})/nodes-assets/$',
|
||||
api.UserGrantedNodesWithAssetsApi.as_view(), name='user-nodes-assets'),
|
||||
url(r'^v1/user/nodes-assets/$', api.UserGrantedNodesWithAssetsApi.as_view(),
|
||||
name='my-nodes-assets'),
|
||||
path('user/<uuid:pk>/assets/',
|
||||
api.UserGrantedAssetsApi.as_view(), name='user-assets'),
|
||||
path('user/assets/', api.UserGrantedAssetsApi.as_view(),
|
||||
name='my-assets'),
|
||||
path('user/<uuid:pk>/nodes/',
|
||||
api.UserGrantedNodesApi.as_view(), name='user-nodes'),
|
||||
path('user/nodes/', api.UserGrantedNodesApi.as_view(),
|
||||
name='my-nodes'),
|
||||
path('user/<uuid:pk>/nodes/<uuid:node_id>/assets/',
|
||||
api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'),
|
||||
path('user/nodes/<uuid:node_id>/assets/',
|
||||
api.UserGrantedNodeAssetsApi.as_view(), name='my-node-assets'),
|
||||
path('user/<uuid:pk>/nodes-assets/',
|
||||
api.UserGrantedNodesWithAssetsApi.as_view(), name='user-nodes-assets'),
|
||||
path('user/nodes-assets/', api.UserGrantedNodesWithAssetsApi.as_view(),
|
||||
name='my-nodes-assets'),
|
||||
|
||||
# 查询某个用户组授权的资产和资产组
|
||||
url(r'^v1/user-group/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$',
|
||||
api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'),
|
||||
url(r'^v1/user-group/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/$',
|
||||
api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'),
|
||||
url(r'^v1/user-group/(?P<pk>[0-9a-zA-Z\-]{36})/nodes-assets/$',
|
||||
api.UserGroupGrantedNodesWithAssetsApi.as_view(),
|
||||
name='user-group-nodes-assets'),
|
||||
url(
|
||||
r'^v1/user-group/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/(?P<node_id>[0-9a-zA-Z\-]{36})/assets/$',
|
||||
api.UserGroupGrantedNodeAssetsApi.as_view(),
|
||||
name='user-group-node-assets'),
|
||||
path('user-group/<uuid:pk>/assets/',
|
||||
api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'),
|
||||
path('user-group/<uuid:pk>/nodes/',
|
||||
api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'),
|
||||
path('user-group/<uuid:pk>/nodes-assets/',
|
||||
api.UserGroupGrantedNodesWithAssetsApi.as_view(),
|
||||
name='user-group-nodes-assets'),
|
||||
path('user-group/<uuid:pk>/nodes/<uuid:node_id>/assets/',
|
||||
api.UserGroupGrantedNodeAssetsApi.as_view(),
|
||||
name='user-group-node-assets'),
|
||||
|
||||
# 用户和资产授权变更
|
||||
url(r'^v1/asset-permissions/(?P<pk>[0-9a-zA-Z\-]{36})/user/remove/$',
|
||||
api.AssetPermissionRemoveUserApi.as_view(),
|
||||
name='asset-permission-remove-user'),
|
||||
url(r'^v1/asset-permissions/(?P<pk>[0-9a-zA-Z\-]{36})/user/add/$',
|
||||
api.AssetPermissionAddUserApi.as_view(),
|
||||
name='asset-permission-add-user'),
|
||||
url(r'^v1/asset-permissions/(?P<pk>[0-9a-zA-Z\-]{36})/asset/remove/$',
|
||||
api.AssetPermissionRemoveAssetApi.as_view(),
|
||||
name='asset-permission-remove-asset'),
|
||||
url(r'^v1/asset-permissions/(?P<pk>[0-9a-zA-Z\-]{36})/asset/add/$',
|
||||
api.AssetPermissionAddAssetApi.as_view(),
|
||||
name='asset-permission-add-asset'),
|
||||
path('asset-permissions/<uuid:pk>/user/remove/',
|
||||
api.AssetPermissionRemoveUserApi.as_view(),
|
||||
name='asset-permission-remove-user'),
|
||||
path('asset-permissions/<uuid:pk>/user/add/',
|
||||
api.AssetPermissionAddUserApi.as_view(),
|
||||
name='asset-permission-add-user'),
|
||||
path('asset-permissions/<uuid:pk>/asset/remove/',
|
||||
api.AssetPermissionRemoveAssetApi.as_view(),
|
||||
name='asset-permission-remove-asset'),
|
||||
path('asset-permissions/<uuid:pk>/asset/add/',
|
||||
api.AssetPermissionAddAssetApi.as_view(),
|
||||
name='asset-permission-add-asset'),
|
||||
|
||||
# 验证用户是否有某个资产和系统用户的权限
|
||||
url(r'v1/asset-permission/user/validate/$', api.ValidateUserAssetPermissionView.as_view(), name='validate-user-asset-permission'),
|
||||
path('asset-permission/user/validate/', api.ValidateUserAssetPermissionView.as_view(),
|
||||
name='validate-user-asset-permission'),
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
# coding:utf-8
|
||||
|
||||
from django.conf.urls import url
|
||||
from django.urls import path
|
||||
from .. import views
|
||||
|
||||
app_name = 'perms'
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^asset-permission/$', views.AssetPermissionListView.as_view(), name='asset-permission-list'),
|
||||
url(r'^asset-permission/create/$', views.AssetPermissionCreateView.as_view(), name='asset-permission-create'),
|
||||
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.AssetPermissionUpdateView.as_view(), name='asset-permission-update'),
|
||||
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.AssetPermissionDetailView.as_view(),name='asset-permission-detail'),
|
||||
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.AssetPermissionDeleteView.as_view(), name='asset-permission-delete'),
|
||||
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/user/$', views.AssetPermissionUserView.as_view(), name='asset-permission-user-list'),
|
||||
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/asset/$', views.AssetPermissionAssetView.as_view(), name='asset-permission-asset-list'),
|
||||
path('asset-permission/', views.AssetPermissionListView.as_view(), name='asset-permission-list'),
|
||||
path('asset-permission/create/', views.AssetPermissionCreateView.as_view(), name='asset-permission-create'),
|
||||
path('asset-permission/<uuid:pk>/update/', views.AssetPermissionUpdateView.as_view(), name='asset-permission-update'),
|
||||
path('asset-permission/<uuid:pk>/', views.AssetPermissionDetailView.as_view(),name='asset-permission-detail'),
|
||||
path('asset-permission/<uuid:pk>/delete/', views.AssetPermissionDeleteView.as_view(), name='asset-permission-delete'),
|
||||
path('asset-permission/<uuid:pk>/user/', views.AssetPermissionUserView.as_view(), name='asset-permission-user-list'),
|
||||
path('asset-permission/<uuid:pk>/asset/', views.AssetPermissionAssetView.as_view(), name='asset-permission-asset-list'),
|
||||
]
|
||||
|
|
|
@ -13,7 +13,7 @@ logger = get_logger(__file__)
|
|||
|
||||
class Tree:
|
||||
def __init__(self):
|
||||
self.__all_nodes = list(Node.objects.all().prefetch_related('assets'))
|
||||
self.__all_nodes = Node.objects.all().prefetch_related('assets')
|
||||
self.__node_asset_map = defaultdict(set)
|
||||
self.nodes = defaultdict(dict)
|
||||
self.root = Node.root()
|
||||
|
@ -21,7 +21,7 @@ class Tree:
|
|||
|
||||
def init_node_asset_map(self):
|
||||
for node in self.__all_nodes:
|
||||
assets = node.get_assets().values_list('id', flat=True)
|
||||
assets = [a.id for a in node.assets.all()]
|
||||
for asset in assets:
|
||||
self.__node_asset_map[str(asset)].add(node)
|
||||
|
||||
|
|
|
@ -3,22 +3,20 @@
|
|||
from __future__ import unicode_literals, absolute_import
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic import ListView, CreateView, UpdateView, DetailView
|
||||
from django.views.generic import ListView, CreateView, UpdateView, DetailView, TemplateView
|
||||
from django.views.generic.edit import DeleteView, SingleObjectMixin
|
||||
from django.urls import reverse_lazy
|
||||
from django.conf import settings
|
||||
|
||||
from common.mixins import AdminUserRequiredMixin
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from orgs.utils import current_org
|
||||
from .hands import Node, Asset, SystemUser, User, UserGroup
|
||||
from .models import AssetPermission
|
||||
from .forms import AssetPermissionForm
|
||||
|
||||
|
||||
class AssetPermissionListView(AdminUserRequiredMixin, ListView):
|
||||
model = AssetPermission
|
||||
class AssetPermissionListView(AdminUserRequiredMixin, TemplateView):
|
||||
template_name = 'perms/asset_permission_list.html'
|
||||
paginate_by = settings.DISPLAY_PER_PAGE
|
||||
user = user_group = asset = node = system_user = q = ""
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -87,7 +85,6 @@ class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView):
|
|||
'system_users_remain': SystemUser.objects.exclude(
|
||||
granted_by_permissions=self.object
|
||||
),
|
||||
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
@ -116,11 +113,13 @@ class AssetPermissionUserView(AdminUserRequiredMixin,
|
|||
return queryset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
context = {
|
||||
'app': _('Perms'),
|
||||
'action': _('Asset permission user list'),
|
||||
'users_remain': User.objects.exclude(asset_permissions=self.object)
|
||||
.exclude(role=User.ROLE_APP),
|
||||
'users_remain': current_org.get_org_users().exclude(
|
||||
asset_permissions=self.object
|
||||
),
|
||||
'user_groups_remain': UserGroup.objects.exclude(
|
||||
asset_permissions=self.object
|
||||
)
|
||||
|
@ -138,7 +137,7 @@ class AssetPermissionAssetView(AdminUserRequiredMixin,
|
|||
object = None
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(queryset=AssetPermission.objects.all())
|
||||
self.object = self.get_object(queryset = AssetPermission.objects.all())
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
|
|
|
@ -335,13 +335,13 @@ div.dataTables_wrapper div.dataTables_filter {
|
|||
|
||||
.nav-header, body.mini-navbar .nav-header {
|
||||
padding: 0;
|
||||
background: #202c37;
|
||||
/*background: #202c37;*/
|
||||
}
|
||||
|
||||
.profile-element div:first-child {
|
||||
line-height: 60px;
|
||||
/*width: 70px;*/
|
||||
float: left;
|
||||
/*float: left;*/
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 5.7 KiB |
|
@ -179,7 +179,7 @@ function APIUpdateAttr(props) {
|
|||
toastr.error(fail_message);
|
||||
}
|
||||
if (typeof props.error === 'function') {
|
||||
return props.error(jqXHR.responseText);
|
||||
return props.error(jqXHR.responseText, jqXHR.status);
|
||||
}
|
||||
});
|
||||
// return true;
|
||||
|
@ -224,6 +224,47 @@ function objectDelete(obj, name, url, redirectTo) {
|
|||
});
|
||||
}
|
||||
|
||||
function orgDelete(obj, name, url, redirectTo){
|
||||
function doDelete() {
|
||||
var body = {};
|
||||
var success = function() {
|
||||
if (!redirectTo) {
|
||||
$(obj).parent().parent().remove();
|
||||
} else {
|
||||
window.location.href=redirectTo;
|
||||
}
|
||||
};
|
||||
var fail = function(responseText, status) {
|
||||
if (status === 400){
|
||||
swal("错误", "[ " + name + " ] 组织中包含未删除信息,请删除后重试", "error");
|
||||
}
|
||||
else if (status === 405){
|
||||
swal("错误", "请勿在组织 [ "+ name + " ] 下执行此操作,切换到其他组织后重试", "error");
|
||||
}
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: url,
|
||||
body: JSON.stringify(body),
|
||||
method: 'DELETE',
|
||||
success_message: "删除成功",
|
||||
success: success,
|
||||
error: fail
|
||||
});
|
||||
}
|
||||
swal({
|
||||
title: "请确保组织内的以下信息已删除",
|
||||
text: "用户列表、用户组、资产列表、网域列表、管理用户、系统用户、标签管理、资产授权规则",
|
||||
type: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonText: '取消',
|
||||
confirmButtonColor: "#ed5565",
|
||||
confirmButtonText: '确认',
|
||||
closeOnConfirm: true
|
||||
}, function () {
|
||||
doDelete();
|
||||
});
|
||||
}
|
||||
|
||||
$.fn.serializeObject = function()
|
||||
{
|
||||
var o = {};
|
||||
|
@ -250,6 +291,28 @@ function makeLabel(data) {
|
|||
var jumpserver = {};
|
||||
jumpserver.checked = false;
|
||||
jumpserver.selected = {};
|
||||
jumpserver.language = {
|
||||
processing: "加载中",
|
||||
search: "搜索",
|
||||
select: {
|
||||
rows: {
|
||||
_: "选中 %d 项",
|
||||
0: ""
|
||||
}
|
||||
},
|
||||
lengthMenu: "每页 _MENU_",
|
||||
info: "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项",
|
||||
infoFiltered: "",
|
||||
infoEmpty: "",
|
||||
zeroRecords: "没有匹配项",
|
||||
emptyTable: "没有记录",
|
||||
paginate: {
|
||||
first: "«",
|
||||
previous: "‹",
|
||||
next: "›",
|
||||
last: "»"
|
||||
}
|
||||
};
|
||||
jumpserver.initDataTable = function (options) {
|
||||
// options = {
|
||||
// ele *: $('#dataTable_id'),
|
||||
|
@ -293,21 +356,7 @@ jumpserver.initDataTable = function (options) {
|
|||
},
|
||||
columns: options.columns || [],
|
||||
select: options.select || select,
|
||||
language: {
|
||||
search: "搜索",
|
||||
lengthMenu: "每页 _MENU_",
|
||||
info: "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项",
|
||||
infoFiltered: "",
|
||||
infoEmpty: "",
|
||||
zeroRecords: "没有匹配项",
|
||||
emptyTable: "没有记录",
|
||||
paginate: {
|
||||
first: "«",
|
||||
previous: "‹",
|
||||
next: "›",
|
||||
last: "»"
|
||||
}
|
||||
},
|
||||
language: jumpserver.language,
|
||||
lengthMenu: [[10, 15, 25, 50, -1], [10, 15, 25, 50, "All"]]
|
||||
});
|
||||
table.on('select', function(e, dt, type, indexes) {
|
||||
|
@ -343,6 +392,16 @@ jumpserver.initDataTable = function (options) {
|
|||
return table;
|
||||
};
|
||||
|
||||
jumpserver.initStaticTable = function (selector) {
|
||||
$(selector).DataTable({
|
||||
"searching": false,
|
||||
"bInfo": false,
|
||||
"paging": false,
|
||||
"order": [],
|
||||
"language": jumpserver.language
|
||||
});
|
||||
};
|
||||
|
||||
jumpserver.initServerSideDataTable = function (options) {
|
||||
// options = {
|
||||
// ele *: $('#dataTable_id'),
|
||||
|
@ -375,9 +434,8 @@ jumpserver.initServerSideDataTable = function (options) {
|
|||
};
|
||||
var table = ele.DataTable({
|
||||
pageLength: options.pageLength || 15,
|
||||
dom: options.dom || '<"#uc.pull-left">flt<"row m-t"<"col-md-8"<"#op.col-md-6"><"col-md-6 text-center"i>><"col-md-4"p>>',
|
||||
dom: options.dom || '<"#uc.pull-left">fltr<"row m-t"<"col-md-8"<"#op.col-md-6"><"col-md-6 text-center"i>><"col-md-4"p>>',
|
||||
order: options.order || [],
|
||||
// select: options.select || 'multi',
|
||||
buttons: [],
|
||||
columnDefs: columnDefs,
|
||||
serverSide: true,
|
||||
|
@ -432,21 +490,7 @@ jumpserver.initServerSideDataTable = function (options) {
|
|||
},
|
||||
columns: options.columns || [],
|
||||
select: options.select || select,
|
||||
language: {
|
||||
search: "搜索",
|
||||
lengthMenu: "每页 _MENU_",
|
||||
info: "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项",
|
||||
infoFiltered: "",
|
||||
infoEmpty: "",
|
||||
zeroRecords: "没有匹配项",
|
||||
emptyTable: "没有记录",
|
||||
paginate: {
|
||||
first: "«",
|
||||
previous: "‹",
|
||||
next: "›",
|
||||
last: "»"
|
||||
}
|
||||
},
|
||||
language: jumpserver.language,
|
||||
lengthMenu: [[10, 15, 25, 50], [10, 15, 25, 50]]
|
||||
});
|
||||
table.selected = [];
|
||||
|
@ -477,8 +521,7 @@ jumpserver.initServerSideDataTable = function (options) {
|
|||
}
|
||||
})
|
||||
}
|
||||
}).
|
||||
on('draw', function(){
|
||||
}).on('draw', function(){
|
||||
$('#op').html(options.op_html || '');
|
||||
$('#uc').html(options.uc_html || '');
|
||||
var table_data = [];
|
||||
|
@ -683,7 +726,7 @@ function popoverPasswordRules(password_check_rules, $el) {
|
|||
}
|
||||
|
||||
// 初始化弹窗popover
|
||||
function initPopover($container, $progress, $idPassword, $el, password_check_rules){
|
||||
function initPopover($container, $progress, $idPassword, $el, password_check_rules, i18n_fallback){
|
||||
options = {};
|
||||
// User Interface
|
||||
options.ui = {
|
||||
|
@ -695,6 +738,14 @@ function initPopover($container, $progress, $idPassword, $el, password_check_rul
|
|||
showProgressbar: true,
|
||||
showVerdictsInsideProgressBar: true
|
||||
};
|
||||
options.i18n = {
|
||||
fallback: i18n_fallback,
|
||||
t: function (key) {
|
||||
var result = '';
|
||||
result = options.i18n.fallback[key];
|
||||
return result === key ? '' : result;
|
||||
}
|
||||
};
|
||||
$idPassword.pwstrength(options);
|
||||
popoverPasswordRules(password_check_rules, $el);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class="footer fixed">
|
||||
<div class="pull-right">
|
||||
Version <strong>1.3.3-{% include '_build.html' %}</strong> GPLv2.
|
||||
Version <strong>1.4.0-{% include '_build.html' %}</strong> GPLv2.
|
||||
<!--<img style="display: none" src="http://www.jumpserver.org/img/evaluate_avatar1.jpg">-->
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
</a>
|
||||
<ul class="dropdown-menu animated fadeInRight m-t-xs profile-dropdown">
|
||||
<li><a href="{% url 'users:user-profile' %}"><i class="fa fa-cogs"> </i><span> {% trans 'Profile' %}</span></a></li>
|
||||
{% if request.user.is_superuser %}
|
||||
{% if request.user.is_org_admin %}
|
||||
{% if request.COOKIES.IN_ADMIN_PAGE == 'No' %}
|
||||
<li><a id="switch_admin"><i class="fa fa-exchange"></i><span> {% trans 'Admin page' %}</span></a></li>
|
||||
{% else %}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div class="sidebar-collapse">
|
||||
<ul class="nav" id="side-menu">
|
||||
{% include '_user_profile.html' %}
|
||||
{% if request.user.is_superuser and request.COOKIES.IN_ADMIN_PAGE != "No" %}
|
||||
{% if request.user.is_org_admin and request.COOKIES.IN_ADMIN_PAGE != "No" %}
|
||||
{% include '_nav.html' %}
|
||||
{% else %}
|
||||
{% include '_nav_user.html' %}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{% load i18n %}
|
||||
<li id="index">
|
||||
<a href="{% url 'index' %}">
|
||||
<i class="fa fa-dashboard" style="width: 14px"></i> <span class="nav-label">{% trans 'Dashboard' %}</span><span
|
||||
class="label label-info pull-right"></span>
|
||||
<i class="fa fa-dashboard" style="width: 14px"></i> <span class="nav-label">{% trans 'Dashboard' %}</span>
|
||||
<span class="label label-info pull-right"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li id="users">
|
||||
|
@ -48,9 +48,12 @@
|
|||
<span class="nav-label">{% trans 'Web terminal' %}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% if request.user.is_superuser %}
|
||||
<li id="terminal"><a href="{% url 'terminal:terminal-list' %}">{% trans 'Terminal' %}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</li>
|
||||
{% if request.user.is_superuser %}
|
||||
<li id="ops">
|
||||
<a>
|
||||
<i class="fa fa-coffee" style="width: 14px"></i> <span class="nav-label">{% trans 'Job Center' %}</span><span class="fa arrow"></span>
|
||||
|
@ -59,6 +62,7 @@
|
|||
<li id="task"><a href="{% url 'ops:task-list' %}">{% trans 'Task list' %}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li id="audits">
|
||||
<a>
|
||||
<i class="fa fa-history" style="width: 14px"></i> <span class="nav-label">{% trans 'Audits' %}</span><span class="fa arrow"></span>
|
||||
|
@ -76,8 +80,29 @@
|
|||
{# <li id="download"><a href="">{% trans 'File download' %}</a></li>#}
|
||||
{# </ul>#}
|
||||
{#</li>#}
|
||||
{% if XPACK_ENABLED %}
|
||||
<li id="xpack">
|
||||
<a>
|
||||
<i class="fa fa-sitemap" style="width: 14px"></i> <span class="nav-label">{% trans 'XPack' %}</span><span class="fa arrow"></span>
|
||||
</a>
|
||||
<ul class="nav nav-second-level">
|
||||
{% for plugin in XPACK_PLUGINS %}
|
||||
<li id="{{ plugin.name }}"><a href="{{ plugin.endpoint }}">{% trans plugin.verbose_name %}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if request.user.is_superuser %}
|
||||
<li id="settings">
|
||||
<a href="{% url 'settings:basic-setting' %}">
|
||||
<i class="fa fa-gears"></i> <span class="nav-label">{% trans 'Settings' %}</span><span class="label label-info pull-right"></span>
|
||||
</a>
|
||||
</li>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
var current_org = '{{ CURRENT_ORG.name }}';
|
||||
console.log(current_org);
|
||||
})
|
||||
</script>
|
|
@ -1,15 +1,41 @@
|
|||
{% load static %}
|
||||
{% load i18n %}
|
||||
<li class="nav-header">
|
||||
<div class="dropdown profile-element">
|
||||
<div href="http://www.jumpserver.org" target="_blank">
|
||||
<img alt="logo" height="55" width="185" src="/static/img/logo-text.png" style="margin-left: 20px"/>
|
||||
<div class="profile-element" style="height: 65px">
|
||||
<div href="http://www.jumpserver.org" target="_blank" style="width: 100%; background-image: url({% static 'img/header-profile.png' %})">
|
||||
<img alt="logo" height="55" width="185" src="/static/img/logo-text.png"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
<div class="logo-element">
|
||||
<img alt="image" height="40" src="/static/img/logo.png"/>
|
||||
</div>
|
||||
{% if ADMIN_ORGS and request.COOKIES.IN_ADMIN_PAGE != 'No' %}
|
||||
{% if ADMIN_ORGS|length > 1 or not CURRENT_ORG.is_default %}
|
||||
<div style="height: 55px;">
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" aria-expanded="false" style="display: block; background-color: transparent; color: #8095a8; padding: 14px 20px 14px 25px">
|
||||
<i class="fa fa-bookmark" style="width: 14px; "></i>
|
||||
<span class="nav-label" style="padding-left: 7px">
|
||||
{{ CURRENT_ORG.name }}
|
||||
</span>
|
||||
<span class="fa fa-sort-desc pull-right"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu" style="width: 219px;">
|
||||
{% for org in ADMIN_ORGS %}
|
||||
<li>
|
||||
<a class="org-dropdown"
|
||||
href="{% url 'orgs:org-switch' pk=org.id %}"
|
||||
data-id="{{ org.id }}">
|
||||
{{ org.name }}
|
||||
{% if org.id == CURRENT_ORG.id %}
|
||||
<span class="fa fa-circle pull-right" style="padding-top: 5px; color: #1ab394"></span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</li>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
|
|
|
@ -25,8 +25,7 @@ from .hands import SystemUser
|
|||
from .models import Terminal, Status, Session, Task
|
||||
from .serializers import TerminalSerializer, StatusSerializer, \
|
||||
SessionSerializer, TaskSerializer, ReplaySerializer
|
||||
from .hands import IsSuperUserOrAppUser, IsAppUser, \
|
||||
IsSuperUserOrAppUserOrUserReadonly
|
||||
from common.permissions import IsAppUser, IsOrgAdminOrAppUser
|
||||
from .backends import get_command_storage, get_multi_command_storage, \
|
||||
SessionCommandSerializer
|
||||
|
||||
|
@ -36,7 +35,7 @@ logger = logging.getLogger(__file__)
|
|||
class TerminalViewSet(viewsets.ModelViewSet):
|
||||
queryset = Terminal.objects.filter(is_deleted=False)
|
||||
serializer_class = TerminalSerializer
|
||||
permission_classes = (IsSuperUserOrAppUserOrUserReadonly,)
|
||||
permission_classes = (AllowAny,)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
name = request.data.get('name')
|
||||
|
@ -105,7 +104,7 @@ class TerminalTokenApi(APIView):
|
|||
class StatusViewSet(viewsets.ModelViewSet):
|
||||
queryset = Status.objects.all()
|
||||
serializer_class = StatusSerializer
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
session_serializer_class = SessionSerializer
|
||||
task_serializer_class = TaskSerializer
|
||||
|
||||
|
@ -177,7 +176,7 @@ class StatusViewSet(viewsets.ModelViewSet):
|
|||
class SessionViewSet(viewsets.ModelViewSet):
|
||||
queryset = Session.objects.all()
|
||||
serializer_class = SessionSerializer
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
def get_queryset(self):
|
||||
terminal_id = self.kwargs.get("terminal", None)
|
||||
|
@ -200,11 +199,11 @@ class SessionViewSet(viewsets.ModelViewSet):
|
|||
class TaskViewSet(BulkModelViewSet):
|
||||
queryset = Task.objects.all()
|
||||
serializer_class = TaskSerializer
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
|
||||
class KillSessionAPI(APIView):
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
model = Task
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
@ -236,7 +235,7 @@ class CommandViewSet(viewsets.ViewSet):
|
|||
command_store = get_command_storage()
|
||||
multi_command_storage = get_multi_command_storage()
|
||||
serializer_class = SessionCommandSerializer
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
def get_queryset(self):
|
||||
self.command_store.filter(**dict(self.request.query_params))
|
||||
|
@ -244,13 +243,14 @@ class CommandViewSet(viewsets.ViewSet):
|
|||
def create(self, request, *args, **kwargs):
|
||||
serializer = self.serializer_class(data=request.data, many=True)
|
||||
if serializer.is_valid():
|
||||
print(serializer.validated_data)
|
||||
ok = self.command_store.bulk_save(serializer.validated_data)
|
||||
if ok:
|
||||
return Response("ok", status=201)
|
||||
else:
|
||||
return Response("Save error", status=500)
|
||||
else:
|
||||
msg = "Not valid: {}".format(serializer.errors)
|
||||
msg = "Command not valid: {}".format(serializer.errors)
|
||||
logger.error(msg)
|
||||
return Response({"msg": msg}, status=401)
|
||||
|
||||
|
@ -262,7 +262,7 @@ class CommandViewSet(viewsets.ViewSet):
|
|||
|
||||
class SessionReplayViewSet(viewsets.ViewSet):
|
||||
serializer_class = ReplaySerializer
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
session = None
|
||||
upload_to = 'replay' # 仅添加到本地存储中
|
||||
|
||||
|
@ -348,7 +348,7 @@ class SessionReplayViewSet(viewsets.ViewSet):
|
|||
|
||||
class SessionReplayV2ViewSet(SessionReplayViewSet):
|
||||
serializer_class = ReplaySerializer
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
session = None
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
|
|
|
@ -21,7 +21,7 @@ class CommandStore(CommandBase):
|
|||
user=command["user"], asset=command["asset"],
|
||||
system_user=command["system_user"], input=command["input"],
|
||||
output=command["output"], session=command["session"],
|
||||
timestamp=command["timestamp"]
|
||||
org_id=command["org_id"], timestamp=command["timestamp"]
|
||||
)
|
||||
|
||||
def bulk_save(self, commands):
|
||||
|
@ -33,7 +33,7 @@ class CommandStore(CommandBase):
|
|||
_commands.append(self.model(
|
||||
user=c["user"], asset=c["asset"], system_user=c["system_user"],
|
||||
input=c["input"], output=c["output"], session=c["session"],
|
||||
timestamp=c["timestamp"]
|
||||
org_id=c["org_id"], timestamp=c["timestamp"]
|
||||
))
|
||||
return self.model.objects.bulk_create(_commands)
|
||||
|
||||
|
|
|
@ -4,8 +4,10 @@ import uuid
|
|||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins import OrgModelMixin
|
||||
|
||||
class AbstractSessionCommand(models.Model):
|
||||
|
||||
class AbstractSessionCommand(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
user = models.CharField(max_length=64, db_index=True, verbose_name=_("User"))
|
||||
asset = models.CharField(max_length=128, db_index=True, verbose_name=_("Asset"))
|
||||
|
|
|
@ -12,5 +12,6 @@ class SessionCommandSerializer(serializers.Serializer):
|
|||
input = serializers.CharField(max_length=128)
|
||||
output = serializers.CharField(max_length=1024, allow_blank=True)
|
||||
session = serializers.CharField(max_length=36)
|
||||
org_id = serializers.CharField(max_length=36, required=False, default='', allow_null=True)
|
||||
timestamp = serializers.IntegerField()
|
||||
|
||||
|
|
|
@ -2,7 +2,4 @@
|
|||
#
|
||||
|
||||
from users.models import User
|
||||
from users.permissions import IsSuperUserOrAppUser, IsAppUser, \
|
||||
IsSuperUserOrAppUserOrUserReadonly
|
||||
from users.utils import AdminUserRequiredMixin
|
||||
from assets.models import SystemUser
|
||||
from assets.models import SystemUser
|
||||
|
|
|
@ -8,6 +8,7 @@ from django.utils import timezone
|
|||
from django.conf import settings
|
||||
|
||||
from users.models import User
|
||||
from orgs.mixins import OrgModelMixin
|
||||
from .backends.command.models import AbstractSessionCommand
|
||||
|
||||
|
||||
|
@ -112,7 +113,7 @@ class Status(models.Model):
|
|||
return self.date_created.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
class Session(models.Model):
|
||||
class Session(OrgModelMixin):
|
||||
LOGIN_FROM_CHOICES = (
|
||||
('ST', 'SSH Terminal'),
|
||||
('WT', 'Web Terminal'),
|
||||
|
|
|
@ -150,12 +150,7 @@
|
|||
});
|
||||
}
|
||||
$(document).ready(function() {
|
||||
$('table').DataTable({
|
||||
"searching": false,
|
||||
"paging": false,
|
||||
"bInfo" : false,
|
||||
"order": []
|
||||
});
|
||||
jumpserver.initStaticTable('table');
|
||||
$('.select2').select2({
|
||||
dropdownAutoWidth: true,
|
||||
width: "auto"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.conf.urls import url
|
||||
from django.urls import path
|
||||
from rest_framework import routers
|
||||
|
||||
from .. import api
|
||||
|
@ -10,26 +10,26 @@ from .. import api
|
|||
app_name = 'terminal'
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
router.register(r'v1/terminal/(?P<terminal>[a-zA-Z0-9\-]{36})?/?status', api.StatusViewSet, 'terminal-status')
|
||||
router.register(r'v1/terminal/(?P<terminal>[a-zA-Z0-9\-]{36})?/?sessions', api.SessionViewSet, 'terminal-sessions')
|
||||
router.register(r'v1/tasks', api.TaskViewSet, 'tasks')
|
||||
router.register(r'v1/terminal', api.TerminalViewSet, 'terminal')
|
||||
router.register(r'v1/command', api.CommandViewSet, 'command')
|
||||
router.register(r'v1/sessions', api.SessionViewSet, 'session')
|
||||
router.register(r'v1/status', api.StatusViewSet, 'session')
|
||||
router.register(r'terminal/(?P<terminal>[a-zA-Z0-9\-]{36})?/?status', api.StatusViewSet, 'terminal-status')
|
||||
router.register(r'terminal/(?P<terminal>[a-zA-Z0-9\-]{36})?/?sessions', api.SessionViewSet, 'terminal-sessions')
|
||||
router.register(r'terminal', api.TerminalViewSet, 'terminal')
|
||||
router.register(r'tasks', api.TaskViewSet, 'tasks')
|
||||
router.register(r'command', api.CommandViewSet, 'command')
|
||||
router.register(r'sessions', api.SessionViewSet, 'session')
|
||||
router.register(r'status', api.StatusViewSet, 'session')
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^v1/sessions/(?P<pk>[0-9a-zA-Z\-]{36})/replay/$',
|
||||
api.SessionReplayViewSet.as_view({'get': 'retrieve', 'post': 'create'}),
|
||||
name='session-replay'),
|
||||
url(r'^v1/tasks/kill-session/', api.KillSessionAPI.as_view(), name='kill-session'),
|
||||
url(r'^v1/terminal/(?P<terminal>[a-zA-Z0-9\-]{36})/access-key', api.TerminalTokenApi.as_view(),
|
||||
name='terminal-access-key'),
|
||||
url(r'^v1/terminal/config', api.TerminalConfig.as_view(), name='terminal-config'),
|
||||
path('sessions/<uuid:pk>/replay/',
|
||||
api.SessionReplayV2ViewSet.as_view({'get': 'retrieve', 'post': 'create'}),
|
||||
name='session-replay'),
|
||||
path('tasks/kill-session/', api.KillSessionAPI.as_view(), name='kill-session'),
|
||||
path('terminal/<uuid:terminal>/access-key/', api.TerminalTokenApi.as_view(),
|
||||
name='terminal-access-key'),
|
||||
path('terminal/config/', api.TerminalConfig.as_view(), name='terminal-config'),
|
||||
# v2: get session's replay
|
||||
url(r'^v2/sessions/(?P<pk>[0-9a-zA-Z\-]{36})/replay/$',
|
||||
api.SessionReplayV2ViewSet.as_view({'get': 'retrieve'}),
|
||||
name='session-replay-v2'),
|
||||
# path('v2/sessions/<uuid:pk>/replay/',
|
||||
# api.SessionReplayV2ViewSet.as_view({'get': 'retrieve'}),
|
||||
# name='session-replay-v2'),
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.conf.urls import url
|
||||
from django.urls import path
|
||||
|
||||
from .. import views
|
||||
|
||||
|
@ -10,20 +10,20 @@ app_name = 'terminal'
|
|||
|
||||
urlpatterns = [
|
||||
# Terminal view
|
||||
url(r'^terminal/$', views.TerminalListView.as_view(), name='terminal-list'),
|
||||
url(r'^terminal/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.TerminalDetailView.as_view(), name='terminal-detail'),
|
||||
url(r'^terminal/(?P<pk>[0-9a-zA-Z\-]{36})/connect/$', views.TerminalConnectView.as_view(), name='terminal-connect'),
|
||||
url(r'^terminal/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.TerminalUpdateView.as_view(), name='terminal-update'),
|
||||
url(r'^(?P<pk>[0-9a-zA-Z\-]{36})/accept/$', views.TerminalAcceptView.as_view(), name='terminal-accept'),
|
||||
url(r'^web-terminal/$', views.WebTerminalView.as_view(), name='web-terminal'),
|
||||
path('terminal/', views.TerminalListView.as_view(), name='terminal-list'),
|
||||
path('terminal/<uuid:pk>/', views.TerminalDetailView.as_view(), name='terminal-detail'),
|
||||
path('terminal/<uuid:pk>/connect/', views.TerminalConnectView.as_view(), name='terminal-connect'),
|
||||
path('terminal/<uuid:pk>/update/', views.TerminalUpdateView.as_view(), name='terminal-update'),
|
||||
path('<uuid:pk>/accept/', views.TerminalAcceptView.as_view(), name='terminal-accept'),
|
||||
path('web-terminal/', views.WebTerminalView.as_view(), name='web-terminal'),
|
||||
|
||||
# Session view
|
||||
url(r'^session-online/$', views.SessionOnlineListView.as_view(), name='session-online-list'),
|
||||
url(r'^session-offline/$', views.SessionOfflineListView.as_view(), name='session-offline-list'),
|
||||
url(r'^session/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.SessionDetailView.as_view(), name='session-detail'),
|
||||
path('session-online/', views.SessionOnlineListView.as_view(), name='session-online-list'),
|
||||
path('session-offline/', views.SessionOfflineListView.as_view(), name='session-offline-list'),
|
||||
path('session/<uuid:pk>/', views.SessionDetailView.as_view(), name='session-detail'),
|
||||
|
||||
# Command view
|
||||
url(r'^command/$', views.CommandListView.as_view(), name='command-list'),
|
||||
url(r'^command/export/$', views.CommandExportView.as_view(), name='command-export')
|
||||
path('command/', views.CommandListView.as_view(), name='command-list'),
|
||||
path('command/export/', views.CommandExportView.as_view(), name='command-export')
|
||||
|
||||
]
|
||||
|
|
|
@ -8,7 +8,8 @@ from django.http import HttpResponse
|
|||
from django.template import loader
|
||||
import time
|
||||
|
||||
from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin
|
||||
from common.mixins import DatetimeSearchMixin
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from ..models import Command
|
||||
from .. import utils
|
||||
from ..backends import get_multi_command_storage
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue