* [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
老广 2018-08-06 23:34:35 -05:00 committed by GitHub
parent dded4e10fb
commit d5451a482a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
121 changed files with 2192 additions and 1201 deletions

1
.gitignore vendored
View File

@ -32,3 +32,4 @@ django.db
celerybeat-schedule.db celerybeat-schedule.db
data/static data/static
docs/_build/ docs/_build/
xpack

View File

@ -2,4 +2,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
__version__ = "1.3.3" __version__ = "1.4.0"

View File

@ -20,7 +20,7 @@ from rest_framework_bulk import BulkModelViewSet
from common.mixins import IDInFilterMixin from common.mixins import IDInFilterMixin
from common.utils import get_logger from common.utils import get_logger
from ..hands import IsSuperUser from ..hands import IsOrgAdmin
from ..models import AdminUser, Asset from ..models import AdminUser, Asset
from .. import serializers from .. import serializers
from ..tasks import test_admin_user_connectability_manual from ..tasks import test_admin_user_connectability_manual
@ -39,19 +39,19 @@ class AdminUserViewSet(IDInFilterMixin, BulkModelViewSet):
""" """
queryset = AdminUser.objects.all() queryset = AdminUser.objects.all()
serializer_class = serializers.AdminUserSerializer serializer_class = serializers.AdminUserSerializer
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
class AdminUserAuthApi(generics.UpdateAPIView): class AdminUserAuthApi(generics.UpdateAPIView):
queryset = AdminUser.objects.all() queryset = AdminUser.objects.all()
serializer_class = serializers.AdminUserAuthSerializer serializer_class = serializers.AdminUserAuthSerializer
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
class ReplaceNodesAdminUserApi(generics.UpdateAPIView): class ReplaceNodesAdminUserApi(generics.UpdateAPIView):
queryset = AdminUser.objects.all() queryset = AdminUser.objects.all()
serializer_class = serializers.ReplaceNodeAdminUserSerializer serializer_class = serializers.ReplaceNodeAdminUserSerializer
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
def update(self, request, *args, **kwargs): def update(self, request, *args, **kwargs):
admin_user = self.get_object() admin_user = self.get_object()
@ -75,7 +75,7 @@ class AdminUserTestConnectiveApi(generics.RetrieveAPIView):
Test asset admin user connectivity Test asset admin user connectivity
""" """
queryset = AdminUser.objects.all() queryset = AdminUser.objects.all()
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
admin_user = self.get_object() admin_user = self.get_object()

View File

@ -2,8 +2,9 @@
# #
import random import random
import time
from rest_framework import generics from rest_framework import generics, permissions
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import BulkModelViewSet
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
@ -13,7 +14,7 @@ from django.db.models import Q
from common.mixins import IDInFilterMixin from common.mixins import IDInFilterMixin
from common.utils import get_logger 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 ..models import Asset, SystemUser, AdminUser, Node
from .. import serializers from .. import serializers
from ..tasks import update_asset_hardware_info_manual, \ from ..tasks import update_asset_hardware_info_manual, \
@ -39,38 +40,42 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
queryset = Asset.objects.all() queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer serializer_class = serializers.AssetSerializer
pagination_class = LimitOffsetPagination pagination_class = LimitOffsetPagination
permission_classes = (IsSuperUserOrAppUser,) permission_classes = (permissions.AllowAny,)
def get_queryset(self): def filter_node(self):
queryset = super().get_queryset()\
.prefetch_related('labels', 'nodes')\
.select_related('admin_user')
admin_user_id = self.request.query_params.get('admin_user_id')
node_id = self.request.query_params.get("node_id") 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") show_current_asset = self.request.query_params.get("show_current_asset")
if admin_user_id: if node.is_root():
admin_user = get_object_or_404(AdminUser, id=admin_user_id) if show_current_asset:
queryset = queryset.filter(admin_user=admin_user) self.queryset = self.queryset.filter(
if node_id and show_current_asset:
node = get_object_or_404(Node, id=node_id)
if node.is_root():
queryset = queryset.filter(
Q(nodes=node_id) | Q(nodes__isnull=True) Q(nodes=node_id) | Q(nodes__isnull=True)
).distinct() ).distinct()
else: return
queryset = queryset.filter(nodes=node).distinct() 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: def filter_admin_user_id(self):
node = get_object_or_404(Node, id=node_id) admin_user_id = self.request.query_params.get('admin_user_id')
if node.is_root(): if admin_user_id:
queryset = Asset.objects.all() admin_user = get_object_or_404(AdminUser, id=admin_user_id)
else: self.queryset = self.queryset.filter(admin_user=admin_user)
queryset = queryset.filter(
nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key), def get_queryset(self):
).distinct() self.queryset = super().get_queryset()\
return queryset .prefetch_related('labels', 'nodes')\
.select_related('admin_user')
self.filter_admin_user_id()
self.filter_node()
return self.queryset
class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView): class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
@ -79,7 +84,7 @@ class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
""" """
queryset = Asset.objects.all() queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer serializer_class = serializers.AssetSerializer
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
class AssetRefreshHardwareApi(generics.RetrieveAPIView): class AssetRefreshHardwareApi(generics.RetrieveAPIView):
@ -88,7 +93,7 @@ class AssetRefreshHardwareApi(generics.RetrieveAPIView):
""" """
queryset = Asset.objects.all() queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer serializer_class = serializers.AssetSerializer
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
asset_id = kwargs.get('pk') asset_id = kwargs.get('pk')
@ -102,7 +107,7 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView):
Test asset admin user connectivity Test asset admin user connectivity
""" """
queryset = Asset.objects.all() queryset = Asset.objects.all()
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
asset_id = kwargs.get('pk') asset_id = kwargs.get('pk')
@ -113,7 +118,7 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView):
class AssetGatewayApi(generics.RetrieveAPIView): class AssetGatewayApi(generics.RetrieveAPIView):
queryset = Asset.objects.all() queryset = Asset.objects.all()
permission_classes = (IsSuperUserOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
asset_id = kwargs.get('pk') asset_id = kwargs.get('pk')

View File

@ -2,12 +2,11 @@
from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import BulkModelViewSet
from rest_framework.views import APIView, Response from rest_framework.views import APIView, Response
from rest_framework.generics import RetrieveAPIView
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
from common.utils import get_logger from common.utils import get_logger
from ..hands import IsSuperUser, IsSuperUserOrAppUser from common.permissions import IsOrgAdmin, IsAppUser, IsOrgAdminOrAppUser
from ..models import Domain, Gateway from ..models import Domain, Gateway
from ..utils import test_gateway_connectability from ..utils import test_gateway_connectability
from .. import serializers from .. import serializers
@ -19,7 +18,7 @@ __all__ = ['DomainViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"]
class DomainViewSet(BulkModelViewSet): class DomainViewSet(BulkModelViewSet):
queryset = Domain.objects.all() queryset = Domain.objects.all()
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.DomainSerializer serializer_class = serializers.DomainSerializer
def get_serializer_class(self): def get_serializer_class(self):
@ -29,7 +28,7 @@ class DomainViewSet(BulkModelViewSet):
def get_permissions(self): def get_permissions(self):
if self.request.query_params.get('gateway'): if self.request.query_params.get('gateway'):
self.permission_classes = (IsSuperUserOrAppUser,) self.permission_classes = (IsOrgAdminOrAppUser,)
return super().get_permissions() return super().get_permissions()
@ -37,12 +36,12 @@ class GatewayViewSet(BulkModelViewSet):
filter_fields = ("domain",) filter_fields = ("domain",)
search_fields = filter_fields search_fields = filter_fields
queryset = Gateway.objects.all() queryset = Gateway.objects.all()
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.GatewaySerializer serializer_class = serializers.GatewaySerializer
class GatewayTestConnectionApi(SingleObjectMixin, APIView): class GatewayTestConnectionApi(SingleObjectMixin, APIView):
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
model = Gateway model = Gateway
object = None object = None

View File

@ -17,7 +17,7 @@ from rest_framework_bulk import BulkModelViewSet
from django.db.models import Count from django.db.models import Count
from common.utils import get_logger from common.utils import get_logger
from ..hands import IsSuperUser from ..hands import IsOrgAdmin
from ..models import Label from ..models import Label
from .. import serializers from .. import serializers
@ -27,8 +27,7 @@ __all__ = ['LabelViewSet']
class LabelViewSet(BulkModelViewSet): class LabelViewSet(BulkModelViewSet):
queryset = Label.objects.annotate(asset_count=Count("assets")) permission_classes = (IsOrgAdmin,)
permission_classes = (IsSuperUser,)
serializer_class = serializers.LabelSerializer serializer_class = serializers.LabelSerializer
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
@ -36,3 +35,7 @@ class LabelViewSet(BulkModelViewSet):
self.serializer_class = serializers.LabelDistinctSerializer self.serializer_class = serializers.LabelDistinctSerializer
self.queryset = self.queryset.values("name").distinct() self.queryset = self.queryset.values("name").distinct()
return super().list(request, *args, **kwargs) return super().list(request, *args, **kwargs)
def get_queryset(self):
self.queryset = Label.objects.annotate(asset_count=Count("assets"))
return self.queryset

View File

@ -13,16 +13,17 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from rest_framework import generics, mixins from rest_framework import generics, mixins, viewsets
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import BulkModelViewSet
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.db.models import Count
from common.utils import get_logger, get_object_or_none from common.utils import get_logger, get_object_or_none
from ..hands import IsSuperUser from ..hands import IsOrgAdmin
from ..models import Node from ..models import Node
from ..tasks import update_assets_hardware_info_util, test_asset_connectability_util from ..tasks import update_assets_hardware_info_util, test_asset_connectability_util
from .. import serializers from .. import serializers
@ -30,57 +31,31 @@ from .. import serializers
logger = get_logger(__file__) logger = get_logger(__file__)
__all__ = [ __all__ = [
'NodeViewSet', 'NodeChildrenApi', 'NodeViewSet', 'NodeChildrenApi', 'NodeAssetsApi',
'NodeAssetsApi', 'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'NodeReplaceAssetsApi',
'NodeAddAssetsApi', 'NodeRemoveAssetsApi',
'NodeReplaceAssetsApi',
'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi', 'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi',
'TestNodeConnectiveApi' 'TestNodeConnectiveApi'
] ]
class NodeViewSet(BulkModelViewSet): class NodeViewSet(viewsets.ModelViewSet):
queryset = Node.objects.all() queryset = Node.objects.all()
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeSerializer serializer_class = serializers.NodeSerializer
def get_queryset(self):
queryset = super().get_queryset().annotate(Count('assets'))
return queryset
def perform_create(self, serializer): def perform_create(self, serializer):
child_key = Node.root().get_next_child_key() child_key = Node.root().get_next_child_key()
serializer.validated_data["key"] = child_key serializer.validated_data["key"] = child_key
serializer.save() serializer.save()
# 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): class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
queryset = Node.objects.all() queryset = Node.objects.all()
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeSerializer serializer_class = serializers.NodeSerializer
instance = None instance = None
@ -126,22 +101,26 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
query_all = self.request.query_params.get("all") query_all = self.request.query_params.get("all")
query_assets = self.request.query_params.get('assets') query_assets = self.request.query_params.get('assets')
node = self.get_object() node = self.get_object()
if node is None: if node is None:
node = Node.root() node = Node.root()
node.assets__count = node.get_all_assets().count()
queryset.append(node) 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)) queryset.extend(list(children))
if query_assets: if query_assets:
assets = node.get_assets() assets = node.get_assets()
for asset in assets: for asset in assets:
node_fake = Node() node_fake = Node()
node_fake.assets__count = 0
node_fake.id = asset.id node_fake.id = asset.id
node_fake.is_node = False node_fake.is_node = False
node_fake.parent_id = node.id node_fake.key = node.key + ':0'
node_fake.value = asset.hostname node_fake.value = asset.hostname
queryset.append(node_fake) queryset.append(node_fake)
queryset = sorted(queryset, key=lambda x: x.is_node, reverse=True) 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): class NodeAssetsApi(generics.ListAPIView):
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetSerializer serializer_class = serializers.AssetSerializer
def get_queryset(self): def get_queryset(self):
@ -167,7 +146,7 @@ class NodeAssetsApi(generics.ListAPIView):
class NodeAddChildrenApi(generics.UpdateAPIView): class NodeAddChildrenApi(generics.UpdateAPIView):
queryset = Node.objects.all() queryset = Node.objects.all()
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeAddChildrenSerializer serializer_class = serializers.NodeAddChildrenSerializer
instance = None instance = None
@ -185,7 +164,7 @@ class NodeAddChildrenApi(generics.UpdateAPIView):
class NodeAddAssetsApi(generics.UpdateAPIView): class NodeAddAssetsApi(generics.UpdateAPIView):
serializer_class = serializers.NodeAssetsSerializer serializer_class = serializers.NodeAssetsSerializer
queryset = Node.objects.all() queryset = Node.objects.all()
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
instance = None instance = None
def perform_update(self, serializer): def perform_update(self, serializer):
@ -197,7 +176,7 @@ class NodeAddAssetsApi(generics.UpdateAPIView):
class NodeRemoveAssetsApi(generics.UpdateAPIView): class NodeRemoveAssetsApi(generics.UpdateAPIView):
serializer_class = serializers.NodeAssetsSerializer serializer_class = serializers.NodeAssetsSerializer
queryset = Node.objects.all() queryset = Node.objects.all()
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
instance = None instance = None
def perform_update(self, serializer): def perform_update(self, serializer):
@ -213,7 +192,7 @@ class NodeRemoveAssetsApi(generics.UpdateAPIView):
class NodeReplaceAssetsApi(generics.UpdateAPIView): class NodeReplaceAssetsApi(generics.UpdateAPIView):
serializer_class = serializers.NodeAssetsSerializer serializer_class = serializers.NodeAssetsSerializer
queryset = Node.objects.all() queryset = Node.objects.all()
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
instance = None instance = None
def perform_update(self, serializer): def perform_update(self, serializer):
@ -224,7 +203,7 @@ class NodeReplaceAssetsApi(generics.UpdateAPIView):
class RefreshNodeHardwareInfoApi(APIView): class RefreshNodeHardwareInfoApi(APIView):
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
model = Node model = Node
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
@ -237,7 +216,7 @@ class RefreshNodeHardwareInfoApi(APIView):
class TestNodeConnectiveApi(APIView): class TestNodeConnectiveApi(APIView):
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
model = Node model = Node
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):

View File

@ -16,8 +16,9 @@
from rest_framework import generics from rest_framework import generics
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import BulkModelViewSet
from common.utils import get_logger from common.utils import get_logger
from ..hands import IsSuperUser, IsSuperUserOrAppUser from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from ..models import SystemUser from ..models import SystemUser
from .. import serializers from .. import serializers
from ..tasks import push_system_user_to_assets_manual, \ from ..tasks import push_system_user_to_assets_manual, \
@ -37,7 +38,7 @@ class SystemUserViewSet(BulkModelViewSet):
""" """
queryset = SystemUser.objects.all() queryset = SystemUser.objects.all()
serializer_class = serializers.SystemUserSerializer serializer_class = serializers.SystemUserSerializer
permission_classes = (IsSuperUserOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView): class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
@ -45,7 +46,7 @@ class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
Get system user auth info Get system user auth info
""" """
queryset = SystemUser.objects.all() queryset = SystemUser.objects.all()
permission_classes = (IsSuperUserOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.SystemUserAuthSerializer serializer_class = serializers.SystemUserAuthSerializer
def destroy(self, request, *args, **kwargs): def destroy(self, request, *args, **kwargs):
@ -59,7 +60,7 @@ class SystemUserPushApi(generics.RetrieveAPIView):
Push system user to cluster assets api Push system user to cluster assets api
""" """
queryset = SystemUser.objects.all() queryset = SystemUser.objects.all()
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
system_user = self.get_object() system_user = self.get_object()
@ -75,7 +76,7 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView):
Push system user to cluster assets api Push system user to cluster assets api
""" """
queryset = SystemUser.objects.all() queryset = SystemUser.objects.all()
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
system_user = self.get_object() system_user = self.get_object()

View File

@ -3,14 +3,17 @@
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from ..models import Asset, AdminUser
from common.utils import get_logger from common.utils import get_logger
from orgs.mixins import OrgModelForm
from ..models import Asset, AdminUser
logger = get_logger(__file__) logger = get_logger(__file__)
__all__ = ['AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm'] __all__ = ['AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm']
class AssetCreateForm(forms.ModelForm): class AssetCreateForm(OrgModelForm):
class Meta: class Meta:
model = Asset model = Asset
fields = [ fields = [
@ -50,7 +53,7 @@ class AssetCreateForm(forms.ModelForm):
} }
class AssetUpdateForm(forms.ModelForm): class AssetUpdateForm(OrgModelForm):
class Meta: class Meta:
model = Asset model = Asset
fields = [ fields = [
@ -90,7 +93,7 @@ class AssetUpdateForm(forms.ModelForm):
} }
class AssetBulkUpdateForm(forms.ModelForm): class AssetBulkUpdateForm(OrgModelForm):
assets = forms.ModelMultipleChoiceField( assets = forms.ModelMultipleChoiceField(
required=True, help_text='* required', required=True, help_text='* required',
label=_('Select assets'), queryset=Asset.objects.all(), 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, label=_('Port'), required=False, min_value=1, max_value=65535,
) )
admin_user = forms.ModelChoiceField( admin_user = forms.ModelChoiceField(
required=False, queryset=AdminUser.objects.all(), required=False, queryset=AdminUser.objects,
label=_("Admin user"), label=_("Admin user"),
widget=forms.Select( widget=forms.Select(
attrs={ attrs={

View File

@ -3,6 +3,7 @@
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from orgs.mixins import OrgModelForm
from ..models import Domain, Asset, Gateway from ..models import Domain, Asset, Gateway
from .user import PasswordAndKeyAuthForm from .user import PasswordAndKeyAuthForm
@ -34,7 +35,7 @@ class DomainForm(forms.ModelForm):
return instance return instance
class GatewayForm(PasswordAndKeyAuthForm): class GatewayForm(PasswordAndKeyAuthForm, OrgModelForm):
def save(self, commit=True): def save(self, commit=True):
# Because we define custom field, so we need rewrite :method: `save` # Because we define custom field, so we need rewrite :method: `save`

View File

@ -11,6 +11,6 @@
""" """
from common.mixins import AdminUserRequiredMixin from common.permissions import AdminUserRequiredMixin
from common.permissions import IsAppUser, IsSuperUser, IsValidUser, IsSuperUserOrAppUser from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser
from users.models import User, UserGroup from users.models import User, UserGroup

View File

@ -13,6 +13,7 @@ from django.core.cache import cache
from ..const import ASSET_ADMIN_CONN_CACHE_KEY from ..const import ASSET_ADMIN_CONN_CACHE_KEY
from .user import AdminUser, SystemUser from .user import AdminUser, SystemUser
from orgs.mixins import OrgModelMixin,OrgManager
__all__ = ['Asset'] __all__ = ['Asset']
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -44,12 +45,7 @@ class AssetQuerySet(models.QuerySet):
return self.active() return self.active()
class AssetManager(models.Manager): class Asset(OrgModelMixin):
def get_queryset(self):
return AssetQuerySet(self.model, using=self._db)
class Asset(models.Model):
# Important # Important
PLATFORM_CHOICES = ( PLATFORM_CHOICES = (
('Linux', 'Linux'), ('Linux', 'Linux'),
@ -71,16 +67,11 @@ class Asset(models.Model):
) )
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
db_index=True) hostname = models.CharField(max_length=128, verbose_name=_('Hostname'))
hostname = models.CharField(max_length=128, unique=True, protocol = models.CharField(max_length=128, default=SSH_PROTOCOL, choices=PROTOCOL_CHOICES, verbose_name=_('Protocol'))
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')) port = models.IntegerField(default=22, verbose_name=_('Port'))
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform'))
default='Linux', verbose_name=_('Platform'))
domain = models.ForeignKey("assets.Domain", null=True, blank=True, domain = models.ForeignKey("assets.Domain", null=True, blank=True,
related_name='assets', verbose_name=_("Domain"), related_name='assets', verbose_name=_("Domain"),
on_delete=models.SET_NULL) on_delete=models.SET_NULL)
@ -94,11 +85,8 @@ class Asset(models.Model):
null=True, verbose_name=_("Admin user")) null=True, verbose_name=_("Admin user"))
# Some information # Some information
public_ip = models.GenericIPAddressField(max_length=32, blank=True, public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP'))
null=True, number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
verbose_name=_('Public IP'))
number = models.CharField(max_length=32, null=True, blank=True,
verbose_name=_('Asset number'))
# Collect # Collect
vendor = models.CharField(max_length=64, null=True, blank=True, 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, comment = models.TextField(max_length=128, default='', blank=True,
verbose_name=_('Comment')) verbose_name=_('Comment'))
objects = AssetManager() objects = OrgManager.from_queryset(AssetQuerySet)()
def __str__(self): def __str__(self):
return '{0.hostname}({0.ip})'.format(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)) nodes = list(reduce(lambda x, y: set(x) | set(y), nodes))
return nodes return nodes
@property
def org_name(self):
from orgs.models import Organization
org = Organization.get_instance(self.org_id)
return org.name
@property @property
def hardware_info(self): def hardware_info(self):
if self.cpu_count: if self.cpu_count:
@ -233,7 +227,7 @@ class Asset(models.Model):
return data return data
class Meta: class Meta:
unique_together = ('ip', 'port') unique_together = [('org_id', 'hostname')]
verbose_name = _("Asset") verbose_name = _("Asset")
@classmethod @classmethod

View File

@ -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.utils import get_signer, ssh_key_string_to_obj, ssh_key_gen
from common.validators import alphanumeric from common.validators import alphanumeric
from orgs.mixins import OrgModelMixin
from .utils import private_key_validator from .utils import private_key_validator
signer = get_signer() signer = get_signer()
class AssetUser(models.Model): class AssetUser(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) 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]) 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')) _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, ]) _private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])

View File

@ -7,12 +7,13 @@ import random
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelMixin
from .base import AssetUser from .base import AssetUser
__all__ = ['Domain', 'Gateway'] __all__ = ['Domain', 'Gateway']
class Domain(models.Model): class Domain(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) 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, unique=True, verbose_name=_('Name'))
comment = models.TextField(blank=True, verbose_name=_('Comment')) 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) ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
port = models.IntegerField(default=22, verbose_name=_('Port')) port = models.IntegerField(default=22, verbose_name=_('Port'))
protocol = models.CharField(choices=PROTOCOL_CHOICES, max_length=16, default=SSH_PROTOCOL, verbose_name=_("Protocol")) 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")) comment = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Comment"))
is_active = models.BooleanField(default=True, verbose_name=_("Is active")) is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
def __str__(self): def __str__(self):
return self.name return self.name
class Meta:
unique_together = [('name', 'org_id')]

View File

@ -4,9 +4,10 @@
import uuid import uuid
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelMixin
class Label(models.Model): class Label(OrgModelMixin):
SYSTEM_CATEGORY = "S" SYSTEM_CATEGORY = "S"
USER_CATEGORY = "U" USER_CATEGORY = "U"
CATEGORY_CHOICES = ( CATEGORY_CHOICES = (
@ -34,4 +35,4 @@ class Label(models.Model):
class Meta: class Meta:
db_table = "assets_label" db_table = "assets_label"
unique_together = ('name', 'value') unique_together = [('name', 'value')]

View File

@ -5,12 +5,15 @@ import uuid
from django.db import models, transaction from django.db import models, transaction
from django.db.models import Q from django.db.models import Q
from django.utils.translation import ugettext_lazy as _ 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'] __all__ = ['Node']
class Node(models.Model): class Node(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1' key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1'
value = models.CharField(max_length=128, verbose_name=_("Value")) value = models.CharField(max_length=128, verbose_name=_("Value"))
@ -20,7 +23,8 @@ class Node(models.Model):
is_node = True is_node = True
def __str__(self): def __str__(self):
return self.full_value return self.value
# return self.full_value
def __eq__(self, other): def __eq__(self, other):
return self.key == other.key return self.key == other.key
@ -93,12 +97,10 @@ class Node(models.Model):
def get_assets(self): def get_assets(self):
from .asset import Asset from .asset import Asset
if self.is_root(): if self.is_default_node():
assets = Asset.objects.filter( assets = Asset.objects.filter(nodes__isnull=True)
Q(nodes__id=self.id) | Q(nodes__isnull=True)
)
else: else:
assets = self.assets.all() assets = Asset.objects.filter(nodes__id=self.id)
return assets return assets
def get_valid_assets(self): def get_valid_assets(self):
@ -106,49 +108,61 @@ class Node(models.Model):
def get_all_assets(self): def get_all_assets(self):
from .asset import Asset from .asset import Asset
if self.is_root(): pattern = r'^{0}$|^{0}:'.format(self.key)
assets = Asset.objects.all() args = []
kwargs = {}
if self.is_default_node():
args.append(Q(nodes__key__regex=pattern) | Q(nodes=None))
else: else:
pattern = r'^{0}$|^{0}:'.format(self.key) kwargs['nodes__key__regex'] = pattern
assets = Asset.objects.filter(nodes__key__regex=pattern) assets = Asset.objects.filter(*args, **kwargs)
return assets return assets
def get_all_valid_assets(self): def get_all_valid_assets(self):
return self.get_all_assets().valid() return self.get_all_assets().valid()
def is_default_node(self):
return self.is_root() and self.key == '0'
def is_root(self): 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 @property
def parent(self): def parent(self):
if self.key == "0" or not self.key.startswith("0"): if self.is_root():
return self.__class__.root() return self
parent_key = ":".join(self.key.split(":")[:-1])
try: try:
parent = self.__class__.objects.get(key=parent_key) parent = self.__class__.objects.get(key=self.parent_key)
return parent return parent
except Node.DoesNotExist: except Node.DoesNotExist:
return self.__class__.root() return self.__class__.root()
@parent.setter @parent.setter
def parent(self, parent): def parent(self, parent):
if self.is_node: if not self.is_node:
children = self.get_all_children() self.key = parent.key + ':fake'
old_key = self.key return
with transaction.atomic(): children = self.get_all_children()
self.key = parent.get_next_child_key() old_key = self.key
for child in children: with transaction.atomic():
child.key = child.key.replace(old_key, self.key, 1) self.key = parent.get_next_child_key()
child.save() for child in children:
self.save() child.key = child.key.replace(old_key, self.key, 1)
else: child.save()
self.key = parent.key+':fake' self.save()
def get_ancestor(self, with_self=False): def get_ancestor(self, with_self=False):
if self.is_root(): if self.is_root():
ancestor = self.__class__.objects.filter(key='0') root = self.__class__.root()
return ancestor return [root]
_key = self.key.split(':') _key = self.key.split(':')
if not with_self: if not with_self:
_key.pop() _key.pop()
@ -162,10 +176,35 @@ class Node(models.Model):
return ancestor return ancestor
@classmethod @classmethod
def root(cls): def create_root_node(cls):
obj, created = cls.objects.get_or_create( # 如果使用current_org 在set_current_org时会死循环
key='0', defaults={"key": '0', 'value': "ROOT"} _current_org = get_current_org()
) with transaction.atomic():
print(obj) if _current_org.is_default():
return obj 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))

View File

@ -69,6 +69,7 @@ class AdminUser(AssetUser):
class Meta: class Meta:
ordering = ['name'] ordering = ['name']
unique_together = [('name', 'org_id')]
verbose_name = _("Admin user") verbose_name = _("Admin user")
@classmethod @classmethod
@ -176,6 +177,7 @@ class SystemUser(AssetUser):
class Meta: class Meta:
ordering = ['name'] ordering = ['name']
unique_together = [('name', 'org_id')]
verbose_name = _("System user") verbose_name = _("System user")
@classmethod @classmethod

View File

@ -58,7 +58,7 @@ class ReplaceNodeAdminUserSerializer(serializers.ModelSerializer):
管理用户更新关联到的集群 管理用户更新关联到的集群
""" """
nodes = serializers.PrimaryKeyRelatedField( nodes = serializers.PrimaryKeyRelatedField(
many=True, queryset=Node.objects.all() many=True, queryset = Node.objects.all()
) )
class Meta: class Meta:

View File

@ -20,12 +20,12 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
model = Asset model = Asset
list_serializer_class = BulkListSerializer list_serializer_class = BulkListSerializer
fields = '__all__' 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): def get_field_names(self, declared_fields, info):
fields = super().get_field_names(declared_fields, info) fields = super().get_field_names(declared_fields, info)
fields.extend([ fields.extend([
'hardware_info', 'is_connective', 'hardware_info', 'is_connective', 'org_name'
]) ])
return fields return fields
@ -43,7 +43,7 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
fields = ( fields = (
"id", "hostname", "ip", "port", "system_users_granted", "id", "hostname", "ip", "port", "system_users_granted",
"is_active", "system_users_join", "os", 'domain', "is_active", "system_users_join", "os", 'domain',
"platform", "comment", "protocol", "platform", "comment", "protocol", "org_id", "org_name",
) )
@staticmethod @staticmethod
@ -61,6 +61,6 @@ class MyAssetGrantedSerializer(AssetGrantedSerializer):
model = Asset model = Asset
fields = ( fields = (
"id", "hostname", "system_users_granted", "id", "hostname", "system_users_granted",
"is_active", "system_users_join", "is_active", "system_users_join", "org_name",
"os", "platform", "comment", "os", "platform", "comment", "org_id", "protocol"
) )

View File

@ -26,7 +26,7 @@ class NodeGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer):
model = Node model = Node
fields = [ fields = [
'id', 'key', 'name', 'value', 'parent', 'id', 'key', 'name', 'value', 'parent',
'assets_granted', 'assets_amount', 'assets_granted', 'assets_amount', 'org_id',
] ]
@staticmethod @staticmethod
@ -43,12 +43,16 @@ class NodeGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer):
class NodeSerializer(serializers.ModelSerializer): class NodeSerializer(serializers.ModelSerializer):
parent = serializers.SerializerMethodField()
assets_amount = serializers.SerializerMethodField() assets_amount = serializers.SerializerMethodField()
tree_id = serializers.SerializerMethodField()
tree_parent = serializers.SerializerMethodField()
class Meta: class Meta:
model = Node 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 list_serializer_class = BulkListSerializer
def validate(self, data): def validate(self, data):
@ -63,12 +67,16 @@ class NodeSerializer(serializers.ModelSerializer):
return data return data
@staticmethod @staticmethod
def get_parent(obj): def get_assets_amount(obj):
return obj.parent.id if obj.is_node else obj.parent_id return obj.assets__count if hasattr(obj, 'assets__count') else 0
@staticmethod @staticmethod
def get_assets_amount(obj): def get_tree_id(obj):
return obj.get_all_assets().count() if obj.is_node else 0 return obj.key
@staticmethod
def get_tree_parent(obj):
return obj.parent_key
def get_fields(self): def get_fields(self):
fields = super().get_fields() fields = super().get_fields()
@ -78,7 +86,7 @@ class NodeSerializer(serializers.ModelSerializer):
class NodeAssetsSerializer(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: class Meta:
model = Node model = Node

View File

@ -71,7 +71,7 @@ function initTable2() {
function onSelected2(event, treeNode) { function onSelected2(event, treeNode) {
var url = asset_table2.ajax.url(); 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); setCookie('node_selected', treeNode.id);
asset_table2.ajax.url(url); asset_table2.ajax.url(url);
asset_table2.ajax.reload(); asset_table2.ajax.reload();
@ -97,17 +97,20 @@ function initTree2() {
var zNodes = []; var zNodes = [];
$.get("{% url 'api-assets:node-list' %}", function(data, status){ $.get("{% url 'api-assets:node-list' %}", function(data, status){
$.each(data, function (index, value) { $.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;#} {#value["open"] = true;#}
if (value["key"] === "0") { if (value["key"] === "0") {
value["open"] = true; value["open"] = true;
} }
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')'; value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
value['value'] = value['value'];
}); });
zNodes = data; zNodes = data;
$.fn.zTree.init($("#assetTree2"), setting, zNodes); $.fn.zTree.init($("#assetTree2"), setting, zNodes);
zTree2 = $.fn.zTree.getZTreeObj("assetTree2"); zTree2 = $.fn.zTree.getZTreeObj("assetTree2");
var root = zTree2.getNodes()[0];
zTree2.expandNode(root);
}); });
} }

View File

@ -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 %}

View File

@ -130,7 +130,7 @@
</div> </div>
</div> </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="col-sm-5" style="padding-left: 0;padding-right: 0">
<div class="panel panel-primary"> <div class="panel panel-primary">
<div class="panel-heading"> <div class="panel-heading">

View File

@ -10,6 +10,7 @@
{% block custom_head_css_js %} {% block custom_head_css_js %}
<link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet"> <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 type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
<script src="{% static 'js/jquery.form.min.js' %}"></script> <script src="{% static 'js/jquery.form.min.js' %}"></script>
<style type="text/css"> <style type="text/css">
@ -27,6 +28,10 @@
list-style: none; list-style: none;
background-clip: padding-box; background-clip: padding-box;
} }
.dataTables_wrapper .dataTables_processing {
opacity: .9;
border: none;
}
div#rMenu li{ div#rMenu li{
margin: 1px 0; margin: 1px 0;
cursor: pointer; 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) { {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 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); 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" %}', 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: [ columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "id"}, {data: "hostname" }, {data: "ip" },
{data: "cpu_cores"}, {data: "is_active", orderable: false }, {data: "cpu_cores"}, {data: "is_active", orderable: false },
@ -202,17 +190,17 @@ function addTreeNode() {
if (!parentNode){ if (!parentNode){
return 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){ $.post(url, {}, function (data, status){
if (status === "success") { if (status === "success") {
var newNode = { var newNode = {
name: data["value"], name: data["value"],
id: data["id"], id: data["id"],
pId: parentNode.id pId: parentNode.node_id
}; };
newNode.checked = zTree.getSelectedNodes()[0].checked; newNode.checked = zTree.getSelectedNodes()[0].checked;
zTree.addNodes(parentNode, 0, newNode); 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); zTree.editName(node);
} else { } else {
alert("{% trans 'Create node failed' %}") alert("{% trans 'Create node failed' %}")
@ -231,7 +219,7 @@ function removeTreeNode() {
} else if (current_node.assets_amount !== 0) { } else if (current_node.assets_amount !== 0) {
toastr.error("{% trans 'Have assets, cancel' %}"); toastr.error("{% trans 'Have assets, cancel' %}");
} else { } 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({ $.ajax({
url: url, url: url,
method: "DELETE", method: "DELETE",
@ -291,7 +279,7 @@ function onBodyMouseDown(event){
function onRename(event, treeId, treeNode, isCancel){ function onRename(event, treeId, treeNode, isCancel){
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", treeNode.id); var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", treeNode.node_id);
var data = {"value": treeNode.name}; var data = {"value": treeNode.name};
if (isCancel){ if (isCancel){
return return
@ -305,9 +293,9 @@ function onRename(event, treeId, treeNode, isCancel){
function onSelected(event, treeNode) { function onSelected(event, treeNode) {
var url = asset_table.ajax.url(); 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')); 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.url(url);
asset_table.ajax.reload(); asset_table.ajax.reload();
} }
@ -324,7 +312,7 @@ function selectQueryNode() {
node_id = cookie_node_id; node_id = cookie_node_id;
} }
node = zTree.getNodesByParam("id", node_id, null); node = zTree.getNodesByParam("node_id", node_id, null);
if (node){ if (node){
zTree.selectNode(node[0]); zTree.selectNode(node[0]);
} }
@ -341,11 +329,7 @@ function beforeDrop(treeId, treeNodes, targetNode, moveType) {
}); });
var msg = "你想移动节点: `" + treeNodesNames.join(",") + "` 到 `" + targetNode.value + "` 下吗?"; var msg = "你想移动节点: `" + treeNodesNames.join(",") + "` 到 `" + targetNode.value + "` 下吗?";
if (confirm(msg)){ return confirm(msg);
return true
} else {
return false
}
} }
function onDrag(event, treeId, treeNodes) { function onDrag(event, treeId, treeNodes) {
@ -354,10 +338,10 @@ function onDrag(event, treeId, treeNodes) {
function onDrop(event, treeId, treeNodes, targetNode, moveType) { function onDrop(event, treeId, treeNodes, targetNode, moveType) {
var treeNodesIds = []; var treeNodesIds = [];
$.each(treeNodes, function (index, value) { $.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}; var body = {nodes: treeNodesIds};
APIUpdateAttr({ APIUpdateAttr({
url: the_url, url: the_url,
@ -401,16 +385,21 @@ function initTree() {
var zNodes = []; var zNodes = [];
$.get("{% url 'api-assets:node-list' %}", function(data, status){ $.get("{% url 'api-assets:node-list' %}", function(data, status){
$.each(data, function (index, value) { $.each(data, function (index, value) {
value["pId"] = value["parent"]; value["node_id"] = value["id"];
if (value["key"] === "0") { value["id"] = value["tree_id"];
value["open"] = true; if (value["tree_id"] !== value["tree_parent"]){
} value["pId"] = value["tree_parent"];
} else {
value["isParent"] = true;
}
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')'; value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
value['value'] = value['value']; value['value'] = value['value'];
}); });
zNodes = data; zNodes = data;
$.fn.zTree.init($("#assetTree"), setting, zNodes); $.fn.zTree.init($("#assetTree"), setting, zNodes);
zTree = $.fn.zTree.getZTreeObj("assetTree"); zTree = $.fn.zTree.getZTreeObj("assetTree");
var root = zTree.getNodes()[0];
zTree.expandNode(root);
rMenu = $("#rMenu"); rMenu = $("#rMenu");
selectQueryNode(); selectQueryNode();
}); });
@ -462,7 +451,7 @@ $(document).ready(function(){
$.ajax({ $.ajax({
url: "{% url "assets:asset-export" %}", url: "{% url "assets:asset-export" %}",
method: 'POST', 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", dataType: "json",
success: function (data, textStatus) { success: function (data, textStatus) {
window.open(data.redirect) window.open(data.redirect)
@ -479,8 +468,8 @@ $(document).ready(function(){
var current_node; var current_node;
if (nodes && nodes.length ===1 ){ if (nodes && nodes.length ===1 ){
current_node = nodes[0]; current_node = nodes[0];
action = setUrlParam(action, 'node_id', current_node.id); action = setUrlParam(action, 'node_id', current_node.node_id);
{#action += "?node_id=" + current_node.id;#} {#action += "?node_id=" + current_node.node_id;#}
$form.attr("action", action) $form.attr("action", action)
} }
$form.find('.help-block').remove(); $form.find('.help-block').remove();
@ -506,7 +495,7 @@ $(document).ready(function(){
var current_node; var current_node;
if (nodes && nodes.length ===1 ){ if (nodes && nodes.length ===1 ){
current_node = nodes[0]; current_node = nodes[0];
url += "?node_id=" + current_node.id; url += "?node_id=" + current_node.node_id;
} }
window.open(url, '_self'); window.open(url, '_self');
}) })
@ -520,7 +509,7 @@ $(document).ready(function(){
return null; 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) { function success(data) {
rMenu.css({"visibility" : "hidden"}); rMenu.css({"visibility" : "hidden"});
var task_id = data.task; var task_id = data.task;
@ -545,7 +534,7 @@ $(document).ready(function(){
return null; 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) { function success(data) {
rMenu.css({"visibility" : "hidden"}); rMenu.css({"visibility" : "hidden"});
var task_id = data.task; var task_id = data.task;
@ -682,7 +671,7 @@ $(document).ready(function(){
}; };
APIUpdateAttr({ APIUpdateAttr({
'url': '/api/assets/v1/nodes/' + current_node.id + '/assets/remove/', 'url': '/api/assets/v1/nodes/' + current_node.node_id + '/assets/remove/',
'method': 'PUT', 'method': 'PUT',
'body': JSON.stringify(data), 'body': JSON.stringify(data),
'success': success 'success': success
@ -719,9 +708,7 @@ $(document).ready(function(){
return return
} }
var data = { var data = {'assets': assets_selected};
'assets': assets_selected
};
var success = function () { var success = function () {
asset_table2.selected = []; asset_table2.selected = [];
asset_table2.ajax.reload() asset_table2.ajax.reload()
@ -729,9 +716,9 @@ $(document).ready(function(){
var url = ''; var url = '';
if (update_node_action === "move") { 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 { } 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({ APIUpdateAttr({

View File

@ -4,8 +4,8 @@
{% block help_message %} {% block help_message %}
<div class="alert alert-info help-message"> <div class="alert alert-info help-message">
网域功能是为了解决部分环境(如:混合云)无法直接连接而新增的功能,原理是通过网关服务器进行跳转登 网域功能是为了解决部分环境(如:混合云)无法直接连接而新增的功能,原理是通过网关服务器进行跳转登录。<br>
录。 JMS => 网域网关 => 目标资产
</div> </div>
{% endblock %} {% endblock %}

View File

@ -55,11 +55,14 @@
</div> </div>
</div> </div>
</div> </div>
{% include 'assets/_user_asset_detail_modal.html' %}
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <script>
var zTree, rMenu, asset_table; var zTree, asset_table;
var inited = false; var inited = false;
var url; var url;
function initTable() { function initTable() {
@ -68,14 +71,15 @@ function initTable() {
} else { } else {
inited = true; inited = true;
} }
console.log("init table")
url = "{% url 'api-perms:my-assets' %}";
var options = { var options = {
ele: $('#user_assets_table'), ele: $('#user_assets_table'),
columnDefs: [ columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) { {targets: 1, createdCell: function (td, cellData, rowData) {
{% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %} var detail_btn = '<a class="asset_detail" asset-id="rowData_id" data-toggle="modal" data-target="#user_asset_detail_modal" tabindex="0">'+ cellData +'</a>'
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>'; $(td).html(detail_btn.replace("rowData_id", rowData.id));
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); }},
}},
{targets: 3, createdCell: function (td, cellData) { {targets: 3, createdCell: function (td, cellData) {
if (!cellData) { if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>') $(td).html('<i class="fa fa-times text-danger"></i>')
@ -103,33 +107,13 @@ function initTable() {
} }
function onSelected(event, treeNode) { function onSelected(event, treeNode) {
console.log("select");
url = '{% url "api-perms:my-node-assets" node_id=DEFAULT_PK %}'; url = '{% url "api-perms:my-node-assets" node_id=DEFAULT_PK %}';
url = url.replace("{{ DEFAULT_PK }}", treeNode.id); url = url.replace("{{ DEFAULT_PK }}", treeNode.node_id);
initTable();
setCookie('node_selected', treeNode.id); setCookie('node_selected', treeNode.id);
asset_table.ajax.url(url); asset_table.ajax.url(url);
asset_table.ajax.reload(); 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() { function initTree() {
var setting = { var setting = {
view: { view: {
@ -149,23 +133,56 @@ function initTree() {
var zNodes = []; var zNodes = [];
$.get("{% url 'api-perms:my-nodes' %}", function(data, status){ $.get("{% url 'api-perms:my-nodes' %}", function(data, status){
$.each(data, function (index, value) { $.each(data, function (index, value) {
value["pId"] = value["parent"]; value["node_id"] = value["id"];
if (value["key"] === "0") { value["id"] = value["tree_id"];
value["open"] = true; 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; zNodes = data;
$.fn.zTree.init($("#assetTree"), setting, zNodes); $.fn.zTree.init($("#assetTree"), setting, zNodes);
zTree = $.fn.zTree.getZTreeObj("assetTree"); zTree = $.fn.zTree.getZTreeObj("assetTree");
rMenu = $("#rMenu"); var root = zTree.getNodes()[0];
selectQueryNode(); zTree.expandNode(root);
}); });
} }
$(document).ready(function () { $(document).ready(function () {
initTree(); 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> </script>
{% endblock %} {% endblock %}

View File

@ -1,5 +1,5 @@
# coding:utf-8 # coding:utf-8
from django.conf.urls import url from django.urls import path
from .. import api from .. import api
from rest_framework_bulk.routes import BulkRouter from rest_framework_bulk.routes import BulkRouter
@ -7,54 +7,54 @@ app_name = 'assets'
router = BulkRouter() router = BulkRouter()
router.register(r'v1/assets', api.AssetViewSet, 'asset') router.register(r'assets', api.AssetViewSet, 'asset')
router.register(r'v1/admin-user', api.AdminUserViewSet, 'admin-user') router.register(r'admin-user', api.AdminUserViewSet, 'admin-user')
router.register(r'v1/system-user', api.SystemUserViewSet, 'system-user') router.register(r'system-user', api.SystemUserViewSet, 'system-user')
router.register(r'v1/labels', api.LabelViewSet, 'label') router.register(r'labels', api.LabelViewSet, 'label')
router.register(r'v1/nodes', api.NodeViewSet, 'node') router.register(r'nodes', api.NodeViewSet, 'node')
router.register(r'v1/domain', api.DomainViewSet, 'domain') router.register(r'domain', api.DomainViewSet, 'domain')
router.register(r'v1/gateway', api.GatewayViewSet, 'gateway') router.register(r'gateway', api.GatewayViewSet, 'gateway')
urlpatterns = [ urlpatterns = [
url(r'^v1/assets-bulk/$', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'), path('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(), path('system-user/<uuid:pk>/auth-info/',
name='system-user-auth-info'), api.SystemUserAuthInfoApi.as_view(), name='system-user-auth-info'),
url(r'^v1/assets/(?P<pk>[0-9a-zA-Z\-]{36})/refresh/$', path('assets/<uuid:pk>/refresh/',
api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'), api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'),
url(r'^v1/assets/(?P<pk>[0-9a-zA-Z\-]{36})/alive/$', path('assets/<uuid:pk>/alive/',
api.AssetAdminUserTestApi.as_view(), name='asset-alive-test'), api.AssetAdminUserTestApi.as_view(), name='asset-alive-test'),
url(r'^v1/assets/(?P<pk>[0-9a-zA-Z\-]{36})/gateway/$', path('assets/<uuid:pk>/gateway/',
api.AssetGatewayApi.as_view(), name='asset-gateway'), api.AssetGatewayApi.as_view(), name='asset-gateway'),
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/$', path('admin-user/<uuid:pk>/nodes/',
api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'), api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'),
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/auth/$', path('admin-user/<uuid:pk>/auth/',
api.AdminUserAuthApi.as_view(), name='admin-user-auth'), api.AdminUserAuthApi.as_view(), name='admin-user-auth'),
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/connective/$', path('admin-user/<uuid:pk>/connective/',
api.AdminUserTestConnectiveApi.as_view(), name='admin-user-connective'), api.AdminUserTestConnectiveApi.as_view(), name='admin-user-connective'),
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/push/$', path('system-user/<uuid:pk>/push/',
api.SystemUserPushApi.as_view(), name='system-user-push'), api.SystemUserPushApi.as_view(), name='system-user-push'),
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/connective/$', path('system-user/<uuid:pk>/connective/',
api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'), api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'),
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/children/$', path('nodes/<uuid:pk>/children/',
api.NodeChildrenApi.as_view(), name='node-children'), api.NodeChildrenApi.as_view(), name='node-children'),
url(r'^v1/nodes/children/$', api.NodeChildrenApi.as_view(), name='node-children-2'), path('nodes/children/', api.NodeChildrenApi.as_view(), name='node-children-2'),
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/children/add/$', path('nodes/<uuid:pk>/children/add/',
api.NodeAddChildrenApi.as_view(), name='node-add-children'), api.NodeAddChildrenApi.as_view(), name='node-add-children'),
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$', path('nodes/<uuid:pk>/assets/',
api.NodeAssetsApi.as_view(), name='node-assets'), api.NodeAssetsApi.as_view(), name='node-assets'),
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/add/$', path('nodes/<uuid:pk>/assets/add/',
api.NodeAddAssetsApi.as_view(), name='node-add-assets'), api.NodeAddAssetsApi.as_view(), name='node-add-assets'),
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/replace/$', path('nodes/<uuid:pk>/assets/replace/',
api.NodeReplaceAssetsApi.as_view(), name='node-replace-assets'), api.NodeReplaceAssetsApi.as_view(), name='node-replace-assets'),
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/remove/$', path('nodes/<uuid:pk>/assets/remove/',
api.NodeRemoveAssetsApi.as_view(), name='node-remove-assets'), api.NodeRemoveAssetsApi.as_view(), name='node-remove-assets'),
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/refresh-hardware-info/$', path('nodes/<uuid:pk>/refresh-hardware-info/',
api.RefreshNodeHardwareInfoApi.as_view(), name='node-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/$', path('nodes/<uuid:pk>/test-connective/',
api.TestNodeConnectiveApi.as_view(), name='node-test-connective'), api.TestNodeConnectiveApi.as_view(), name='node-test-connective'),
url(r'^v1/gateway/(?P<pk>[0-9a-zA-Z\-]{36})/test-connective/$', path('gateway/<uuid:pk>/test-connective/',
api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'), api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'),
] ]
urlpatterns += router.urls urlpatterns += router.urls

View File

@ -1,52 +1,52 @@
# coding:utf-8 # coding:utf-8
from django.conf.urls import url from django.urls import path
from .. import views from .. import views
app_name = 'assets' app_name = 'assets'
urlpatterns = [ urlpatterns = [
# Resource asset url # Resource asset url
url(r'^$', views.AssetListView.as_view(), name='asset-index'), path('', views.AssetListView.as_view(), name='asset-index'),
url(r'^asset/$', views.AssetListView.as_view(), name='asset-list'), path('asset/', views.AssetListView.as_view(), name='asset-list'),
url(r'^asset/create/$', views.AssetCreateView.as_view(), name='asset-create'), path('asset/create/', views.AssetCreateView.as_view(), name='asset-create'),
url(r'^asset/export/$', views.AssetExportView.as_view(), name='asset-export'), path('asset/export/', views.AssetExportView.as_view(), name='asset-export'),
url(r'^asset/import/$', views.BulkImportAssetView.as_view(), name='asset-import'), path('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'), path('asset/<uuid:pk>/', 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'), path('asset/<uuid:pk>/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'), path('asset/<uuid:pk>/delete/', views.AssetDeleteView.as_view(), name='asset-delete'),
url(r'^asset/update/$', views.AssetBulkUpdateView.as_view(), name='asset-bulk-update'), path('asset/update/', views.AssetBulkUpdateView.as_view(), name='asset-bulk-update'),
# User asset view # 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 # Resource admin user url
url(r'^admin-user/$', views.AdminUserListView.as_view(), name='admin-user-list'), path('admin-user/', views.AdminUserListView.as_view(), name='admin-user-list'),
url(r'^admin-user/create/$', views.AdminUserCreateView.as_view(), name='admin-user-create'), path('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'), path('admin-user/<uuid:pk>/', 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'), path('admin-user/<uuid:pk>/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'), path('admin-user/<uuid:pk>/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/<uuid:pk>/assets/', views.AdminUserAssetsView.as_view(), name='admin-user-assets'),
# Resource system user url # Resource system user url
url(r'^system-user/$', views.SystemUserListView.as_view(), name='system-user-list'), path('system-user/', views.SystemUserListView.as_view(), name='system-user-list'),
url(r'^system-user/create/$', views.SystemUserCreateView.as_view(), name='system-user-create'), path('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'), path('system-user/<uuid:pk>/', 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'), path('system-user/<uuid:pk>/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'), path('system-user/<uuid:pk>/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/<uuid:pk>/asset/', views.SystemUserAssetView.as_view(), name='system-user-asset'),
url(r'^label/$', views.LabelListView.as_view(), name='label-list'), path('label/', views.LabelListView.as_view(), name='label-list'),
url(r'^label/create/$', views.LabelCreateView.as_view(), name='label-create'), path('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'), path('label/<uuid:pk>/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/<uuid:pk>/delete/', views.LabelDeleteView.as_view(), name='label-delete'),
url(r'^domain/$', views.DomainListView.as_view(), name='domain-list'), path('domain/', views.DomainListView.as_view(), name='domain-list'),
url(r'^domain/create/$', views.DomainCreateView.as_view(), name='domain-create'), path('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'), path('domain/<uuid:pk>/', 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'), path('domain/<uuid:pk>/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'), path('domain/<uuid:pk>/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/<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'), path('domain/<uuid:pk>/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/gateway/<uuid:pk>/update/', views.DomainGatewayUpdateView.as_view(), name='domain-gateway-update'),
] ]

View File

@ -11,7 +11,7 @@ from django.views.generic.detail import DetailView, SingleObjectMixin
from common.const import create_success_msg, update_success_msg from common.const import create_success_msg, update_success_msg
from .. import forms from .. import forms
from ..models import AdminUser, Node from ..models import AdminUser, Node
from ..hands import AdminUserRequiredMixin from common.permissions import AdminUserRequiredMixin
__all__ = [ __all__ = [
'AdminUserCreateView', 'AdminUserDetailView', 'AdminUserCreateView', 'AdminUserDetailView',

View File

@ -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 common.const import create_success_msg, update_success_msg
from .. import forms from .. import forms
from ..models import Asset, AdminUser, SystemUser, Label, Node, Domain from ..models import Asset, AdminUser, SystemUser, Label, Node, Domain
from ..hands import AdminUserRequiredMixin from common.permissions import AdminUserRequiredMixin
__all__ = [ __all__ = [
@ -186,7 +186,7 @@ class AssetDeleteView(AdminUserRequiredMixin, DeleteView):
success_url = reverse_lazy('assets:asset-list') success_url = reverse_lazy('assets:asset-list')
class AssetDetailView(DetailView): class AssetDetailView(LoginRequiredMixin, DetailView):
model = Asset model = Asset
context_object_name = 'asset' context_object_name = 'asset'
template_name = 'assets/asset_detail.html' template_name = 'assets/asset_detail.html'
@ -203,7 +203,7 @@ class AssetDetailView(DetailView):
@method_decorator(csrf_exempt, name='dispatch') @method_decorator(csrf_exempt, name='dispatch')
class AssetExportView(View): class AssetExportView(LoginRequiredMixin, View):
def get(self, request): def get(self, request):
spm = request.GET.get('spm', '') spm = request.GET.get('spm', '')
assets_id_default = [Asset.objects.first().id] if Asset.objects.first() else [] assets_id_default = [Asset.objects.first().id] if Asset.objects.first() else []
@ -211,7 +211,7 @@ class AssetExportView(View):
fields = [ fields = [
field for field in Asset._meta.fields field for field in Asset._meta.fields
if field.name not in [ if field.name not in [
'date_created' 'date_created', 'org_id'
] ]
] ]
filename = 'assets-{}.csv'.format( filename = 'assets-{}.csv'.format(

View File

@ -7,7 +7,7 @@ from django.views.generic.detail import SingleObjectMixin
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.urls import reverse_lazy, reverse 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.const import create_success_msg, update_success_msg
from common.utils import get_object_or_none from common.utils import get_object_or_none
from ..models import Domain, Gateway from ..models import Domain, Gateway

View File

@ -6,7 +6,7 @@ from django.views.generic import TemplateView, CreateView, \
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.urls import reverse_lazy 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 common.const import create_success_msg, update_success_msg
from ..models import Label from ..models import Label
from ..forms import LabelForm from ..forms import LabelForm

View File

@ -10,7 +10,7 @@ from django.views.generic.detail import DetailView
from common.const import create_success_msg, update_success_msg from common.const import create_success_msg, update_success_msg
from ..forms import SystemUserForm from ..forms import SystemUserForm
from ..models import SystemUser, Node from ..models import SystemUser, Node
from ..hands import AdminUserRequiredMixin from common.permissions import AdminUserRequiredMixin
__all__ = [ __all__ = [

View File

@ -3,7 +3,7 @@
from rest_framework import viewsets from rest_framework import viewsets
from common.permissions import IsSuperUserOrAppUser from common.permissions import IsOrgAdminOrAppUser
from .models import FTPLog from .models import FTPLog
from .serializers import FTPLogSerializer from .serializers import FTPLogSerializer
@ -11,4 +11,4 @@ from .serializers import FTPLogSerializer
class FTPLogViewSet(viewsets.ModelViewSet): class FTPLogViewSet(viewsets.ModelViewSet):
queryset = FTPLog.objects.all() queryset = FTPLog.objects.all()
serializer_class = FTPLogSerializer serializer_class = FTPLogSerializer
permission_classes = (IsSuperUserOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)

View File

@ -3,8 +3,10 @@ import uuid
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelMixin
class FTPLog(models.Model):
class FTPLog(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
user = models.CharField(max_length=128, verbose_name=_('User')) user = models.CharField(max_length=128, verbose_name=_('User'))
remote_addr = models.CharField(max_length=15, verbose_name=_("Remote addr"), blank=True, null=True) remote_addr = models.CharField(max_length=15, verbose_name=_("Remote addr"), blank=True, null=True)

View File

@ -9,10 +9,9 @@ from .. import api
app_name = "audits" app_name = "audits"
router = DefaultRouter() router = DefaultRouter()
router.register(r'v1/ftp-log', api.FTPLogViewSet, 'ftp-log') router.register(r'ftp-log', api.FTPLogViewSet, 'ftp-log')
urlpatterns = [ 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 urlpatterns += router.urls

View File

@ -1,8 +1,7 @@
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals from __future__ import unicode_literals
from django.urls import path
from django.conf.urls import url
from .. import views from .. import views
__all__ = ["urlpatterns"] __all__ = ["urlpatterns"]
@ -10,5 +9,5 @@ __all__ = ["urlpatterns"]
app_name = "audits" app_name = "audits"
urlpatterns = [ urlpatterns = [
url(r'^ftp-log/$', views.FTPLogListView.as_view(), name='ftp-log-list'), path('ftp-log/', views.FTPLogListView.as_view(), name='ftp-log-list'),
] ]

View File

@ -2,7 +2,8 @@ from django.conf import settings
from django.views.generic import ListView from django.views.generic import ListView
from django.utils.translation import ugettext as _ 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 from .models import FTPLog

View File

@ -8,12 +8,12 @@ from django.core.mail import get_connection, send_mail
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.conf import settings from django.conf import settings
from .permissions import IsSuperUser from .permissions import IsOrgAdmin
from .serializers import MailTestSerializer, LDAPTestSerializer from .serializers import MailTestSerializer, LDAPTestSerializer
class MailTestingAPI(APIView): class MailTestingAPI(APIView):
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
serializer_class = MailTestSerializer serializer_class = MailTestSerializer
success_message = _("Test mail sent to {}, please check") success_message = _("Test mail sent to {}, please check")
@ -37,7 +37,7 @@ class MailTestingAPI(APIView):
class LDAPTestingAPI(APIView): class LDAPTestingAPI(APIView):
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
serializer_class = LDAPTestSerializer serializer_class = LDAPTestSerializer
success_message = _("Test ldap success") success_message = _("Test ldap success")
@ -86,7 +86,18 @@ class LDAPTestingAPI(APIView):
class DjangoSettingsAPI(APIView): class DjangoSettingsAPI(APIView):
def get(self, request): 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)

View File

@ -62,7 +62,7 @@ class EncryptMixin:
def get_prep_value(self, value): def get_prep_value(self, value):
if value is None: if value is None:
return value return value
return signer.sign(value).decode('utf-8') return signer.sign(value)
class EncryptTextField(EncryptMixin, models.TextField): class EncryptTextField(EncryptMixin, models.TextField):

View File

@ -4,7 +4,6 @@ from django.db import models
from django.http import JsonResponse from django.http import JsonResponse
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.mixins import UserPassesTestMixin
class NoDeleteQuerySet(models.query.QuerySet): class NoDeleteQuerySet(models.query.QuerySet):
@ -119,11 +118,4 @@ class DatetimeSearchMixin:
return super().get(request, *args, **kwargs) 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

View File

@ -2,6 +2,11 @@
# #
from rest_framework import permissions 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): class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission):
@ -21,28 +26,40 @@ class IsAppUser(IsValidUser):
class IsSuperUser(IsValidUser): class IsSuperUser(IsValidUser):
"""Allows access only to superuser"""
def has_permission(self, request, view): def has_permission(self, request, view):
return super(IsSuperUser, self).has_permission(request, view) \ return super(IsSuperUser, self).has_permission(request, view) \
and request.user.is_superuser and request.user.is_superuser
class IsSuperUserOrAppUser(IsValidUser): class IsSuperUserOrAppUser(IsSuperUser):
"""Allows access between superuser and app user"""
def has_permission(self, request, view): def has_permission(self, request, view):
return super(IsSuperUserOrAppUser, self).has_permission(request, view) \ return super(IsSuperUserOrAppUser, self).has_permission(request, view) \
and (request.user.is_superuser or request.user.is_app) 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): def has_permission(self, request, view):
if IsValidUser.has_permission(self, request, view) \ if IsValidUser.has_permission(self, request, view) \
and request.method in permissions.SAFE_METHODS: and request.method in permissions.SAFE_METHODS:
return True return True
else: else:
return IsSuperUserOrAppUser.has_permission(self, request, view) return IsOrgAdminOrAppUser.has_permission(self, request, view)
class IsCurrentUserOrReadOnly(permissions.BasePermission): class IsCurrentUserOrReadOnly(permissions.BasePermission):
@ -50,3 +67,31 @@ class IsCurrentUserOrReadOnly(permissions.BasePermission):
if request.method in permissions.SAFE_METHODS: if request.method in permissions.SAFE_METHODS:
return True return True
return obj == request.user 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)

View File

@ -1,13 +1,13 @@
from __future__ import absolute_import from __future__ import absolute_import
from django.conf.urls import url from django.urls import path
from .. import api from .. import api
app_name = 'common' app_name = 'common'
urlpatterns = [ urlpatterns = [
url(r'^v1/mail/testing/$', api.MailTestingAPI.as_view(), name='mail-testing'), path('mail/testing/', api.MailTestingAPI.as_view(), name='mail-testing'),
url(r'^v1/ldap/testing/$', api.LDAPTestingAPI.as_view(), name='ldap-testing'), path('ldap/testing/', api.LDAPTestingAPI.as_view(), name='ldap-testing'),
url(r'^v1/django-settings/$', api.DjangoSettingsAPI.as_view(), name='django-settings'), # path('django-settings/', api.DjangoSettingsAPI.as_view(), name='django-settings'),
] ]

View File

@ -17,6 +17,7 @@ import threading
from io import StringIO from io import StringIO
import uuid import uuid
from functools import wraps from functools import wraps
import copy
import paramiko import paramiko
import sshpubkeys import sshpubkeys
@ -67,10 +68,8 @@ class Signer(metaclass=Singleton):
self.secret_key = secret_key self.secret_key = secret_key
def sign(self, value): def sign(self, value):
if isinstance(value, bytes):
value = value.decode("utf-8")
s = JSONWebSignatureSerializer(self.secret_key) s = JSONWebSignatureSerializer(self.secret_key)
return s.dumps(value) return s.dumps(value).decode()
def unsign(self, value): def unsign(self, value):
if value is None: if value is None:
@ -410,3 +409,122 @@ def with_cache(func):
cache[key] = res cache[key] = res
return res return res
return wrapper 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)

View File

@ -1,14 +1,12 @@
from django.views.generic import TemplateView
from django.core.cache import cache from django.shortcuts import render, redirect
from django.views.generic import TemplateView, View, DetailView
from django.shortcuts import render, redirect, Http404, reverse
from django.contrib import messages from django.contrib import messages
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.conf import settings from django.conf import settings
from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \ from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \
TerminalSettingForm, SecuritySettingForm TerminalSettingForm, SecuritySettingForm
from .mixins import AdminUserRequiredMixin from common.permissions import AdminUserRequiredMixin
from .signals import ldap_auth_enable from .signals import ldap_auth_enable

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -56,6 +56,7 @@ ALLOWED_HOSTS = CONFIG.ALLOWED_HOSTS or []
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
'orgs.apps.OrgsConfig',
'users.apps.UsersConfig', 'users.apps.UsersConfig',
'assets.apps.AssetsConfig', 'assets.apps.AssetsConfig',
'perms.apps.PermsConfig', 'perms.apps.PermsConfig',
@ -65,6 +66,7 @@ INSTALLED_APPS = [
'audits.apps.AuditsConfig', 'audits.apps.AuditsConfig',
'rest_framework', 'rest_framework',
'rest_framework_swagger', 'rest_framework_swagger',
'drf_yasg',
'django_filters', 'django_filters',
'bootstrap3', 'bootstrap3',
'captcha', 'captcha',
@ -76,6 +78,12 @@ INSTALLED_APPS = [
'django.contrib.staticfiles', '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 = [ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
@ -87,14 +95,35 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'jumpserver.middleware.TimezoneMiddleware', 'jumpserver.middleware.TimezoneMiddleware',
'jumpserver.middleware.DemoMiddleware', 'jumpserver.middleware.DemoMiddleware',
'orgs.middleware.OrgMiddleware',
] ]
ROOT_URLCONF = 'jumpserver.urls' 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 = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', '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, 'APP_DIRS': True,
'OPTIONS': { 'OPTIONS': {
'context_processors': [ 'context_processors': [
@ -107,6 +136,8 @@ TEMPLATES = [
'django.template.context_processors.static', 'django.template.context_processors.static',
'django.template.context_processors.request', 'django.template.context_processors.request',
'django.template.context_processors.media', 'django.template.context_processors.media',
'orgs.context_processor.org_processor',
*get_xpack_context_processor(),
], ],
}, },
}, },
@ -227,13 +258,13 @@ LOGGING = {
'level': LOG_LEVEL, 'level': LOG_LEVEL,
}, },
'django_auth_ldap': { 'django_auth_ldap': {
'handlers': ['console', 'ansible_logs'], 'handlers': ['console', 'file'],
'level': "INFO", 'level': "INFO",
}, },
# 'django.db': { 'django.db': {
# 'handlers': ['console', 'file'], 'handlers': ['console', 'file'],
# 'level': 'DEBUG' 'level': 'DEBUG'
# } }
} }
} }
@ -288,9 +319,10 @@ REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions, # Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users. # or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': ( 'DEFAULT_PERMISSION_CLASSES': (
'users.permissions.IsSuperUser', 'common.permissions.IsOrgAdmin',
), ),
'DEFAULT_AUTHENTICATION_CLASSES': ( 'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'users.authentication.AccessKeyAuthentication', 'users.authentication.AccessKeyAuthentication',
'users.authentication.AccessTokenAuthentication', 'users.authentication.AccessTokenAuthentication',
'users.authentication.PrivateTokenAuthentication', 'users.authentication.PrivateTokenAuthentication',
@ -373,7 +405,7 @@ CACHES = {
'password': CONFIG.REDIS_PASSWORD if CONFIG.REDIS_PASSWORD else '', 'password': CONFIG.REDIS_PASSWORD if CONFIG.REDIS_PASSWORD else '',
'host': CONFIG.REDIS_HOST or '127.0.0.1', 'host': CONFIG.REDIS_HOST or '127.0.0.1',
'port': CONFIG.REDIS_PORT or 6379, '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 DISPLAY_PER_PAGE = CONFIG.DISPLAY_PER_PAGE or 25
DEFAULT_EXPIRED_YEARS = 70 DEFAULT_EXPIRED_YEARS = 70
USER_GUIDE_URL = "" USER_GUIDE_URL = ""
SWAGGER_SETTINGS = {
'SECURITY_DEFINITIONS': {
'basic': {
'type': 'basic'
}
},
}

View File

@ -1,45 +1,102 @@
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals 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 import settings
from django.conf.urls.static import static from django.conf.urls.static import static
from rest_framework.response import Response
from rest_framework.schemas import get_schema_view from django.views.decorators.csrf import csrf_exempt
from rest_framework_swagger.renderers import SwaggerUIRenderer, OpenAPIRenderer 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 from .views import IndexView, LunaView
schema_view = get_schema_view(title='Users API', renderer_classes=[OpenAPIRenderer, SwaggerUIRenderer]) schema_view = get_schema_view(
urlpatterns = [ openapi.Info(
url(r'^$', IndexView.as_view(), name='index'), title="Jumpserver API Docs",
url(r'^luna/', LunaView.as_view(), name='luna-error'), default_version='v1',
url(r'^users/', include('users.urls.views_urls', namespace='users')), description="Jumpserver Restful api docs",
url(r'^assets/', include('assets.urls.views_urls', namespace='assets')), terms_of_service="https://www.jumpserver.org",
url(r'^perms/', include('perms.urls.views_urls', namespace='perms')), contact=openapi.Contact(email="support@fit2cloud.com"),
url(r'^terminal/', include('terminal.urls.views_urls', namespace='terminal')), license=openapi.License(name="GPLv2 License"),
url(r'^ops/', include('ops.urls.view_urls', namespace='ops')), ),
url(r'^audits/', include('audits.urls.view_urls', namespace='audits')), public=True,
url(r'^settings/', include('common.urls.view_urls', namespace='settings')), permission_classes=(permissions.AllowAny,),
url(r'^common/', include('common.urls.view_urls', namespace='common')), )
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 class HttpResponseTemporaryRedirect(HttpResponse):
url(r'^captcha/', include('captcha.urls')), 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) \ urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \
+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
if settings.DEBUG: if settings.DEBUG:
urlpatterns += [ 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'),
] ]

View File

@ -4,12 +4,13 @@ from django.http import HttpResponse
from django.views.generic import TemplateView, View from django.views.generic import TemplateView, View
from django.utils import timezone from django.utils import timezone
from django.db.models import Count from django.db.models import Count
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import redirect from django.shortcuts import redirect
from django.contrib.auth.mixins import LoginRequiredMixin
from users.models import User from users.models import User
from assets.models import Asset from assets.models import Asset
from terminal.models import Session from terminal.models import Session
from orgs.utils import current_org
class IndexView(LoginRequiredMixin, TemplateView): class IndexView(LoginRequiredMixin, TemplateView):
@ -20,14 +21,16 @@ class IndexView(LoginRequiredMixin, TemplateView):
session_month_dates = [] session_month_dates = []
session_month_dates_archive = [] session_month_dates_archive = []
def get(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if not request.user.is_superuser: 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 redirect('assets:user-asset-list')
return super(IndexView, self).get(request, *args, **kwargs) return super(IndexView, self).dispatch(request, *args, **kwargs)
@staticmethod @staticmethod
def get_user_count(): def get_user_count():
return User.objects.filter(role__in=('Admin', 'User')).count() return current_org.get_org_users().count()
@staticmethod @staticmethod
def get_asset_count(): def get_asset_count():
@ -49,7 +52,6 @@ class IndexView(LoginRequiredMixin, TemplateView):
def get_week_login_asset_count(self): def get_week_login_asset_count(self):
return self.session_week.count() return self.session_week.count()
# return self.session_week.values('asset').distinct().count()
def get_month_day_metrics(self): def get_month_day_metrics(self):
month_str = [d.strftime('%m-%d') for d in self.session_month_dates] or ['0'] month_str = [d.strftime('%m-%d') for d in self.session_month_dates] or ['0']
@ -175,4 +177,7 @@ class LunaView(View):
Luna是单独部署的一个程序你需要部署lunacoco配置nginx做url分发, Luna是单独部署的一个程序你需要部署lunacoco配置nginx做url分发,
如果你看到了这个页面证明你访问的不是nginx监听的端口祝你好运 如果你看到了这个页面证明你访问的不是nginx监听的端口祝你好运
""" """
return HttpResponse(msg) return HttpResponse(msg)

View File

@ -8,7 +8,7 @@ from django.utils.translation import ugettext as _
from rest_framework import viewsets, generics from rest_framework import viewsets, generics
from rest_framework.views import Response from rest_framework.views import Response
from .hands import IsSuperUser from common.permissions import IsOrgAdmin
from .models import Task, AdHoc, AdHocRunHistory, CeleryTask from .models import Task, AdHoc, AdHocRunHistory, CeleryTask
from .serializers import TaskSerializer, AdHocSerializer, \ from .serializers import TaskSerializer, AdHocSerializer, \
AdHocRunHistorySerializer AdHocRunHistorySerializer
@ -18,13 +18,15 @@ from .tasks import run_ansible_task
class TaskViewSet(viewsets.ModelViewSet): class TaskViewSet(viewsets.ModelViewSet):
queryset = Task.objects.all() queryset = Task.objects.all()
serializer_class = TaskSerializer serializer_class = TaskSerializer
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
label = None
help_text = ''
class TaskRun(generics.RetrieveAPIView): class TaskRun(generics.RetrieveAPIView):
queryset = Task.objects.all() queryset = Task.objects.all()
serializer_class = TaskViewSet serializer_class = TaskViewSet
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
task = self.get_object() task = self.get_object()
@ -35,7 +37,7 @@ class TaskRun(generics.RetrieveAPIView):
class AdHocViewSet(viewsets.ModelViewSet): class AdHocViewSet(viewsets.ModelViewSet):
queryset = AdHoc.objects.all() queryset = AdHoc.objects.all()
serializer_class = AdHocSerializer serializer_class = AdHocSerializer
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
def get_queryset(self): def get_queryset(self):
task_id = self.request.query_params.get('task') task_id = self.request.query_params.get('task')
@ -48,7 +50,7 @@ class AdHocViewSet(viewsets.ModelViewSet):
class AdHocRunHistorySet(viewsets.ModelViewSet): class AdHocRunHistorySet(viewsets.ModelViewSet):
queryset = AdHocRunHistory.objects.all() queryset = AdHocRunHistory.objects.all()
serializer_class = AdHocRunHistorySerializer serializer_class = AdHocRunHistorySerializer
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
def get_queryset(self): def get_queryset(self):
task_id = self.request.query_params.get('task') task_id = self.request.query_params.get('task')
@ -65,7 +67,7 @@ class AdHocRunHistorySet(viewsets.ModelViewSet):
class CeleryTaskLogApi(generics.RetrieveAPIView): class CeleryTaskLogApi(generics.RetrieveAPIView):
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
buff_size = 1024 * 10 buff_size = 1024 * 10
end = False end = False
queryset = CeleryTask.objects.all() queryset = CeleryTask.objects.all()

View File

@ -7,5 +7,9 @@ class OpsConfig(AppConfig):
name = 'ops' name = 'ops'
def ready(self): def ready(self):
from orgs.models import Organization
from orgs.utils import set_current_org
set_current_org(Organization.root())
super().ready() super().ready()
from .celery import signal_handler from .celery import signal_handler

View File

@ -1,4 +1,2 @@
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from users.permissions import IsSuperUser
from users.utils import AdminUserRequiredMixin

View File

@ -263,7 +263,8 @@ class AdHoc(models.Model):
} }
:return: :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 @property
def options(self): def options(self):

View File

@ -1,7 +1,7 @@
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals from __future__ import unicode_literals
from django.conf.urls import url from django.urls import path
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
from .. import api from .. import api
@ -9,13 +9,13 @@ from .. import api
app_name = "ops" app_name = "ops"
router = DefaultRouter() router = DefaultRouter()
router.register(r'v1/tasks', api.TaskViewSet, 'task') router.register(r'tasks', api.TaskViewSet, 'task')
router.register(r'v1/adhoc', api.AdHocViewSet, 'adhoc') router.register(r'adhoc', api.AdHocViewSet, 'adhoc')
router.register(r'v1/history', api.AdHocRunHistorySet, 'history') router.register(r'history', api.AdHocRunHistorySet, 'history')
urlpatterns = [ urlpatterns = [
url(r'^v1/tasks/(?P<pk>[0-9a-zA-Z\-]{36})/run/$', api.TaskRun.as_view(), name='task-run'), path('tasks/<uuid:pk>/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('celery/task/<uuid:pk>/log/', api.CeleryTaskLogApi.as_view(), name='celery-task-log'),
] ]
urlpatterns += router.urls urlpatterns += router.urls

View File

@ -1,8 +1,7 @@
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals from __future__ import unicode_literals
from django.urls import path
from django.conf.urls import url
from .. import views from .. import views
__all__ = ["urlpatterns"] __all__ = ["urlpatterns"]
@ -10,13 +9,13 @@ __all__ = ["urlpatterns"]
app_name = "ops" app_name = "ops"
urlpatterns = [ urlpatterns = [
# TResource Task url # Resource Task url
url(r'^task/$', views.TaskListView.as_view(), name='task-list'), path('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'), path('task/<uuid:pk>/', 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'), path('task/<uuid:pk>/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'), path('task/<uuid:pk>/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'), path('adhoc/<uuid:pk>/', 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'), path('adhoc/<uuid:pk>/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'), path('adhoc/history/<uuid:pk>/', 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'), path('celery/task/<uuid:pk>/log/', views.CeleryTaskLogView.as_view(), name='celery-task-log'),
] ]

View File

@ -6,7 +6,7 @@ from django.views.generic import ListView, DetailView, TemplateView
from common.mixins import DatetimeSearchMixin from common.mixins import DatetimeSearchMixin
from .models import Task, AdHoc, AdHocRunHistory, CeleryTask from .models import Task, AdHoc, AdHocRunHistory, CeleryTask
from .hands import AdminUserRequiredMixin from common.permissions import AdminUserRequiredMixin
class TaskListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): class TaskListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):

0
apps/orgs/__init__.py Normal file
View File

3
apps/orgs/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

14
apps/orgs/api.py Normal file
View File

@ -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,)

5
apps/orgs/apps.py Normal file
View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class OrgsConfig(AppConfig):
name = 'orgs'

View File

@ -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

16
apps/orgs/middleware.py Normal file
View File

@ -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

View File

104
apps/orgs/mixins.py Normal file
View File

@ -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()

103
apps/orgs/models.py Normal file
View File

@ -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

10
apps/orgs/serializers.py Normal file
View File

@ -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']

3
apps/orgs/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
#

View File

@ -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

View File

@ -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')
]

47
apps/orgs/utils.py Normal file
View File

@ -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'))

30
apps/orgs/views.py Normal file
View File

@ -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}))

View File

@ -7,7 +7,8 @@ from rest_framework.generics import ListAPIView, get_object_or_404, RetrieveUpda
from rest_framework import viewsets from rest_framework import viewsets
from common.utils import set_or_append_attr_bulk, get_object_or_none 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 .utils import AssetPermissionUtil
from .models import AssetPermission from .models import AssetPermission
from .hands import AssetGrantedSerializer, User, UserGroup, Asset, Node, \ from .hands import AssetGrantedSerializer, User, UserGroup, Asset, Node, \
@ -21,7 +22,7 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
""" """
queryset = AssetPermission.objects.all() queryset = AssetPermission.objects.all()
serializer_class = serializers.AssetPermissionCreateUpdateSerializer serializer_class = serializers.AssetPermissionCreateUpdateSerializer
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
def get_serializer_class(self): def get_serializer_class(self):
if self.action in ("list", 'retrieve'): if self.action in ("list", 'retrieve'):
@ -54,11 +55,11 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
return permissions return permissions
class UserGrantedAssetsApi(ListAPIView): class UserGrantedAssetsApi(RootOrgViewMixin, ListAPIView):
""" """
用户授权的所有资产 用户授权的所有资产
""" """
permission_classes = (IsSuperUserOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = AssetGrantedSerializer serializer_class = AssetGrantedSerializer
def get_queryset(self): def get_queryset(self):
@ -83,8 +84,8 @@ class UserGrantedAssetsApi(ListAPIView):
return super().get_permissions() return super().get_permissions()
class UserGrantedNodesApi(ListAPIView): class UserGrantedNodesApi(RootOrgViewMixin, ListAPIView):
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
serializer_class = NodeSerializer serializer_class = NodeSerializer
def get_queryset(self): def get_queryset(self):
@ -103,8 +104,8 @@ class UserGrantedNodesApi(ListAPIView):
return super().get_permissions() return super().get_permissions()
class UserGrantedNodesWithAssetsApi(ListAPIView): class UserGrantedNodesWithAssetsApi(RootOrgViewMixin, ListAPIView):
permission_classes = (IsSuperUserOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = NodeGrantedSerializer serializer_class = NodeGrantedSerializer
def get_queryset(self): def get_queryset(self):
@ -132,8 +133,8 @@ class UserGrantedNodesWithAssetsApi(ListAPIView):
return super().get_permissions() return super().get_permissions()
class UserGrantedNodeAssetsApi(ListAPIView): class UserGrantedNodeAssetsApi(RootOrgViewMixin, ListAPIView):
permission_classes = (IsSuperUserOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = AssetGrantedSerializer serializer_class = AssetGrantedSerializer
def get_queryset(self): def get_queryset(self):
@ -159,7 +160,7 @@ class UserGrantedNodeAssetsApi(ListAPIView):
class UserGroupGrantedAssetsApi(ListAPIView): class UserGroupGrantedAssetsApi(ListAPIView):
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
serializer_class = AssetGrantedSerializer serializer_class = AssetGrantedSerializer
def get_queryset(self): def get_queryset(self):
@ -179,7 +180,7 @@ class UserGroupGrantedAssetsApi(ListAPIView):
class UserGroupGrantedNodesApi(ListAPIView): class UserGroupGrantedNodesApi(ListAPIView):
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
serializer_class = NodeSerializer serializer_class = NodeSerializer
def get_queryset(self): def get_queryset(self):
@ -195,7 +196,7 @@ class UserGroupGrantedNodesApi(ListAPIView):
class UserGroupGrantedNodesWithAssetsApi(ListAPIView): class UserGroupGrantedNodesWithAssetsApi(ListAPIView):
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
serializer_class = NodeGrantedSerializer serializer_class = NodeGrantedSerializer
def get_queryset(self): def get_queryset(self):
@ -218,7 +219,7 @@ class UserGroupGrantedNodesWithAssetsApi(ListAPIView):
class UserGroupGrantedNodeAssetsApi(ListAPIView): class UserGroupGrantedNodeAssetsApi(ListAPIView):
permission_classes = (IsSuperUserOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = AssetGrantedSerializer serializer_class = AssetGrantedSerializer
def get_queryset(self): def get_queryset(self):
@ -235,8 +236,8 @@ class UserGroupGrantedNodeAssetsApi(ListAPIView):
return assets return assets
class ValidateUserAssetPermissionView(APIView): class ValidateUserAssetPermissionView(RootOrgViewMixin, APIView):
permission_classes = (IsSuperUserOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
@staticmethod @staticmethod
def get(request): def get(request):
@ -260,7 +261,7 @@ class AssetPermissionRemoveUserApi(RetrieveUpdateAPIView):
""" """
将用户从授权中移除Detail页面会调用 将用户从授权中移除Detail页面会调用
""" """
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetPermissionUpdateUserSerializer serializer_class = serializers.AssetPermissionUpdateUserSerializer
queryset = AssetPermission.objects.all() queryset = AssetPermission.objects.all()
@ -277,7 +278,7 @@ class AssetPermissionRemoveUserApi(RetrieveUpdateAPIView):
class AssetPermissionAddUserApi(RetrieveUpdateAPIView): class AssetPermissionAddUserApi(RetrieveUpdateAPIView):
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetPermissionUpdateUserSerializer serializer_class = serializers.AssetPermissionUpdateUserSerializer
queryset = AssetPermission.objects.all() queryset = AssetPermission.objects.all()
@ -297,7 +298,7 @@ class AssetPermissionRemoveAssetApi(RetrieveUpdateAPIView):
""" """
将用户从授权中移除Detail页面会调用 将用户从授权中移除Detail页面会调用
""" """
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetPermissionUpdateAssetSerializer serializer_class = serializers.AssetPermissionUpdateAssetSerializer
queryset = AssetPermission.objects.all() queryset = AssetPermission.objects.all()
@ -314,7 +315,7 @@ class AssetPermissionRemoveAssetApi(RetrieveUpdateAPIView):
class AssetPermissionAddAssetApi(RetrieveUpdateAPIView): class AssetPermissionAddAssetApi(RetrieveUpdateAPIView):
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetPermissionUpdateAssetSerializer serializer_class = serializers.AssetPermissionUpdateAssetSerializer
queryset = AssetPermission.objects.all() queryset = AssetPermission.objects.all()

View File

@ -4,11 +4,13 @@ from __future__ import absolute_import, unicode_literals
from django import forms from django import forms
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelForm
from orgs.utils import current_org
from .hands import User from .hands import User
from .models import AssetPermission from .models import AssetPermission
class AssetPermissionForm(forms.ModelForm): class AssetPermissionForm(OrgModelForm):
users = forms.ModelMultipleChoiceField( users = forms.ModelMultipleChoiceField(
queryset=User.objects.exclude(role=User.ROLE_APP), queryset=User.objects.exclude(role=User.ROLE_APP),
label=_("User"), label=_("User"),
@ -21,10 +23,18 @@ class AssetPermissionForm(forms.ModelForm):
required=False, 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: class Meta:
model = AssetPermission model = AssetPermission
exclude = ( exclude = (
'id', 'date_created', 'created_by' 'id', 'date_created', 'created_by', 'org_id'
) )
widgets = { widgets = {
'users': forms.SelectMultiple( 'users': forms.SelectMultiple(

View File

@ -1,7 +1,7 @@
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
# #
from users.utils import AdminUserRequiredMixin from common.permissions import AdminUserRequiredMixin
from users.models import User, UserGroup from users.models import User, UserGroup
from assets.models import Asset, SystemUser, Node from assets.models import Asset, SystemUser, Node
from assets.serializers import AssetGrantedSerializer, NodeGrantedSerializer, NodeSerializer from assets.serializers import AssetGrantedSerializer, NodeGrantedSerializer, NodeSerializer

View File

@ -6,6 +6,8 @@ from django.utils import timezone
from common.utils import date_expired_default, set_or_append_attr_bulk from common.utils import date_expired_default, set_or_append_attr_bulk
from orgs.mixins import OrgModelMixin, OrgManager
class AssetPermissionQuerySet(models.QuerySet): class AssetPermissionQuerySet(models.QuerySet):
def active(self): def active(self):
@ -16,17 +18,14 @@ class AssetPermissionQuerySet(models.QuerySet):
.filter(date_expired__gt=timezone.now()) .filter(date_expired__gt=timezone.now())
class AssetPermissionManager(models.Manager): class AssetPermissionManager(OrgManager):
def get_queryset(self):
return AssetPermissionQuerySet(self.model, using=self._db)
def valid(self): def valid(self):
return self.get_queryset().valid() return self.get_queryset().valid()
class AssetPermission(models.Model): class AssetPermission(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) 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")) 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")) 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")) 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')) date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created'))
comment = models.TextField(verbose_name=_('Comment'), blank=True) 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): def __str__(self):
return self.name return self.name
@ -71,7 +73,7 @@ class AssetPermission(models.Model):
return assets return assets
class NodePermission(models.Model): class NodePermission(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
node = models.ForeignKey('assets.Node', on_delete=models.CASCADE, verbose_name=_("Node")) 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")) user_group = models.ForeignKey('users.UserGroup', on_delete=models.CASCADE, verbose_name=_("User group"))

View File

@ -80,12 +80,12 @@ function onSelected(event, treeNode) {
var url = table.ajax.url(); var url = table.ajax.url();
if (treeNode.is_node) { if (treeNode.is_node) {
url = setUrlParam(url, 'asset', ""); url = setUrlParam(url, 'asset', "");
url = setUrlParam(url, 'node', treeNode.id) url = setUrlParam(url, 'node', treeNode.node_id)
} else { } else {
url = setUrlParam(url, 'node', ""); 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.url(url);
table.ajax.reload(); table.ajax.reload();
} }
@ -111,10 +111,19 @@ function selectQueryNode() {
function filter(treeId, parentNode, childNodes) { function filter(treeId, parentNode, childNodes) {
$.each(childNodes, function (index, value) { $.each(childNodes, function (index, value) {
value["pId"] = value["parent"]; value["node_id"] = value["id"];
value["name"] = value["value"]; value["id"] = value["tree_id"];
value["isParent"] = value["is_node"]; 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["iconSkin"] = value["is_node"] ? null : 'file';
{#value["pId"] = value["parent"];#}
{#value["name"] = value["value"];#}
value["isParent"] = value["is_node"];
}); });
return childNodes; return childNodes;
} }
@ -227,7 +236,7 @@ function initTree() {
async: { async: {
enable: true, enable: true,
url: "{% url 'api-assets:node-children-2' %}?assets=1&all=", 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, dataFilter: filter,
type: 'get' type: 'get'
}, },
@ -238,19 +247,22 @@ function initTree() {
}; };
var zNodes = []; 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) { $.each(data, function (index, value) {
value["pId"] = value["parent"]; value["node_id"] = value["id"];
value["name"] = value["value"]; value["id"] = value["tree_id"];
value["open"] = value["key"] === "0"; if (value["tree_id"] !== value["tree_parent"]) {
value["pId"] = value["tree_parent"];
}
value["isParent"] = value["is_node"]; value["isParent"] = value["is_node"];
value['name'] = value['value'];
value["iconSkin"] = value["is_node"] ? null : 'file'; value["iconSkin"] = value["is_node"] ? null : 'file';
}); });
zNodes = data; zNodes = data;
{#$.fn.zTree.init($("#assetTree"), setting);#}
$.fn.zTree.init($("#assetTree"), setting, zNodes); $.fn.zTree.init($("#assetTree"), setting, zNodes);
zTree = $.fn.zTree.getZTreeObj("assetTree"); zTree = $.fn.zTree.getZTreeObj("assetTree");
{#selectQueryNode();#} var root = zTree.getNodes()[0];
zTree.expandNode(root);
}); });
} }
@ -288,9 +300,9 @@ $(document).ready(function(){
var _assets = []; var _assets = [];
$.each(nodes, function (id, node) { $.each(nodes, function (id, node) {
if (node.is_node) { if (node.is_node) {
_nodes.push(node.id) _nodes.push(node.node_id)
} else { } else {
_assets.push(node.id) _assets.push(node.node_id)
} }
}); });
url += "?assets=" + _assets.join(",") + "&nodes=" + _nodes.join(","); url += "?assets=" + _assets.join(",") + "&nodes=" + _nodes.join(",");

View File

@ -1,63 +1,62 @@
# coding:utf-8 # coding:utf-8
from django.conf.urls import url from django.urls import path
from rest_framework import routers from rest_framework import routers
from .. import api from .. import api
app_name = 'perms' app_name = 'perms'
router = routers.DefaultRouter() router = routers.DefaultRouter()
router.register('v1/asset-permissions', api.AssetPermissionViewSet, 'asset-permission') router.register('asset-permissions', api.AssetPermissionViewSet, 'asset-permission')
urlpatterns = [ urlpatterns = [
# 查询某个用户授权的资产和资产组 # 查询某个用户授权的资产和资产组
url(r'^v1/user/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$', path('user/<uuid:pk>/assets/',
api.UserGrantedAssetsApi.as_view(), name='user-assets'), api.UserGrantedAssetsApi.as_view(), name='user-assets'),
url(r'^v1/user/assets/$', api.UserGrantedAssetsApi.as_view(), path('user/assets/', api.UserGrantedAssetsApi.as_view(),
name='my-assets'), name='my-assets'),
url(r'^v1/user/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/$', path('user/<uuid:pk>/nodes/',
api.UserGrantedNodesApi.as_view(), name='user-nodes'), api.UserGrantedNodesApi.as_view(), name='user-nodes'),
url(r'^v1/user/nodes/$', api.UserGrantedNodesApi.as_view(), path('user/nodes/', api.UserGrantedNodesApi.as_view(),
name='my-nodes'), name='my-nodes'),
url( path('user/<uuid:pk>/nodes/<uuid:node_id>/assets/',
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'),
api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'), path('user/nodes/<uuid:node_id>/assets/',
url(r'^v1/user/nodes/(?P<node_id>[0-9a-zA-Z\-]{36})/assets/$', api.UserGrantedNodeAssetsApi.as_view(), name='my-node-assets'),
api.UserGrantedNodeAssetsApi.as_view(), name='my-node-assets'), path('user/<uuid:pk>/nodes-assets/',
url(r'^v1/user/(?P<pk>[0-9a-zA-Z\-]{36})/nodes-assets/$', api.UserGrantedNodesWithAssetsApi.as_view(), name='user-nodes-assets'),
api.UserGrantedNodesWithAssetsApi.as_view(), name='user-nodes-assets'), path('user/nodes-assets/', api.UserGrantedNodesWithAssetsApi.as_view(),
url(r'^v1/user/nodes-assets/$', api.UserGrantedNodesWithAssetsApi.as_view(), name='my-nodes-assets'),
name='my-nodes-assets'),
# 查询某个用户组授权的资产和资产组 # 查询某个用户组授权的资产和资产组
url(r'^v1/user-group/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$', path('user-group/<uuid:pk>/assets/',
api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'), api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'),
url(r'^v1/user-group/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/$', path('user-group/<uuid:pk>/nodes/',
api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'), api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'),
url(r'^v1/user-group/(?P<pk>[0-9a-zA-Z\-]{36})/nodes-assets/$', path('user-group/<uuid:pk>/nodes-assets/',
api.UserGroupGrantedNodesWithAssetsApi.as_view(), api.UserGroupGrantedNodesWithAssetsApi.as_view(),
name='user-group-nodes-assets'), name='user-group-nodes-assets'),
url( path('user-group/<uuid:pk>/nodes/<uuid:node_id>/assets/',
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(),
api.UserGroupGrantedNodeAssetsApi.as_view(), name='user-group-node-assets'),
name='user-group-node-assets'),
# 用户和资产授权变更 # 用户和资产授权变更
url(r'^v1/asset-permissions/(?P<pk>[0-9a-zA-Z\-]{36})/user/remove/$', path('asset-permissions/<uuid:pk>/user/remove/',
api.AssetPermissionRemoveUserApi.as_view(), api.AssetPermissionRemoveUserApi.as_view(),
name='asset-permission-remove-user'), name='asset-permission-remove-user'),
url(r'^v1/asset-permissions/(?P<pk>[0-9a-zA-Z\-]{36})/user/add/$', path('asset-permissions/<uuid:pk>/user/add/',
api.AssetPermissionAddUserApi.as_view(), api.AssetPermissionAddUserApi.as_view(),
name='asset-permission-add-user'), name='asset-permission-add-user'),
url(r'^v1/asset-permissions/(?P<pk>[0-9a-zA-Z\-]{36})/asset/remove/$', path('asset-permissions/<uuid:pk>/asset/remove/',
api.AssetPermissionRemoveAssetApi.as_view(), api.AssetPermissionRemoveAssetApi.as_view(),
name='asset-permission-remove-asset'), name='asset-permission-remove-asset'),
url(r'^v1/asset-permissions/(?P<pk>[0-9a-zA-Z\-]{36})/asset/add/$', path('asset-permissions/<uuid:pk>/asset/add/',
api.AssetPermissionAddAssetApi.as_view(), api.AssetPermissionAddAssetApi.as_view(),
name='asset-permission-add-asset'), 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 urlpatterns += router.urls

View File

@ -1,16 +1,17 @@
# coding:utf-8 # coding:utf-8
from django.conf.urls import url from django.conf.urls import url
from django.urls import path
from .. import views from .. import views
app_name = 'perms' app_name = 'perms'
urlpatterns = [ urlpatterns = [
url(r'^asset-permission/$', views.AssetPermissionListView.as_view(), name='asset-permission-list'), path('asset-permission/', views.AssetPermissionListView.as_view(), name='asset-permission-list'),
url(r'^asset-permission/create/$', views.AssetPermissionCreateView.as_view(), name='asset-permission-create'), path('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'), path('asset-permission/<uuid:pk>/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'), path('asset-permission/<uuid:pk>/', 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'), path('asset-permission/<uuid:pk>/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'), path('asset-permission/<uuid:pk>/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/<uuid:pk>/asset/', views.AssetPermissionAssetView.as_view(), name='asset-permission-asset-list'),
] ]

View File

@ -13,7 +13,7 @@ logger = get_logger(__file__)
class Tree: class Tree:
def __init__(self): 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.__node_asset_map = defaultdict(set)
self.nodes = defaultdict(dict) self.nodes = defaultdict(dict)
self.root = Node.root() self.root = Node.root()
@ -21,7 +21,7 @@ class Tree:
def init_node_asset_map(self): def init_node_asset_map(self):
for node in self.__all_nodes: 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: for asset in assets:
self.__node_asset_map[str(asset)].add(node) self.__node_asset_map[str(asset)].add(node)

View File

@ -3,22 +3,20 @@
from __future__ import unicode_literals, absolute_import from __future__ import unicode_literals, absolute_import
from django.utils.translation import ugettext as _ 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.views.generic.edit import DeleteView, SingleObjectMixin
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.conf import settings 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 .hands import Node, Asset, SystemUser, User, UserGroup
from .models import AssetPermission from .models import AssetPermission
from .forms import AssetPermissionForm from .forms import AssetPermissionForm
class AssetPermissionListView(AdminUserRequiredMixin, ListView): class AssetPermissionListView(AdminUserRequiredMixin, TemplateView):
model = AssetPermission
template_name = 'perms/asset_permission_list.html' 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): def get_context_data(self, **kwargs):
context = { context = {
@ -87,7 +85,6 @@ class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView):
'system_users_remain': SystemUser.objects.exclude( 'system_users_remain': SystemUser.objects.exclude(
granted_by_permissions=self.object granted_by_permissions=self.object
), ),
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
@ -116,11 +113,13 @@ class AssetPermissionUserView(AdminUserRequiredMixin,
return queryset return queryset
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
'app': _('Perms'), 'app': _('Perms'),
'action': _('Asset permission user list'), 'action': _('Asset permission user list'),
'users_remain': User.objects.exclude(asset_permissions=self.object) 'users_remain': current_org.get_org_users().exclude(
.exclude(role=User.ROLE_APP), asset_permissions=self.object
),
'user_groups_remain': UserGroup.objects.exclude( 'user_groups_remain': UserGroup.objects.exclude(
asset_permissions=self.object asset_permissions=self.object
) )
@ -138,7 +137,7 @@ class AssetPermissionAssetView(AdminUserRequiredMixin,
object = None object = None
def get(self, request, *args, **kwargs): 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) return super().get(request, *args, **kwargs)
def get_queryset(self): def get_queryset(self):

View File

@ -335,13 +335,13 @@ div.dataTables_wrapper div.dataTables_filter {
.nav-header, body.mini-navbar .nav-header { .nav-header, body.mini-navbar .nav-header {
padding: 0; padding: 0;
background: #202c37; /*background: #202c37;*/
} }
.profile-element div:first-child { .profile-element div:first-child {
line-height: 60px; line-height: 60px;
/*width: 70px;*/ /*width: 70px;*/
float: left; /*float: left;*/
text-align: center; text-align: center;
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -179,7 +179,7 @@ function APIUpdateAttr(props) {
toastr.error(fail_message); toastr.error(fail_message);
} }
if (typeof props.error === 'function') { if (typeof props.error === 'function') {
return props.error(jqXHR.responseText); return props.error(jqXHR.responseText, jqXHR.status);
} }
}); });
// return true; // 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() $.fn.serializeObject = function()
{ {
var o = {}; var o = {};
@ -250,6 +291,28 @@ function makeLabel(data) {
var jumpserver = {}; var jumpserver = {};
jumpserver.checked = false; jumpserver.checked = false;
jumpserver.selected = {}; 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) { jumpserver.initDataTable = function (options) {
// options = { // options = {
// ele *: $('#dataTable_id'), // ele *: $('#dataTable_id'),
@ -293,21 +356,7 @@ jumpserver.initDataTable = function (options) {
}, },
columns: options.columns || [], columns: options.columns || [],
select: options.select || select, select: options.select || select,
language: { language: jumpserver.language,
search: "搜索",
lengthMenu: "每页 _MENU_",
info: "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项",
infoFiltered: "",
infoEmpty: "",
zeroRecords: "没有匹配项",
emptyTable: "没有记录",
paginate: {
first: "«",
previous: "",
next: "",
last: "»"
}
},
lengthMenu: [[10, 15, 25, 50, -1], [10, 15, 25, 50, "All"]] lengthMenu: [[10, 15, 25, 50, -1], [10, 15, 25, 50, "All"]]
}); });
table.on('select', function(e, dt, type, indexes) { table.on('select', function(e, dt, type, indexes) {
@ -343,6 +392,16 @@ jumpserver.initDataTable = function (options) {
return table; return table;
}; };
jumpserver.initStaticTable = function (selector) {
$(selector).DataTable({
"searching": false,
"bInfo": false,
"paging": false,
"order": [],
"language": jumpserver.language
});
};
jumpserver.initServerSideDataTable = function (options) { jumpserver.initServerSideDataTable = function (options) {
// options = { // options = {
// ele *: $('#dataTable_id'), // ele *: $('#dataTable_id'),
@ -375,9 +434,8 @@ jumpserver.initServerSideDataTable = function (options) {
}; };
var table = ele.DataTable({ var table = ele.DataTable({
pageLength: options.pageLength || 15, 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 || [], order: options.order || [],
// select: options.select || 'multi',
buttons: [], buttons: [],
columnDefs: columnDefs, columnDefs: columnDefs,
serverSide: true, serverSide: true,
@ -432,21 +490,7 @@ jumpserver.initServerSideDataTable = function (options) {
}, },
columns: options.columns || [], columns: options.columns || [],
select: options.select || select, select: options.select || select,
language: { language: jumpserver.language,
search: "搜索",
lengthMenu: "每页 _MENU_",
info: "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项",
infoFiltered: "",
infoEmpty: "",
zeroRecords: "没有匹配项",
emptyTable: "没有记录",
paginate: {
first: "«",
previous: "",
next: "",
last: "»"
}
},
lengthMenu: [[10, 15, 25, 50], [10, 15, 25, 50]] lengthMenu: [[10, 15, 25, 50], [10, 15, 25, 50]]
}); });
table.selected = []; table.selected = [];
@ -477,8 +521,7 @@ jumpserver.initServerSideDataTable = function (options) {
} }
}) })
} }
}). }).on('draw', function(){
on('draw', function(){
$('#op').html(options.op_html || ''); $('#op').html(options.op_html || '');
$('#uc').html(options.uc_html || ''); $('#uc').html(options.uc_html || '');
var table_data = []; var table_data = [];
@ -683,7 +726,7 @@ function popoverPasswordRules(password_check_rules, $el) {
} }
// 初始化弹窗popover // 初始化弹窗popover
function initPopover($container, $progress, $idPassword, $el, password_check_rules){ function initPopover($container, $progress, $idPassword, $el, password_check_rules, i18n_fallback){
options = {}; options = {};
// User Interface // User Interface
options.ui = { options.ui = {
@ -695,6 +738,14 @@ function initPopover($container, $progress, $idPassword, $el, password_check_rul
showProgressbar: true, showProgressbar: true,
showVerdictsInsideProgressBar: 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); $idPassword.pwstrength(options);
popoverPasswordRules(password_check_rules, $el); popoverPasswordRules(password_check_rules, $el);
} }

View File

@ -1,6 +1,6 @@
<div class="footer fixed"> <div class="footer fixed">
<div class="pull-right"> <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">--> <!--<img style="display: none" src="http://www.jumpserver.org/img/evaluate_avatar1.jpg">-->
</div> </div>
<div> <div>

View File

@ -35,7 +35,7 @@
</a> </a>
<ul class="dropdown-menu animated fadeInRight m-t-xs profile-dropdown"> <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> <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' %} {% 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> <li><a id="switch_admin"><i class="fa fa-exchange"></i><span> {% trans 'Admin page' %}</span></a></li>
{% else %} {% else %}

View File

@ -2,7 +2,7 @@
<div class="sidebar-collapse"> <div class="sidebar-collapse">
<ul class="nav" id="side-menu"> <ul class="nav" id="side-menu">
{% include '_user_profile.html' %} {% 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' %} {% include '_nav.html' %}
{% else %} {% else %}
{% include '_nav_user.html' %} {% include '_nav_user.html' %}

View File

@ -1,8 +1,8 @@
{% load i18n %} {% load i18n %}
<li id="index"> <li id="index">
<a href="{% url 'index' %}"> <a href="{% url 'index' %}">
<i class="fa fa-dashboard" style="width: 14px"></i> <span class="nav-label">{% trans 'Dashboard' %}</span><span <i class="fa fa-dashboard" style="width: 14px"></i> <span class="nav-label">{% trans 'Dashboard' %}</span>
class="label label-info pull-right"></span> <span class="label label-info pull-right"></span>
</a> </a>
</li> </li>
<li id="users"> <li id="users">
@ -48,9 +48,12 @@
<span class="nav-label">{% trans 'Web terminal' %}</span> <span class="nav-label">{% trans 'Web terminal' %}</span>
</a> </a>
</li> </li>
{% if request.user.is_superuser %}
<li id="terminal"><a href="{% url 'terminal:terminal-list' %}">{% trans 'Terminal' %}</a></li> <li id="terminal"><a href="{% url 'terminal:terminal-list' %}">{% trans 'Terminal' %}</a></li>
{% endif %}
</ul> </ul>
</li> </li>
{% if request.user.is_superuser %}
<li id="ops"> <li id="ops">
<a> <a>
<i class="fa fa-coffee" style="width: 14px"></i> <span class="nav-label">{% trans 'Job Center' %}</span><span class="fa arrow"></span> <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> <li id="task"><a href="{% url 'ops:task-list' %}">{% trans 'Task list' %}</a></li>
</ul> </ul>
</li> </li>
{% endif %}
<li id="audits"> <li id="audits">
<a> <a>
<i class="fa fa-history" style="width: 14px"></i> <span class="nav-label">{% trans 'Audits' %}</span><span class="fa arrow"></span> <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>#} {# <li id="download"><a href="">{% trans 'File download' %}</a></li>#}
{# </ul>#} {# </ul>#}
{#</li>#} {#</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"> <li id="settings">
<a href="{% url 'settings:basic-setting' %}"> <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> <i class="fa fa-gears"></i> <span class="nav-label">{% trans 'Settings' %}</span><span class="label label-info pull-right"></span>
</a> </a>
</li> </li>
{% endif %}
<script>
$(document).ready(function () {
var current_org = '{{ CURRENT_ORG.name }}';
console.log(current_org);
})
</script>

View File

@ -1,15 +1,41 @@
{% load static %} {% load static %}
{% load i18n %} {% load i18n %}
<li class="nav-header"> <li class="nav-header">
<div class="dropdown profile-element"> <div class="profile-element" style="height: 65px">
<div href="http://www.jumpserver.org" target="_blank"> <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" style="margin-left: 20px"/> <img alt="logo" height="55" width="185" src="/static/img/logo-text.png"/>
</div> </div>
</div> </div>
<div class="clearfix"></div>
<div class="logo-element"> <div class="logo-element">
<img alt="image" height="40" src="/static/img/logo.png"/> <img alt="image" height="40" src="/static/img/logo.png"/>
</div> </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> </li>
<script> <script>
$(document).ready(function () { $(document).ready(function () {

View File

@ -25,8 +25,7 @@ from .hands import SystemUser
from .models import Terminal, Status, Session, Task from .models import Terminal, Status, Session, Task
from .serializers import TerminalSerializer, StatusSerializer, \ from .serializers import TerminalSerializer, StatusSerializer, \
SessionSerializer, TaskSerializer, ReplaySerializer SessionSerializer, TaskSerializer, ReplaySerializer
from .hands import IsSuperUserOrAppUser, IsAppUser, \ from common.permissions import IsAppUser, IsOrgAdminOrAppUser
IsSuperUserOrAppUserOrUserReadonly
from .backends import get_command_storage, get_multi_command_storage, \ from .backends import get_command_storage, get_multi_command_storage, \
SessionCommandSerializer SessionCommandSerializer
@ -36,7 +35,7 @@ logger = logging.getLogger(__file__)
class TerminalViewSet(viewsets.ModelViewSet): class TerminalViewSet(viewsets.ModelViewSet):
queryset = Terminal.objects.filter(is_deleted=False) queryset = Terminal.objects.filter(is_deleted=False)
serializer_class = TerminalSerializer serializer_class = TerminalSerializer
permission_classes = (IsSuperUserOrAppUserOrUserReadonly,) permission_classes = (AllowAny,)
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
name = request.data.get('name') name = request.data.get('name')
@ -105,7 +104,7 @@ class TerminalTokenApi(APIView):
class StatusViewSet(viewsets.ModelViewSet): class StatusViewSet(viewsets.ModelViewSet):
queryset = Status.objects.all() queryset = Status.objects.all()
serializer_class = StatusSerializer serializer_class = StatusSerializer
permission_classes = (IsSuperUserOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
session_serializer_class = SessionSerializer session_serializer_class = SessionSerializer
task_serializer_class = TaskSerializer task_serializer_class = TaskSerializer
@ -177,7 +176,7 @@ class StatusViewSet(viewsets.ModelViewSet):
class SessionViewSet(viewsets.ModelViewSet): class SessionViewSet(viewsets.ModelViewSet):
queryset = Session.objects.all() queryset = Session.objects.all()
serializer_class = SessionSerializer serializer_class = SessionSerializer
permission_classes = (IsSuperUserOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
def get_queryset(self): def get_queryset(self):
terminal_id = self.kwargs.get("terminal", None) terminal_id = self.kwargs.get("terminal", None)
@ -200,11 +199,11 @@ class SessionViewSet(viewsets.ModelViewSet):
class TaskViewSet(BulkModelViewSet): class TaskViewSet(BulkModelViewSet):
queryset = Task.objects.all() queryset = Task.objects.all()
serializer_class = TaskSerializer serializer_class = TaskSerializer
permission_classes = (IsSuperUserOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
class KillSessionAPI(APIView): class KillSessionAPI(APIView):
permission_classes = (IsSuperUserOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
model = Task model = Task
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
@ -236,7 +235,7 @@ class CommandViewSet(viewsets.ViewSet):
command_store = get_command_storage() command_store = get_command_storage()
multi_command_storage = get_multi_command_storage() multi_command_storage = get_multi_command_storage()
serializer_class = SessionCommandSerializer serializer_class = SessionCommandSerializer
permission_classes = (IsSuperUserOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
def get_queryset(self): def get_queryset(self):
self.command_store.filter(**dict(self.request.query_params)) self.command_store.filter(**dict(self.request.query_params))
@ -244,13 +243,14 @@ class CommandViewSet(viewsets.ViewSet):
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data, many=True) serializer = self.serializer_class(data=request.data, many=True)
if serializer.is_valid(): if serializer.is_valid():
print(serializer.validated_data)
ok = self.command_store.bulk_save(serializer.validated_data) ok = self.command_store.bulk_save(serializer.validated_data)
if ok: if ok:
return Response("ok", status=201) return Response("ok", status=201)
else: else:
return Response("Save error", status=500) return Response("Save error", status=500)
else: else:
msg = "Not valid: {}".format(serializer.errors) msg = "Command not valid: {}".format(serializer.errors)
logger.error(msg) logger.error(msg)
return Response({"msg": msg}, status=401) return Response({"msg": msg}, status=401)
@ -262,7 +262,7 @@ class CommandViewSet(viewsets.ViewSet):
class SessionReplayViewSet(viewsets.ViewSet): class SessionReplayViewSet(viewsets.ViewSet):
serializer_class = ReplaySerializer serializer_class = ReplaySerializer
permission_classes = (IsSuperUserOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
session = None session = None
upload_to = 'replay' # 仅添加到本地存储中 upload_to = 'replay' # 仅添加到本地存储中
@ -348,7 +348,7 @@ class SessionReplayViewSet(viewsets.ViewSet):
class SessionReplayV2ViewSet(SessionReplayViewSet): class SessionReplayV2ViewSet(SessionReplayViewSet):
serializer_class = ReplaySerializer serializer_class = ReplaySerializer
permission_classes = (IsSuperUserOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
session = None session = None
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):

View File

@ -21,7 +21,7 @@ class CommandStore(CommandBase):
user=command["user"], asset=command["asset"], user=command["user"], asset=command["asset"],
system_user=command["system_user"], input=command["input"], system_user=command["system_user"], input=command["input"],
output=command["output"], session=command["session"], output=command["output"], session=command["session"],
timestamp=command["timestamp"] org_id=command["org_id"], timestamp=command["timestamp"]
) )
def bulk_save(self, commands): def bulk_save(self, commands):
@ -33,7 +33,7 @@ class CommandStore(CommandBase):
_commands.append(self.model( _commands.append(self.model(
user=c["user"], asset=c["asset"], system_user=c["system_user"], user=c["user"], asset=c["asset"], system_user=c["system_user"],
input=c["input"], output=c["output"], session=c["session"], 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) return self.model.objects.bulk_create(_commands)

View File

@ -4,8 +4,10 @@ import uuid
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelMixin
class AbstractSessionCommand(models.Model):
class AbstractSessionCommand(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
user = models.CharField(max_length=64, db_index=True, verbose_name=_("User")) user = models.CharField(max_length=64, db_index=True, verbose_name=_("User"))
asset = models.CharField(max_length=128, db_index=True, verbose_name=_("Asset")) asset = models.CharField(max_length=128, db_index=True, verbose_name=_("Asset"))

View File

@ -12,5 +12,6 @@ class SessionCommandSerializer(serializers.Serializer):
input = serializers.CharField(max_length=128) input = serializers.CharField(max_length=128)
output = serializers.CharField(max_length=1024, allow_blank=True) output = serializers.CharField(max_length=1024, allow_blank=True)
session = serializers.CharField(max_length=36) session = serializers.CharField(max_length=36)
org_id = serializers.CharField(max_length=36, required=False, default='', allow_null=True)
timestamp = serializers.IntegerField() timestamp = serializers.IntegerField()

View File

@ -2,7 +2,4 @@
# #
from users.models import User from users.models import User
from users.permissions import IsSuperUserOrAppUser, IsAppUser, \ from assets.models import SystemUser
IsSuperUserOrAppUserOrUserReadonly
from users.utils import AdminUserRequiredMixin
from assets.models import SystemUser

View File

@ -8,6 +8,7 @@ from django.utils import timezone
from django.conf import settings from django.conf import settings
from users.models import User from users.models import User
from orgs.mixins import OrgModelMixin
from .backends.command.models import AbstractSessionCommand 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") return self.date_created.strftime("%Y-%m-%d %H:%M:%S")
class Session(models.Model): class Session(OrgModelMixin):
LOGIN_FROM_CHOICES = ( LOGIN_FROM_CHOICES = (
('ST', 'SSH Terminal'), ('ST', 'SSH Terminal'),
('WT', 'Web Terminal'), ('WT', 'Web Terminal'),

View File

@ -150,12 +150,7 @@
}); });
} }
$(document).ready(function() { $(document).ready(function() {
$('table').DataTable({ jumpserver.initStaticTable('table');
"searching": false,
"paging": false,
"bInfo" : false,
"order": []
});
$('.select2').select2({ $('.select2').select2({
dropdownAutoWidth: true, dropdownAutoWidth: true,
width: "auto" width: "auto"

View File

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.conf.urls import url from django.urls import path
from rest_framework import routers from rest_framework import routers
from .. import api from .. import api
@ -10,26 +10,26 @@ from .. import api
app_name = 'terminal' app_name = 'terminal'
router = routers.DefaultRouter() router = routers.DefaultRouter()
router.register(r'v1/terminal/(?P<terminal>[a-zA-Z0-9\-]{36})?/?status', api.StatusViewSet, 'terminal-status') router.register(r'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'terminal/(?P<terminal>[a-zA-Z0-9\-]{36})?/?sessions', api.SessionViewSet, 'terminal-sessions')
router.register(r'v1/tasks', api.TaskViewSet, 'tasks') router.register(r'terminal', api.TerminalViewSet, 'terminal')
router.register(r'v1/terminal', api.TerminalViewSet, 'terminal') router.register(r'tasks', api.TaskViewSet, 'tasks')
router.register(r'v1/command', api.CommandViewSet, 'command') router.register(r'command', api.CommandViewSet, 'command')
router.register(r'v1/sessions', api.SessionViewSet, 'session') router.register(r'sessions', api.SessionViewSet, 'session')
router.register(r'v1/status', api.StatusViewSet, 'session') router.register(r'status', api.StatusViewSet, 'session')
urlpatterns = [ urlpatterns = [
url(r'^v1/sessions/(?P<pk>[0-9a-zA-Z\-]{36})/replay/$', path('sessions/<uuid:pk>/replay/',
api.SessionReplayViewSet.as_view({'get': 'retrieve', 'post': 'create'}), api.SessionReplayV2ViewSet.as_view({'get': 'retrieve', 'post': 'create'}),
name='session-replay'), name='session-replay'),
url(r'^v1/tasks/kill-session/', api.KillSessionAPI.as_view(), name='kill-session'), path('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(), path('terminal/<uuid:terminal>/access-key/', api.TerminalTokenApi.as_view(),
name='terminal-access-key'), name='terminal-access-key'),
url(r'^v1/terminal/config', api.TerminalConfig.as_view(), name='terminal-config'), path('terminal/config/', api.TerminalConfig.as_view(), name='terminal-config'),
# v2: get session's replay # v2: get session's replay
url(r'^v2/sessions/(?P<pk>[0-9a-zA-Z\-]{36})/replay/$', # path('v2/sessions/<uuid:pk>/replay/',
api.SessionReplayV2ViewSet.as_view({'get': 'retrieve'}), # api.SessionReplayV2ViewSet.as_view({'get': 'retrieve'}),
name='session-replay-v2'), # name='session-replay-v2'),
] ]
urlpatterns += router.urls urlpatterns += router.urls

View File

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.conf.urls import url from django.urls import path
from .. import views from .. import views
@ -10,20 +10,20 @@ app_name = 'terminal'
urlpatterns = [ urlpatterns = [
# Terminal view # Terminal view
url(r'^terminal/$', views.TerminalListView.as_view(), name='terminal-list'), path('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'), path('terminal/<uuid:pk>/', 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'), path('terminal/<uuid:pk>/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'), path('terminal/<uuid:pk>/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'), path('<uuid:pk>/accept/', views.TerminalAcceptView.as_view(), name='terminal-accept'),
url(r'^web-terminal/$', views.WebTerminalView.as_view(), name='web-terminal'), path('web-terminal/', views.WebTerminalView.as_view(), name='web-terminal'),
# Session view # Session view
url(r'^session-online/$', views.SessionOnlineListView.as_view(), name='session-online-list'), path('session-online/', views.SessionOnlineListView.as_view(), name='session-online-list'),
url(r'^session-offline/$', views.SessionOfflineListView.as_view(), name='session-offline-list'), path('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/<uuid:pk>/', views.SessionDetailView.as_view(), name='session-detail'),
# Command view # Command view
url(r'^command/$', views.CommandListView.as_view(), name='command-list'), path('command/', views.CommandListView.as_view(), name='command-list'),
url(r'^command/export/$', views.CommandExportView.as_view(), name='command-export') path('command/export/', views.CommandExportView.as_view(), name='command-export')
] ]

View File

@ -8,7 +8,8 @@ from django.http import HttpResponse
from django.template import loader from django.template import loader
import time import time
from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin from common.mixins import DatetimeSearchMixin
from common.permissions import AdminUserRequiredMixin
from ..models import Command from ..models import Command
from .. import utils from .. import utils
from ..backends import get_multi_command_storage from ..backends import get_multi_command_storage

Some files were not shown because too many files have changed in this diff Show More