Unverified Commit d5451a48 authored by 老广's avatar 老广 Committed by GitHub

Dev (#1646)

* [Update] 添加org

* [Update] 修改url

* [Update] 完成基本框架

* [Update] 修改一些逻辑

* [Update] 修改用户view

* [Update] 修改资产

* [Update] 修改asset api

* [Update] 修改协议小问题

* [Update] stash it

* [Update] 修改约束

* [Update] 修改外键为org_id

* [Update] 删掉Premiddleware

* [Update] 修改Node

* [Update] 修改get_current_org 为 proxy对象 current_org

* [Bugfix] 解决Node.root() 死循环,移动AdminRequired到permission中 (#1571)

* [Update] 修改permission (#1574)

* Tmp org (#1579)

* [Update] 添加org api, 升级到django 2.0

* [Update] fix some bug

* [Update] 修改一些bug

* [Update] 添加授权规则org (#1580)

* [Update] 修复创建授权规则,显示org_name不是有效UUID的bug

* [Update] 更新org之间隔离授权规则,解决QuerySet与Manager问题;修复创建用户,显示org_name不是有效UUID之bug;

* Tmp org (#1583)

* [Update] 修改一些内容

* [Update] 修改datatable 支持process

* [Bugfix] 修复asset queryset 没有valid方法的bug

* [Update] 在线/历史/命令model添加org;修复命令记录保存org失败bug (#1584)

* [Update] 修复创建授权规则,显示org_name不是有效UUID的bug

* [Update] 更新org之间隔离授权规则,解决QuerySet与Manager问题;修复创建用户,显示org_name不是有效UUID之bug;

* [Update] 在线/历史/命令model添加org

* [Bugfix] 修复命令记录,保存org不成功bug

* [Update] Org功能修改

* [Bugfix] 修复merge带来的问题

* [Update] org admin显示资产详情右侧选项卡;修复资产授权添加用户,会显示其他org用户的bug (#1594)

* [Bugfix] 修复资产授权添加用户,显示其他org的用户bug

* [Update] org admin 显示资产详情右侧选项卡

* Tmp org (#1596)

* [Update] 修改index view

* [Update] 修改nav

* [Update] 修改profile

* [Bugfix] 修复org下普通用户打开web终端看不到已被授权的资产和节点bug

* [Update] 修改get_all_assets

* [Bugfix] 修复节点前面有个空目录

* [Bugfix] 修复merge引起的bug

* [Update] Add init

* [Update] Node get_all_assets 过滤游离资产,条件nodes_key=None -> nodes=None

* [Update] 恢复原来的api地址

* [Update] 修改api

* [Bugfix] 修复org下用户查看我的资产不显示已授权节点/资产的bug

* [Bugfix] Fix perm name unique

* [Bugfix] 修复校验失败api

* [Update] Merge with org

* [Merge] 修改一下bug

* [Update] 暂时修改一些url

* [Update] 修改url 为django 2.0 path

* [Update] 优化datatable 和显示组织优化

* [Update] 升级url

* [Bugfix] 修复coco启动失败(load_config_from_server)、硬件刷新,测试连接,str 没有 decode(… (#1613)

* [Bugfix] 修复coco启动失败(load_config_from_server)、硬件刷新,测试连接,str 没有 decode() method的bug

* [Bugfix] (task任务系统)修复资产连接性测试、硬件刷新和系统用户连接性测试失败等bug

* [Bugfix] 修复一些bug

* [Bugfix] 修复一些bug

*  [Update] 更新org下普通用户的资产详情 (#1619)

* [Update] 更新org下普通用户查看资产详情,只显示数据

* [Update] 优化org下普通用户查看资产详情前端代码

* [Update] 创建/更新用户的role选项;密码强度提示信息中英文; (#1623)

* [Update] 修改 超级管理员/组织管理员 在 创建/更新 用户时role的选项 问题

* [Update] 用户密码强度提示信息支持中英文

* [Update] 修改token返回

* [Update] Asset返回org name

* [Update] 修改支持xpack

* [Update] 修改url

* [Bugfix] 修复不登录就能查看资产的bug

* [Update] 用户修改

* [Bugfix] ...

* [Bugfix] 修复跳转错误的问题

*  [Update] xpack/orgs组织添加删除功能-js; 修复Label继承Org后bug; (#1644)

* [Update] 更新xpack下orgs的翻译信息

* [Update] 更新model Label,继承OrgModelMixin;

* [Update] xpack/orgs组织添加删除功能-js; 修复Label继承Org后bug;

* [Bugfix] 修复小bug

* [Update] 优化一些api

* [Update] 优化用户资产页面

* [Update] 更新 xpack/orgs 删除功能:限制在当前org下删除当前org (#1645)

* [Update] 修改版本号
parent dded4e10
...@@ -32,3 +32,4 @@ django.db ...@@ -32,3 +32,4 @@ django.db
celerybeat-schedule.db celerybeat-schedule.db
data/static data/static
docs/_build/ docs/_build/
xpack
...@@ -2,4 +2,4 @@ ...@@ -2,4 +2,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
__version__ = "1.3.3" __version__ = "1.4.0"
...@@ -20,7 +20,7 @@ from rest_framework_bulk import BulkModelViewSet ...@@ -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): ...@@ -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): ...@@ -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()
......
...@@ -2,8 +2,9 @@ ...@@ -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 ...@@ -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): ...@@ -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 node.is_root():
if show_current_asset:
self.queryset = self.queryset.filter(
Q(nodes=node_id) | Q(nodes__isnull=True)
).distinct()
return
if show_current_asset:
self.queryset = self.queryset.filter(nodes=node).distinct()
else:
self.queryset = self.queryset.filter(
nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key),
).distinct()
def filter_admin_user_id(self):
admin_user_id = self.request.query_params.get('admin_user_id')
if admin_user_id: if admin_user_id:
admin_user = get_object_or_404(AdminUser, id=admin_user_id) admin_user = get_object_or_404(AdminUser, id=admin_user_id)
queryset = queryset.filter(admin_user=admin_user) self.queryset = self.queryset.filter(admin_user=admin_user)
if node_id and show_current_asset: def get_queryset(self):
node = get_object_or_404(Node, id=node_id) self.queryset = super().get_queryset()\
if node.is_root(): .prefetch_related('labels', 'nodes')\
queryset = queryset.filter( .select_related('admin_user')
Q(nodes=node_id) | Q(nodes__isnull=True) self.filter_admin_user_id()
).distinct() self.filter_node()
else: return self.queryset
queryset = queryset.filter(nodes=node).distinct()
if node_id and not show_current_asset:
node = get_object_or_404(Node, id=node_id)
if node.is_root():
queryset = Asset.objects.all()
else:
queryset = queryset.filter(
nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key),
).distinct()
return queryset
class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView): class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
...@@ -79,7 +84,7 @@ 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): ...@@ -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): ...@@ -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): ...@@ -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')
......
...@@ -2,12 +2,11 @@ ...@@ -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"] ...@@ -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): ...@@ -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): ...@@ -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
......
...@@ -17,7 +17,7 @@ from rest_framework_bulk import BulkModelViewSet ...@@ -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'] ...@@ -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): ...@@ -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
...@@ -13,16 +13,17 @@ ...@@ -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 ...@@ -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): ...@@ -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: if query_all:
children = node.get_all_children() children = node.get_all_children().annotate(Count("assets"))
else: else:
children = node.get_children() 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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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):
......
...@@ -16,8 +16,9 @@ ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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()
......
...@@ -3,14 +3,17 @@ ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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={
......
...@@ -3,6 +3,7 @@ ...@@ -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): ...@@ -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`
......
...@@ -11,6 +11,6 @@ ...@@ -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
...@@ -13,6 +13,7 @@ from django.core.cache import cache ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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
......
...@@ -11,14 +11,15 @@ from django.conf import settings ...@@ -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, ])
......
...@@ -7,12 +7,13 @@ import random ...@@ -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): ...@@ -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')]
...@@ -4,9 +4,10 @@ ...@@ -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): ...@@ -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')]
...@@ -5,12 +5,15 @@ import uuid ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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 @property
def parent(self): def parent_key(self):
if self.key == "0" or not self.key.startswith("0"):
return self.__class__.root()
parent_key = ":".join(self.key.split(":")[:-1]) parent_key = ":".join(self.key.split(":")[:-1])
return parent_key
@property
def parent(self):
if self.is_root():
return self
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()
...@@ -161,11 +175,36 @@ class Node(models.Model): ...@@ -161,11 +175,36 @@ class Node(models.Model):
).order_by('key') ).order_by('key')
return ancestor return ancestor
@classmethod
def create_root_node(cls):
# 如果使用current_org 在set_current_org时会死循环
_current_org = get_current_org()
with transaction.atomic():
if _current_org.is_default():
key = '0'
else:
set_current_org(Organization.root())
org_nodes_roots = cls.objects.filter(key__regex=r'^[0-9]+$')
org_nodes_roots_keys = org_nodes_roots.values_list('key', flat=True)
key = max([int(k) for k in org_nodes_roots_keys]) + 1
set_current_org(_current_org)
root = cls.objects.create(key=key, value=_current_org.name)
return root
@classmethod @classmethod
def root(cls): def root(cls):
obj, created = cls.objects.get_or_create( root = cls.objects.filter(key__regex=r'^[0-9]+$')
key='0', defaults={"key": '0', 'value': "ROOT"} if root:
) return root[0]
print(obj) else:
return obj return cls.create_root_node()
@classmethod
def generate_fake(cls, count=100):
import random
for i in range(count):
node = random.choice(cls.objects.all())
node.create_child('Node {}'.format(i))
...@@ -69,6 +69,7 @@ class AdminUser(AssetUser): ...@@ -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): ...@@ -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
......
...@@ -58,7 +58,7 @@ class ReplaceNodeAdminUserSerializer(serializers.ModelSerializer): ...@@ -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:
......
...@@ -20,12 +20,12 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer): ...@@ -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): ...@@ -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): ...@@ -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"
) )
...@@ -26,7 +26,7 @@ class NodeGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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
......
...@@ -71,7 +71,7 @@ function initTable2() { ...@@ -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() { ...@@ -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);
}); });
} }
......
{% extends '_modal.html' %}
{% load i18n %}
{% load static %}
<style>
.modal-body {
background-color: white !important;
}
</style>
{% block modal_id %}user_asset_detail_modal{% endblock %}
{% block modal_title %}{% trans "Asset detail" %}{% endblock %}
{% block modal_body %}
<div class="ibox-content" style="background-color: inherit">
<table class="table">
<tbody id="asset_detail_tbody">
</tbody>
</table>
</div>
{% endblock %}
{% block modal_button %}
<button data-dismiss="modal" class="btn btn-white" type="button">{% trans "Close" %}</button>
{% endblock %}
...@@ -130,7 +130,7 @@ ...@@ -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">
......
...@@ -10,6 +10,7 @@ ...@@ -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 @@ ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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){ ...@@ -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){ ...@@ -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() { ...@@ -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) { ...@@ -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) { ...@@ -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() { ...@@ -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(){ ...@@ -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(){ ...@@ -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(){ ...@@ -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(){ ...@@ -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(){ ...@@ -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(){ ...@@ -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(){ ...@@ -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(){ ...@@ -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({
......
...@@ -4,8 +4,8 @@ ...@@ -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 %}
......
...@@ -55,11 +55,14 @@ ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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 %}
\ No newline at end of file
# 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' ...@@ -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
......
# 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'),
] ]
...@@ -11,7 +11,7 @@ from django.views.generic.detail import DetailView, SingleObjectMixin ...@@ -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',
......
...@@ -29,7 +29,7 @@ from common.utils import get_object_or_none, get_logger, is_uuid ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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(
......
...@@ -7,7 +7,7 @@ from django.views.generic.detail import SingleObjectMixin ...@@ -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
......
...@@ -6,7 +6,7 @@ from django.views.generic import TemplateView, CreateView, \ ...@@ -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
......
...@@ -10,7 +10,7 @@ from django.views.generic.detail import DetailView ...@@ -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__ = [
......
...@@ -3,7 +3,7 @@ ...@@ -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 ...@@ -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,)
...@@ -3,8 +3,10 @@ import uuid ...@@ -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)
......
...@@ -9,10 +9,9 @@ from .. import api ...@@ -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
# ~*~ 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"] ...@@ -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'),
] ]
...@@ -2,7 +2,8 @@ from django.conf import settings ...@@ -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
......
...@@ -8,12 +8,12 @@ from django.core.mail import get_connection, send_mail ...@@ -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): ...@@ -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): ...@@ -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)
...@@ -62,7 +62,7 @@ class EncryptMixin: ...@@ -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):
......
...@@ -4,7 +4,6 @@ from django.db import models ...@@ -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: ...@@ -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
...@@ -2,6 +2,11 @@ ...@@ -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): ...@@ -21,28 +26,40 @@ class IsAppUser(IsValidUser):
class IsSuperUser(IsValidUser): class IsSuperUser(IsValidUser):
def has_permission(self, request, view):
return super(IsSuperUser, self).has_permission(request, view) \
and request.user.is_superuser
class IsSuperUserOrAppUser(IsSuperUser):
def has_permission(self, request, view):
return super(IsSuperUserOrAppUser, self).has_permission(request, view) \
and (request.user.is_superuser or request.user.is_app)
class IsOrgAdmin(IsValidUser):
"""Allows access only to superuser""" """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(IsOrgAdmin, self).has_permission(request, view) \
and request.user.is_superuser and current_org.can_admin_by(request.user)
class IsSuperUserOrAppUser(IsValidUser): class IsOrgAdminOrAppUser(IsValidUser):
"""Allows access between superuser and app user""" """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(IsOrgAdminOrAppUser, self).has_permission(request, view) \
and (request.user.is_superuser or request.user.is_app) and (current_org.can_admin_by(request.user) or request.user.is_app)
class IsSuperUserOrAppUserOrUserReadonly(IsSuperUserOrAppUser): 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): ...@@ -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)
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'),
] ]
...@@ -17,6 +17,7 @@ import threading ...@@ -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): ...@@ -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): ...@@ -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)
\ No newline at end of file
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
......
...@@ -8,7 +8,7 @@ msgid "" ...@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n" "Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-07-19 18:29+0800\n" "POT-Creation-Date: 2018-08-03 15:14+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n" "Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: Jumpserver team<ibuler@qq.com>\n" "Language-Team: Jumpserver team<ibuler@qq.com>\n"
...@@ -17,57 +17,57 @@ msgstr "" ...@@ -17,57 +17,57 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: assets/api/node.py:99 #: assets/api/node.py:97
msgid "New node {}" msgid "New node {}"
msgstr "新节点 {}" msgstr "新节点 {}"
#: assets/api/node.py:234 #: assets/api/node.py:232
msgid "更新节点资产硬件信息: {}" msgid "更新节点资产硬件信息: {}"
msgstr "" msgstr ""
#: assets/api/node.py:247 #: assets/api/node.py:245
msgid "测试节点下资产是否可连接: {}" msgid "测试节点下资产是否可连接: {}"
msgstr "" msgstr ""
#: assets/forms/asset.py:24 assets/models/asset.py:89 assets/models/user.py:112 #: assets/forms/asset.py:27 assets/models/asset.py:80 assets/models/user.py:113
#: assets/templates/assets/asset_detail.html:183 #: assets/templates/assets/asset_detail.html:183
#: assets/templates/assets/asset_detail.html:191 #: assets/templates/assets/asset_detail.html:191
#: assets/templates/assets/system_user_detail.html:178 perms/models.py:33 #: assets/templates/assets/system_user_detail.html:178 perms/models.py:32
msgid "Nodes" msgid "Nodes"
msgstr "节点管理" msgstr "节点管理"
#: assets/forms/asset.py:27 assets/forms/asset.py:66 assets/forms/asset.py:109 #: assets/forms/asset.py:30 assets/forms/asset.py:69 assets/forms/asset.py:112
#: assets/forms/asset.py:113 assets/models/asset.py:94 #: assets/forms/asset.py:116 assets/models/asset.py:85
#: assets/models/cluster.py:19 assets/models/user.py:72 #: assets/models/cluster.py:19 assets/models/user.py:73
#: assets/templates/assets/asset_detail.html:73 templates/_nav.html:25 #: assets/templates/assets/asset_detail.html:73 templates/_nav.html:25
msgid "Admin user" msgid "Admin user"
msgstr "管理用户" msgstr "管理用户"
#: assets/forms/asset.py:30 assets/forms/asset.py:69 assets/forms/asset.py:125 #: assets/forms/asset.py:33 assets/forms/asset.py:72 assets/forms/asset.py:128
#: assets/templates/assets/asset_create.html:36 #: assets/templates/assets/asset_create.html:36
#: assets/templates/assets/asset_create.html:38 #: assets/templates/assets/asset_create.html:38
#: assets/templates/assets/asset_list.html:75 #: assets/templates/assets/asset_list.html:80
#: assets/templates/assets/asset_update.html:41 #: assets/templates/assets/asset_update.html:41
#: assets/templates/assets/asset_update.html:43 #: assets/templates/assets/asset_update.html:43
#: assets/templates/assets/user_asset_list.html:34 #: assets/templates/assets/user_asset_list.html:34
msgid "Label" msgid "Label"
msgstr "标签" msgstr "标签"
#: assets/forms/asset.py:34 assets/forms/asset.py:73 assets/models/asset.py:85 #: assets/forms/asset.py:37 assets/forms/asset.py:76 assets/models/asset.py:76
#: assets/models/domain.py:46 #: assets/models/domain.py:47 assets/templates/assets/user_asset_list.html:188
msgid "Domain" msgid "Domain"
msgstr "网域" msgstr "网域"
#: assets/forms/asset.py:38 assets/forms/asset.py:63 assets/forms/asset.py:77 #: assets/forms/asset.py:41 assets/forms/asset.py:66 assets/forms/asset.py:80
#: assets/forms/asset.py:128 assets/templates/assets/asset_create.html:30 #: assets/forms/asset.py:131 assets/templates/assets/asset_create.html:30
#: assets/templates/assets/asset_update.html:35 perms/forms.py:40 #: assets/templates/assets/asset_update.html:35 perms/forms.py:50
#: perms/forms.py:47 perms/models.py:76 #: perms/forms.py:57 perms/models.py:78
#: perms/templates/perms/asset_permission_list.html:57 #: perms/templates/perms/asset_permission_list.html:57
#: perms/templates/perms/asset_permission_list.html:142 #: perms/templates/perms/asset_permission_list.html:142
msgid "Node" msgid "Node"
msgstr "节点" msgstr "节点"
#: assets/forms/asset.py:45 assets/forms/asset.py:85 #: assets/forms/asset.py:48 assets/forms/asset.py:88
msgid "" msgid ""
"root or other NOPASSWD sudo privilege user existed in asset,If asset is " "root or other NOPASSWD sudo privilege user existed in asset,If asset is "
"windows or other set any one, more see admin user left menu" "windows or other set any one, more see admin user left menu"
...@@ -75,42 +75,39 @@ msgstr "" ...@@ -75,42 +75,39 @@ msgstr ""
"root或其他拥有NOPASSWD: ALL权限的用户, 如果是windows或其它硬件可以随意设置一" "root或其他拥有NOPASSWD: ALL权限的用户, 如果是windows或其它硬件可以随意设置一"
"个, 更多信息查看左侧 `管理用户` 菜单" "个, 更多信息查看左侧 `管理用户` 菜单"
#: assets/forms/asset.py:48 assets/forms/asset.py:88 #: assets/forms/asset.py:52 assets/forms/asset.py:92
msgid "* required Must set exact system platform, Windows, Linux ..."
msgstr "* required 必须准确设置操作系统平台,如Windows, Linux ..."
#: assets/forms/asset.py:49 assets/forms/asset.py:89
msgid "" msgid ""
"If your have some network not connect with each other, you can set domain" "If your have some network not connect with each other, you can set domain"
msgstr "如果有多个的互相隔离的网络,设置资产属于的网域,使用网域网关跳转登录" msgstr "如果有多个的互相隔离的网络,设置资产属于的网域,使用网域网关跳转登录"
#: assets/forms/asset.py:96 assets/forms/asset.py:100 assets/forms/domain.py:16 #: assets/forms/asset.py:99 assets/forms/asset.py:103 assets/forms/domain.py:17
#: assets/forms/label.py:15 #: assets/forms/label.py:15
#: perms/templates/perms/asset_permission_asset.html:88 #: perms/templates/perms/asset_permission_asset.html:88
msgid "Select assets" msgid "Select assets"
msgstr "选择资产" msgstr "选择资产"
#: assets/forms/asset.py:105 assets/models/asset.py:81 #: assets/forms/asset.py:108 assets/models/asset.py:73
#: assets/models/domain.py:44 assets/templates/assets/admin_user_assets.html:53 #: assets/models/domain.py:45 assets/templates/assets/admin_user_assets.html:53
#: assets/templates/assets/asset_detail.html:69 #: assets/templates/assets/asset_detail.html:69
#: assets/templates/assets/domain_gateway_list.html:58 #: assets/templates/assets/domain_gateway_list.html:58
#: assets/templates/assets/system_user_asset.html:51 #: assets/templates/assets/system_user_asset.html:51
#: assets/templates/assets/user_asset_list.html:183
msgid "Port" msgid "Port"
msgstr "端口" msgstr "端口"
#: assets/forms/domain.py:14 assets/forms/label.py:13 #: assets/forms/domain.py:15 assets/forms/label.py:13
#: assets/models/asset.py:237 assets/templates/assets/admin_user_list.html:25 #: assets/models/asset.py:231 assets/templates/assets/admin_user_list.html:25
#: assets/templates/assets/domain_detail.html:60 #: assets/templates/assets/domain_detail.html:60
#: assets/templates/assets/domain_list.html:23 #: assets/templates/assets/domain_list.html:23
#: assets/templates/assets/label_list.html:16 #: assets/templates/assets/label_list.html:16
#: assets/templates/assets/system_user_list.html:30 audits/models.py:11 #: assets/templates/assets/system_user_list.html:30 audits/models.py:13
#: audits/templates/audits/ftp_log_list.html:41 #: audits/templates/audits/ftp_log_list.html:41
#: audits/templates/audits/ftp_log_list.html:72 perms/forms.py:37 #: audits/templates/audits/ftp_log_list.html:72 perms/forms.py:47
#: perms/models.py:32 #: perms/models.py:31
#: perms/templates/perms/asset_permission_create_update.html:40 #: perms/templates/perms/asset_permission_create_update.html:40
#: perms/templates/perms/asset_permission_list.html:56 #: perms/templates/perms/asset_permission_list.html:56
#: perms/templates/perms/asset_permission_list.html:139 #: perms/templates/perms/asset_permission_list.html:139
#: terminal/backends/command/models.py:11 terminal/models.py:127 #: terminal/backends/command/models.py:13 terminal/models.py:128
#: terminal/templates/terminal/command_list.html:40 #: terminal/templates/terminal/command_list.html:40
#: terminal/templates/terminal/command_list.html:73 #: terminal/templates/terminal/command_list.html:73
#: terminal/templates/terminal/session_list.html:41 #: terminal/templates/terminal/session_list.html:41
...@@ -118,9 +115,9 @@ msgstr "端口" ...@@ -118,9 +115,9 @@ msgstr "端口"
msgid "Asset" msgid "Asset"
msgstr "资产" msgstr "资产"
#: assets/forms/domain.py:54 assets/forms/user.py:79 assets/forms/user.py:139 #: assets/forms/domain.py:55 assets/forms/user.py:79 assets/forms/user.py:139
#: assets/models/base.py:21 assets/models/cluster.py:18 #: assets/models/base.py:22 assets/models/cluster.py:18
#: assets/models/domain.py:17 assets/models/group.py:20 #: assets/models/domain.py:18 assets/models/group.py:20
#: assets/models/label.py:17 assets/templates/assets/admin_user_detail.html:56 #: assets/models/label.py:17 assets/templates/assets/admin_user_detail.html:56
#: assets/templates/assets/admin_user_list.html:23 #: assets/templates/assets/admin_user_list.html:23
#: assets/templates/assets/domain_detail.html:56 #: assets/templates/assets/domain_detail.html:56
...@@ -132,29 +129,33 @@ msgstr "资产" ...@@ -132,29 +129,33 @@ msgstr "资产"
#: common/templates/common/terminal_setting.html:72 #: common/templates/common/terminal_setting.html:72
#: common/templates/common/terminal_setting.html:90 ops/models/adhoc.py:36 #: common/templates/common/terminal_setting.html:90 ops/models/adhoc.py:36
#: ops/templates/ops/task_detail.html:59 ops/templates/ops/task_list.html:35 #: ops/templates/ops/task_detail.html:59 ops/templates/ops/task_list.html:35
#: perms/models.py:29 perms/templates/perms/asset_permission_detail.html:62 #: orgs/models.py:10 perms/models.py:28
#: perms/templates/perms/asset_permission_detail.html:62
#: perms/templates/perms/asset_permission_list.html:53 #: perms/templates/perms/asset_permission_list.html:53
#: perms/templates/perms/asset_permission_user.html:54 terminal/models.py:16 #: perms/templates/perms/asset_permission_user.html:54 terminal/models.py:17
#: terminal/models.py:154 terminal/templates/terminal/terminal_detail.html:43 #: terminal/models.py:155 terminal/templates/terminal/terminal_detail.html:43
#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:12 #: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14
#: users/models/user.py:49 users/templates/users/_select_user_modal.html:13 #: users/models/user.py:51 users/templates/users/_select_user_modal.html:13
#: users/templates/users/user_detail.html:63 #: users/templates/users/user_detail.html:63
#: users/templates/users/user_group_detail.html:55 #: users/templates/users/user_group_detail.html:55
#: users/templates/users/user_group_list.html:12 #: users/templates/users/user_group_list.html:12
#: users/templates/users/user_list.html:23 #: users/templates/users/user_list.html:23
#: users/templates/users/user_profile.html:51 #: users/templates/users/user_profile.html:51
#: users/templates/users/user_pubkey_update.html:53 #: users/templates/users/user_pubkey_update.html:53
#: xpack/plugins/orgs/templates/orgs/org_detail.html:52
#: xpack/plugins/orgs/templates/orgs/org_list.html:12
#: xpack/templates/orgs/org_list.html:12
msgid "Name" msgid "Name"
msgstr "名称" msgstr "名称"
#: assets/forms/domain.py:55 assets/forms/user.py:80 assets/forms/user.py:140 #: assets/forms/domain.py:56 assets/forms/user.py:80 assets/forms/user.py:140
#: assets/models/base.py:22 assets/templates/assets/admin_user_detail.html:60 #: assets/models/base.py:23 assets/templates/assets/admin_user_detail.html:60
#: assets/templates/assets/admin_user_list.html:24 #: assets/templates/assets/admin_user_list.html:24
#: assets/templates/assets/domain_gateway_list.html:60 #: assets/templates/assets/domain_gateway_list.html:60
#: assets/templates/assets/system_user_detail.html:62 #: assets/templates/assets/system_user_detail.html:62
#: assets/templates/assets/system_user_list.html:27 #: assets/templates/assets/system_user_list.html:27
#: perms/templates/perms/asset_permission_user.html:55 users/forms.py:13 #: perms/templates/perms/asset_permission_user.html:55 users/forms.py:15
#: users/forms.py:31 users/models/authentication.py:70 users/models/user.py:47 #: users/forms.py:33 users/models/authentication.py:70 users/models/user.py:49
#: users/templates/users/_select_user_modal.html:14 #: users/templates/users/_select_user_modal.html:14
#: users/templates/users/login.html:60 #: users/templates/users/login.html:60
#: users/templates/users/login_log_list.html:49 #: users/templates/users/login_log_list.html:49
...@@ -168,8 +169,8 @@ msgstr "用户名" ...@@ -168,8 +169,8 @@ msgstr "用户名"
msgid "Password or private key passphrase" msgid "Password or private key passphrase"
msgstr "密码或密钥密码" msgstr "密码或密钥密码"
#: assets/forms/user.py:25 assets/models/base.py:23 common/forms.py:113 #: assets/forms/user.py:25 assets/models/base.py:24 common/forms.py:113
#: users/forms.py:15 users/forms.py:33 users/forms.py:45 #: users/forms.py:17 users/forms.py:35 users/forms.py:47
#: users/templates/users/login.html:63 #: users/templates/users/login.html:63
#: users/templates/users/reset_password.html:53 #: users/templates/users/reset_password.html:53
#: users/templates/users/user_create.html:10 #: users/templates/users/user_create.html:10
...@@ -180,7 +181,7 @@ msgstr "密码或密钥密码" ...@@ -180,7 +181,7 @@ msgstr "密码或密钥密码"
msgid "Password" msgid "Password"
msgstr "密码" msgstr "密码"
#: assets/forms/user.py:28 users/models/user.py:76 #: assets/forms/user.py:28 users/models/user.py:78
msgid "Private key" msgid "Private key"
msgstr "ssh私钥" msgstr "ssh私钥"
...@@ -212,14 +213,15 @@ msgid "" ...@@ -212,14 +213,15 @@ msgid ""
"password." "password."
msgstr "如果选择手动登录模式,用户名和密码则不需要填写" msgstr "如果选择手动登录模式,用户名和密码则不需要填写"
#: assets/models/asset.py:74 assets/models/domain.py:43 #: assets/models/asset.py:70 assets/models/domain.py:44
#: assets/templates/assets/_asset_list_modal.html:46 #: assets/templates/assets/_asset_list_modal.html:46
#: assets/templates/assets/admin_user_assets.html:52 #: assets/templates/assets/admin_user_assets.html:52
#: assets/templates/assets/asset_detail.html:61 #: assets/templates/assets/asset_detail.html:61
#: assets/templates/assets/asset_list.html:87 #: assets/templates/assets/asset_list.html:92
#: assets/templates/assets/domain_gateway_list.html:57 #: assets/templates/assets/domain_gateway_list.html:57
#: assets/templates/assets/system_user_asset.html:50 #: assets/templates/assets/system_user_asset.html:50
#: assets/templates/assets/user_asset_list.html:46 common/forms.py:144 #: assets/templates/assets/user_asset_list.html:46
#: assets/templates/assets/user_asset_list.html:182 common/forms.py:144
#: perms/templates/perms/asset_permission_asset.html:55 #: perms/templates/perms/asset_permission_asset.html:55
#: users/templates/users/login_log_list.html:52 #: users/templates/users/login_log_list.html:52
#: users/templates/users/user_granted_asset.html:45 #: users/templates/users/user_granted_asset.html:45
...@@ -227,130 +229,136 @@ msgstr "如果选择手动登录模式,用户名和密码则不需要填写" ...@@ -227,130 +229,136 @@ msgstr "如果选择手动登录模式,用户名和密码则不需要填写"
msgid "IP" msgid "IP"
msgstr "IP" msgstr "IP"
#: assets/models/asset.py:77 assets/templates/assets/_asset_list_modal.html:45 #: assets/models/asset.py:71 assets/templates/assets/_asset_list_modal.html:45
#: assets/templates/assets/admin_user_assets.html:51 #: assets/templates/assets/admin_user_assets.html:51
#: assets/templates/assets/asset_detail.html:57 #: assets/templates/assets/asset_detail.html:57
#: assets/templates/assets/asset_list.html:86 #: assets/templates/assets/asset_list.html:91
#: assets/templates/assets/system_user_asset.html:49 #: assets/templates/assets/system_user_asset.html:49
#: assets/templates/assets/user_asset_list.html:45 common/forms.py:143 #: assets/templates/assets/user_asset_list.html:45
#: assets/templates/assets/user_asset_list.html:181 common/forms.py:143
#: perms/templates/perms/asset_permission_asset.html:54 #: perms/templates/perms/asset_permission_asset.html:54
#: users/templates/users/user_granted_asset.html:44 #: users/templates/users/user_granted_asset.html:44
#: users/templates/users/user_group_granted_asset.html:44 #: users/templates/users/user_group_granted_asset.html:44
msgid "Hostname" msgid "Hostname"
msgstr "主机名" msgstr "主机名"
#: assets/models/asset.py:80 assets/models/domain.py:45 #: assets/models/asset.py:72 assets/models/domain.py:46
#: assets/models/user.py:115 #: assets/models/user.py:116
#: assets/templates/assets/domain_gateway_list.html:59 #: assets/templates/assets/domain_gateway_list.html:59
#: assets/templates/assets/system_user_detail.html:70 #: assets/templates/assets/system_user_detail.html:70
#: assets/templates/assets/system_user_list.html:28 #: assets/templates/assets/system_user_list.html:28
#: assets/templates/assets/user_asset_list.html:184
#: terminal/templates/terminal/session_list.html:75 #: terminal/templates/terminal/session_list.html:75
msgid "Protocol" msgid "Protocol"
msgstr "协议" msgstr "协议"
#: assets/models/asset.py:83 assets/templates/assets/asset_detail.html:97 #: assets/models/asset.py:74 assets/templates/assets/asset_detail.html:97
#: assets/templates/assets/user_asset_list.html:185
msgid "Platform" msgid "Platform"
msgstr "系统平台" msgstr "系统平台"
#: assets/models/asset.py:90 assets/models/domain.py:48 #: assets/models/asset.py:81 assets/models/domain.py:49
#: assets/models/label.py:20 assets/templates/assets/asset_detail.html:105 #: assets/models/label.py:20 assets/templates/assets/asset_detail.html:105
#: assets/templates/assets/user_asset_list.html:189
msgid "Is active" msgid "Is active"
msgstr "激活" msgstr "激活"
#: assets/models/asset.py:99 assets/templates/assets/asset_detail.html:65 #: assets/models/asset.py:88 assets/templates/assets/asset_detail.html:65
msgid "Public IP" msgid "Public IP"
msgstr "公网IP" msgstr "公网IP"
#: assets/models/asset.py:101 assets/templates/assets/asset_detail.html:113 #: assets/models/asset.py:89 assets/templates/assets/asset_detail.html:113
msgid "Asset number" msgid "Asset number"
msgstr "资产编号" msgstr "资产编号"
#: assets/models/asset.py:105 assets/templates/assets/asset_detail.html:77 #: assets/models/asset.py:93 assets/templates/assets/asset_detail.html:77
msgid "Vendor" msgid "Vendor"
msgstr "制造商" msgstr "制造商"
#: assets/models/asset.py:107 assets/templates/assets/asset_detail.html:81 #: assets/models/asset.py:95 assets/templates/assets/asset_detail.html:81
msgid "Model" msgid "Model"
msgstr "型号" msgstr "型号"
#: assets/models/asset.py:109 assets/templates/assets/asset_detail.html:109 #: assets/models/asset.py:97 assets/templates/assets/asset_detail.html:109
msgid "Serial number" msgid "Serial number"
msgstr "序列号" msgstr "序列号"
#: assets/models/asset.py:112 #: assets/models/asset.py:100
msgid "CPU model" msgid "CPU model"
msgstr "CPU型号" msgstr "CPU型号"
#: assets/models/asset.py:113 #: assets/models/asset.py:101
msgid "CPU count" msgid "CPU count"
msgstr "CPU数量" msgstr "CPU数量"
#: assets/models/asset.py:114 #: assets/models/asset.py:102
msgid "CPU cores" msgid "CPU cores"
msgstr "CPU核数" msgstr "CPU核数"
#: assets/models/asset.py:116 assets/templates/assets/asset_detail.html:89 #: assets/models/asset.py:104 assets/templates/assets/asset_detail.html:89
msgid "Memory" msgid "Memory"
msgstr "内存" msgstr "内存"
#: assets/models/asset.py:118 #: assets/models/asset.py:106
msgid "Disk total" msgid "Disk total"
msgstr "硬盘大小" msgstr "硬盘大小"
#: assets/models/asset.py:120 #: assets/models/asset.py:108
msgid "Disk info" msgid "Disk info"
msgstr "硬盘信息" msgstr "硬盘信息"
#: assets/models/asset.py:123 assets/templates/assets/asset_detail.html:101 #: assets/models/asset.py:111 assets/templates/assets/asset_detail.html:101
#: assets/templates/assets/user_asset_list.html:186
msgid "OS" msgid "OS"
msgstr "操作系统" msgstr "操作系统"
#: assets/models/asset.py:125 #: assets/models/asset.py:113
msgid "OS version" msgid "OS version"
msgstr "系统版本" msgstr "系统版本"
#: assets/models/asset.py:127 #: assets/models/asset.py:115
msgid "OS arch" msgid "OS arch"
msgstr "系统架构" msgstr "系统架构"
#: assets/models/asset.py:129 #: assets/models/asset.py:117
msgid "Hostname raw" msgid "Hostname raw"
msgstr "主机名原始" msgstr "主机名原始"
#: assets/models/asset.py:133 assets/templates/assets/asset_create.html:34 #: assets/models/asset.py:121 assets/templates/assets/asset_create.html:34
#: assets/templates/assets/asset_detail.html:220 #: assets/templates/assets/asset_detail.html:220
#: assets/templates/assets/asset_update.html:39 templates/_nav.html:27 #: assets/templates/assets/asset_update.html:39 templates/_nav.html:27
msgid "Labels" msgid "Labels"
msgstr "标签管理" msgstr "标签管理"
#: assets/models/asset.py:135 assets/models/base.py:29 #: assets/models/asset.py:123 assets/models/base.py:30
#: assets/models/cluster.py:28 assets/models/group.py:21 #: assets/models/cluster.py:28 assets/models/group.py:21
#: assets/templates/assets/admin_user_detail.html:68 #: assets/templates/assets/admin_user_detail.html:68
#: assets/templates/assets/asset_detail.html:117 #: assets/templates/assets/asset_detail.html:117
#: assets/templates/assets/domain_detail.html:72 #: assets/templates/assets/domain_detail.html:72
#: assets/templates/assets/system_user_detail.html:100 #: assets/templates/assets/system_user_detail.html:100
#: ops/templates/ops/adhoc_detail.html:86 perms/models.py:38 perms/models.py:81 #: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:13 perms/models.py:37
#: perms/templates/perms/asset_permission_detail.html:98 #: perms/models.py:83 perms/templates/perms/asset_permission_detail.html:98
#: users/models/user.py:90 users/templates/users/user_detail.html:111 #: users/models/user.py:92 users/templates/users/user_detail.html:111
msgid "Created by" msgid "Created by"
msgstr "创建者" msgstr "创建者"
#: assets/models/asset.py:138 assets/models/cluster.py:26 #: assets/models/asset.py:126 assets/models/cluster.py:26
#: assets/models/domain.py:20 assets/models/group.py:22 #: assets/models/domain.py:21 assets/models/group.py:22
#: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:64 #: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:64
#: assets/templates/assets/domain_detail.html:68 #: assets/templates/assets/domain_detail.html:68
#: assets/templates/assets/system_user_detail.html:96 #: assets/templates/assets/system_user_detail.html:96
#: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:63 #: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:63
#: perms/models.py:39 perms/models.py:82 #: orgs/models.py:14 perms/models.py:38 perms/models.py:84
#: perms/templates/perms/asset_permission_detail.html:94 #: perms/templates/perms/asset_permission_detail.html:94
#: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:15 #: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:17
#: users/templates/users/user_group_detail.html:63 #: users/templates/users/user_group_detail.html:63
#: xpack/plugins/orgs/templates/orgs/org_detail.html:60
msgid "Date created" msgid "Date created"
msgstr "创建日期" msgstr "创建日期"
#: assets/models/asset.py:140 assets/models/base.py:26 #: assets/models/asset.py:128 assets/models/base.py:27
#: assets/models/cluster.py:29 assets/models/domain.py:18 #: assets/models/cluster.py:29 assets/models/domain.py:19
#: assets/models/domain.py:47 assets/models/group.py:23 #: assets/models/domain.py:48 assets/models/group.py:23
#: assets/models/label.py:21 assets/templates/assets/admin_user_detail.html:72 #: assets/models/label.py:21 assets/templates/assets/admin_user_detail.html:72
#: assets/templates/assets/admin_user_list.html:29 #: assets/templates/assets/admin_user_list.html:29
#: assets/templates/assets/asset_detail.html:125 #: assets/templates/assets/asset_detail.html:125
...@@ -358,22 +366,27 @@ msgstr "创建日期" ...@@ -358,22 +366,27 @@ msgstr "创建日期"
#: assets/templates/assets/domain_gateway_list.html:61 #: assets/templates/assets/domain_gateway_list.html:61
#: assets/templates/assets/domain_list.html:25 #: assets/templates/assets/domain_list.html:25
#: assets/templates/assets/system_user_detail.html:104 #: assets/templates/assets/system_user_detail.html:104
#: assets/templates/assets/system_user_list.html:34 common/models.py:30 #: assets/templates/assets/system_user_list.html:34
#: ops/models/adhoc.py:42 perms/models.py:40 perms/models.py:83 #: assets/templates/assets/user_asset_list.html:190 common/models.py:30
#: perms/templates/perms/asset_permission_detail.html:102 terminal/models.py:26 #: ops/models/adhoc.py:42 orgs/models.py:15 perms/models.py:39
#: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:13 #: perms/models.py:85 perms/templates/perms/asset_permission_detail.html:102
#: users/models/user.py:82 users/templates/users/user_detail.html:123 #: terminal/models.py:27 terminal/templates/terminal/terminal_detail.html:63
#: users/models/group.py:15 users/models/user.py:84
#: users/templates/users/user_detail.html:123
#: users/templates/users/user_group_detail.html:67 #: users/templates/users/user_group_detail.html:67
#: users/templates/users/user_group_list.html:14 #: users/templates/users/user_group_list.html:14
#: users/templates/users/user_profile.html:130 #: users/templates/users/user_profile.html:130
#: xpack/plugins/orgs/templates/orgs/org_detail.html:64
#: xpack/plugins/orgs/templates/orgs/org_list.html:15
#: xpack/templates/orgs/org_list.html:14
msgid "Comment" msgid "Comment"
msgstr "备注" msgstr "备注"
#: assets/models/base.py:24 #: assets/models/base.py:25
msgid "SSH private key" msgid "SSH private key"
msgstr "ssh密钥" msgstr "ssh密钥"
#: assets/models/base.py:25 #: assets/models/base.py:26
msgid "SSH public key" msgid "SSH public key"
msgstr "ssh公钥" msgstr "ssh公钥"
...@@ -385,7 +398,7 @@ msgstr "带宽" ...@@ -385,7 +398,7 @@ msgstr "带宽"
msgid "Contact" msgid "Contact"
msgstr "联系人" msgstr "联系人"
#: assets/models/cluster.py:22 users/models/user.py:68 #: assets/models/cluster.py:22 users/models/user.py:70
#: users/templates/users/user_detail.html:76 #: users/templates/users/user_detail.html:76
msgid "Phone" msgid "Phone"
msgstr "手机" msgstr "手机"
...@@ -411,7 +424,7 @@ msgid "Default" ...@@ -411,7 +424,7 @@ msgid "Default"
msgstr "默认" msgstr "默认"
#: assets/models/cluster.py:36 assets/models/label.py:13 #: assets/models/cluster.py:36 assets/models/label.py:13
#: users/models/user.py:345 #: users/models/user.py:360
msgid "System" msgid "System"
msgstr "系统" msgstr "系统"
...@@ -431,25 +444,29 @@ msgstr "资产组" ...@@ -431,25 +444,29 @@ msgstr "资产组"
msgid "Default asset group" msgid "Default asset group"
msgstr "默认资产组" msgstr "默认资产组"
#: assets/models/label.py:14 audits/models.py:9 #: assets/models/label.py:14 audits/models.py:11
#: audits/templates/audits/ftp_log_list.html:33 #: audits/templates/audits/ftp_log_list.html:33
#: audits/templates/audits/ftp_log_list.html:71 perms/forms.py:14 #: audits/templates/audits/ftp_log_list.html:71 perms/forms.py:16
#: perms/forms.py:31 perms/models.py:30 #: perms/forms.py:41 perms/models.py:29
#: perms/templates/perms/asset_permission_create_update.html:36 #: perms/templates/perms/asset_permission_create_update.html:36
#: perms/templates/perms/asset_permission_list.html:54 #: perms/templates/perms/asset_permission_list.html:54
#: perms/templates/perms/asset_permission_list.html:133 #: perms/templates/perms/asset_permission_list.html:133
#: terminal/backends/command/models.py:10 terminal/models.py:126 #: terminal/backends/command/models.py:12 terminal/models.py:127
#: terminal/templates/terminal/command_list.html:32 #: terminal/templates/terminal/command_list.html:32
#: terminal/templates/terminal/command_list.html:72 #: terminal/templates/terminal/command_list.html:72
#: terminal/templates/terminal/session_list.html:33 #: terminal/templates/terminal/session_list.html:33
#: terminal/templates/terminal/session_list.html:71 users/forms.py:282 #: terminal/templates/terminal/session_list.html:71 users/forms.py:312
#: users/models/user.py:31 users/models/user.py:333 #: users/models/user.py:33 users/models/user.py:348
#: users/templates/users/user_group_detail.html:78 #: users/templates/users/user_group_detail.html:78
#: users/templates/users/user_group_list.html:13 users/views/user.py:367 #: users/templates/users/user_group_list.html:13 users/views/user.py:380
#: xpack/plugins/orgs/forms.py:26
#: xpack/plugins/orgs/templates/orgs/org_detail.html:113
#: xpack/plugins/orgs/templates/orgs/org_list.html:14
#: xpack/templates/orgs/org_list.html:13
msgid "User" msgid "User"
msgstr "用户" msgstr "用户"
#: assets/models/label.py:18 assets/models/node.py:16 #: assets/models/label.py:18 assets/models/node.py:19
#: assets/templates/assets/label_list.html:15 common/models.py:27 #: assets/templates/assets/label_list.html:15 common/models.py:27
msgid "Value" msgid "Value"
msgstr "值" msgstr "值"
...@@ -458,19 +475,19 @@ msgstr "值" ...@@ -458,19 +475,19 @@ msgstr "值"
msgid "Category" msgid "Category"
msgstr "分类" msgstr "分类"
#: assets/models/node.py:15 #: assets/models/node.py:18
msgid "Key" msgid "Key"
msgstr "" msgstr ""
#: assets/models/user.py:108 #: assets/models/user.py:109
msgid "Automatic login" msgid "Automatic login"
msgstr "自动登录" msgstr "自动登录"
#: assets/models/user.py:109 #: assets/models/user.py:110
msgid "Manually login" msgid "Manually login"
msgstr "手动登录" msgstr "手动登录"
#: assets/models/user.py:113 #: assets/models/user.py:114
#: assets/templates/assets/_asset_group_bulk_update_modal.html:11 #: assets/templates/assets/_asset_group_bulk_update_modal.html:11
#: assets/templates/assets/system_user_asset.html:21 #: assets/templates/assets/system_user_asset.html:21
#: assets/views/admin_user.py:29 assets/views/admin_user.py:47 #: assets/views/admin_user.py:29 assets/views/admin_user.py:47
...@@ -488,37 +505,37 @@ msgstr "手动登录" ...@@ -488,37 +505,37 @@ msgstr "手动登录"
msgid "Assets" msgid "Assets"
msgstr "资产管理" msgstr "资产管理"
#: assets/models/user.py:114 #: assets/models/user.py:115
msgid "Priority" msgid "Priority"
msgstr "优先级" msgstr "优先级"
#: assets/models/user.py:116 assets/templates/assets/_system_user.html:59 #: assets/models/user.py:117 assets/templates/assets/_system_user.html:59
#: assets/templates/assets/system_user_detail.html:122 #: assets/templates/assets/system_user_detail.html:122
#: assets/templates/assets/system_user_update.html:10 #: assets/templates/assets/system_user_update.html:10
msgid "Auto push" msgid "Auto push"
msgstr "自动推送" msgstr "自动推送"
#: assets/models/user.py:117 assets/templates/assets/system_user_detail.html:74 #: assets/models/user.py:118 assets/templates/assets/system_user_detail.html:74
msgid "Sudo" msgid "Sudo"
msgstr "Sudo" msgstr "Sudo"
#: assets/models/user.py:118 assets/templates/assets/system_user_detail.html:79 #: assets/models/user.py:119 assets/templates/assets/system_user_detail.html:79
msgid "Shell" msgid "Shell"
msgstr "Shell" msgstr "Shell"
#: assets/models/user.py:119 assets/templates/assets/system_user_detail.html:66 #: assets/models/user.py:120 assets/templates/assets/system_user_detail.html:66
#: assets/templates/assets/system_user_list.html:29 #: assets/templates/assets/system_user_list.html:29
msgid "Login mode" msgid "Login mode"
msgstr "登录模式" msgstr "登录模式"
#: assets/models/user.py:159 audits/models.py:12 #: assets/models/user.py:181 assets/templates/assets/user_asset_list.html:187
#: audits/templates/audits/ftp_log_list.html:49 #: audits/models.py:14 audits/templates/audits/ftp_log_list.html:49
#: audits/templates/audits/ftp_log_list.html:73 perms/forms.py:43 #: audits/templates/audits/ftp_log_list.html:73 perms/forms.py:53
#: perms/models.py:34 perms/models.py:78 #: perms/models.py:33 perms/models.py:80
#: perms/templates/perms/asset_permission_detail.html:140 #: perms/templates/perms/asset_permission_detail.html:140
#: perms/templates/perms/asset_permission_list.html:58 #: perms/templates/perms/asset_permission_list.html:58
#: perms/templates/perms/asset_permission_list.html:145 templates/_nav.html:26 #: perms/templates/perms/asset_permission_list.html:145 templates/_nav.html:26
#: terminal/backends/command/models.py:12 terminal/models.py:128 #: terminal/backends/command/models.py:14 terminal/models.py:129
#: terminal/templates/terminal/command_list.html:48 #: terminal/templates/terminal/command_list.html:48
#: terminal/templates/terminal/command_list.html:74 #: terminal/templates/terminal/command_list.html:74
#: terminal/templates/terminal/session_list.html:49 #: terminal/templates/terminal/session_list.html:49
...@@ -674,7 +691,7 @@ msgstr "重置" ...@@ -674,7 +691,7 @@ msgstr "重置"
#: assets/templates/assets/admin_user_create_update.html:46 #: assets/templates/assets/admin_user_create_update.html:46
#: assets/templates/assets/asset_bulk_update.html:24 #: assets/templates/assets/asset_bulk_update.html:24
#: assets/templates/assets/asset_create.html:68 #: assets/templates/assets/asset_create.html:68
#: assets/templates/assets/asset_list.html:108 #: assets/templates/assets/asset_list.html:113
#: assets/templates/assets/asset_update.html:72 #: assets/templates/assets/asset_update.html:72
#: assets/templates/assets/domain_create_update.html:17 #: assets/templates/assets/domain_create_update.html:17
#: assets/templates/assets/gateway_create_update.html:59 #: assets/templates/assets/gateway_create_update.html:59
...@@ -698,6 +715,16 @@ msgstr "重置" ...@@ -698,6 +715,16 @@ msgstr "重置"
msgid "Submit" msgid "Submit"
msgstr "提交" msgstr "提交"
#: assets/templates/assets/_user_asset_detail_modal.html:7
#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:198
msgid "Asset detail"
msgstr "资产详情"
#: assets/templates/assets/_user_asset_detail_modal.html:20
#: templates/_modal.html:21
msgid "Close"
msgstr "关闭"
#: assets/templates/assets/admin_user_assets.html:18 #: assets/templates/assets/admin_user_assets.html:18
#: assets/templates/assets/admin_user_detail.html:18 #: assets/templates/assets/admin_user_detail.html:18
#: assets/templates/assets/domain_detail.html:18 #: assets/templates/assets/domain_detail.html:18
...@@ -753,7 +780,7 @@ msgstr "测试" ...@@ -753,7 +780,7 @@ msgstr "测试"
#: assets/templates/assets/admin_user_detail.html:24 #: assets/templates/assets/admin_user_detail.html:24
#: assets/templates/assets/admin_user_list.html:85 #: assets/templates/assets/admin_user_list.html:85
#: assets/templates/assets/asset_detail.html:24 #: assets/templates/assets/asset_detail.html:24
#: assets/templates/assets/asset_list.html:175 #: assets/templates/assets/asset_list.html:180
#: assets/templates/assets/domain_detail.html:24 #: assets/templates/assets/domain_detail.html:24
#: assets/templates/assets/domain_detail.html:103 #: assets/templates/assets/domain_detail.html:103
#: assets/templates/assets/domain_gateway_list.html:85 #: assets/templates/assets/domain_gateway_list.html:85
...@@ -771,13 +798,16 @@ msgstr "测试" ...@@ -771,13 +798,16 @@ msgstr "测试"
#: users/templates/users/user_list.html:77 #: users/templates/users/user_list.html:77
#: users/templates/users/user_profile.html:151 #: users/templates/users/user_profile.html:151
#: users/templates/users/user_profile.html:180 #: users/templates/users/user_profile.html:180
#: xpack/plugins/orgs/templates/orgs/org_detail.html:25
#: xpack/plugins/orgs/templates/orgs/org_list.html:48
#: xpack/templates/orgs/org_list.html:43
msgid "Update" msgid "Update"
msgstr "更新" msgstr "更新"
#: assets/templates/assets/admin_user_detail.html:28 #: assets/templates/assets/admin_user_detail.html:28
#: assets/templates/assets/admin_user_list.html:86 #: assets/templates/assets/admin_user_list.html:86
#: assets/templates/assets/asset_detail.html:28 #: assets/templates/assets/asset_detail.html:28
#: assets/templates/assets/asset_list.html:176 #: assets/templates/assets/asset_list.html:181
#: assets/templates/assets/domain_detail.html:28 #: assets/templates/assets/domain_detail.html:28
#: assets/templates/assets/domain_detail.html:104 #: assets/templates/assets/domain_detail.html:104
#: assets/templates/assets/domain_gateway_list.html:86 #: assets/templates/assets/domain_gateway_list.html:86
...@@ -794,6 +824,9 @@ msgstr "更新" ...@@ -794,6 +824,9 @@ msgstr "更新"
#: users/templates/users/user_group_list.html:45 #: users/templates/users/user_group_list.html:45
#: users/templates/users/user_list.html:81 #: users/templates/users/user_list.html:81
#: users/templates/users/user_list.html:85 #: users/templates/users/user_list.html:85
#: xpack/plugins/orgs/templates/orgs/org_detail.html:29
#: xpack/plugins/orgs/templates/orgs/org_list.html:50
#: xpack/templates/orgs/org_list.html:45
msgid "Delete" msgid "Delete"
msgstr "删除" msgstr "删除"
...@@ -808,7 +841,7 @@ msgstr "选择节点" ...@@ -808,7 +841,7 @@ msgstr "选择节点"
#: assets/templates/assets/admin_user_detail.html:100 #: assets/templates/assets/admin_user_detail.html:100
#: assets/templates/assets/asset_detail.html:200 #: assets/templates/assets/asset_detail.html:200
#: assets/templates/assets/asset_list.html:638 #: assets/templates/assets/asset_list.html:646
#: assets/templates/assets/system_user_detail.html:195 #: assets/templates/assets/system_user_detail.html:195
#: assets/templates/assets/system_user_list.html:139 templates/_modal.html:22 #: assets/templates/assets/system_user_list.html:139 templates/_modal.html:22
#: terminal/templates/terminal/session_detail.html:108 #: terminal/templates/terminal/session_detail.html:108
...@@ -820,6 +853,8 @@ msgstr "选择节点" ...@@ -820,6 +853,8 @@ msgstr "选择节点"
#: users/templates/users/user_group_list.html:86 #: users/templates/users/user_group_list.html:86
#: users/templates/users/user_list.html:200 #: users/templates/users/user_list.html:200
#: users/templates/users/user_profile.html:222 #: users/templates/users/user_profile.html:222
#: xpack/plugins/orgs/templates/orgs/org_create_update.html:33
#: xpack/templates/orgs/org_list.html:86
msgid "Confirm" msgid "Confirm"
msgstr "确认" msgstr "确认"
...@@ -841,7 +876,7 @@ msgid "Ratio" ...@@ -841,7 +876,7 @@ msgid "Ratio"
msgstr "比例" msgstr "比例"
#: assets/templates/assets/admin_user_list.html:30 #: assets/templates/assets/admin_user_list.html:30
#: assets/templates/assets/asset_list.html:91 #: assets/templates/assets/asset_list.html:96
#: assets/templates/assets/domain_gateway_list.html:62 #: assets/templates/assets/domain_gateway_list.html:62
#: assets/templates/assets/domain_list.html:26 #: assets/templates/assets/domain_list.html:26
#: assets/templates/assets/label_list.html:17 #: assets/templates/assets/label_list.html:17
...@@ -853,13 +888,11 @@ msgstr "比例" ...@@ -853,13 +888,11 @@ msgstr "比例"
#: terminal/templates/terminal/terminal_list.html:36 #: terminal/templates/terminal/terminal_list.html:36
#: users/templates/users/user_group_list.html:15 #: users/templates/users/user_group_list.html:15
#: users/templates/users/user_list.html:29 #: users/templates/users/user_list.html:29
#: xpack/plugins/orgs/templates/orgs/org_list.html:16
#: xpack/templates/orgs/org_list.html:15
msgid "Action" msgid "Action"
msgstr "动作" msgstr "动作"
#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:198
msgid "Asset detail"
msgstr "资产详情"
#: assets/templates/assets/asset_detail.html:85 #: assets/templates/assets/asset_detail.html:85
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr "CPU"
...@@ -882,9 +915,9 @@ msgid "Quick modify" ...@@ -882,9 +915,9 @@ msgid "Quick modify"
msgstr "快速修改" msgstr "快速修改"
#: assets/templates/assets/asset_detail.html:143 #: assets/templates/assets/asset_detail.html:143
#: assets/templates/assets/asset_list.html:89 #: assets/templates/assets/asset_list.html:94
#: assets/templates/assets/user_asset_list.html:47 perms/models.py:35 #: assets/templates/assets/user_asset_list.html:47 perms/models.py:34
#: perms/models.py:79 #: perms/models.py:81
#: perms/templates/perms/asset_permission_create_update.html:47 #: perms/templates/perms/asset_permission_create_update.html:47
#: perms/templates/perms/asset_permission_detail.html:120 #: perms/templates/perms/asset_permission_detail.html:120
#: perms/templates/perms/asset_permission_list.html:59 #: perms/templates/perms/asset_permission_list.html:59
...@@ -912,120 +945,121 @@ msgstr "刷新" ...@@ -912,120 +945,121 @@ msgstr "刷新"
msgid "Update successfully!" msgid "Update successfully!"
msgstr "更新成功" msgstr "更新成功"
#: assets/templates/assets/asset_list.html:63 assets/views/asset.py:97 #: assets/templates/assets/asset_list.html:68 assets/views/asset.py:97
msgid "Create asset" msgid "Create asset"
msgstr "创建资产" msgstr "创建资产"
#: assets/templates/assets/asset_list.html:67 #: assets/templates/assets/asset_list.html:72
#: users/templates/users/user_list.html:7 #: users/templates/users/user_list.html:7
msgid "Import" msgid "Import"
msgstr "导入" msgstr "导入"
#: assets/templates/assets/asset_list.html:70 #: assets/templates/assets/asset_list.html:75
#: users/templates/users/user_list.html:10 #: users/templates/users/user_list.html:10
msgid "Export" msgid "Export"
msgstr "导出" msgstr "导出"
#: assets/templates/assets/asset_list.html:88 #: assets/templates/assets/asset_list.html:93
msgid "Hardware" msgid "Hardware"
msgstr "硬件" msgstr "硬件"
#: assets/templates/assets/asset_list.html:100 #: assets/templates/assets/asset_list.html:105
#: users/templates/users/user_list.html:38 #: users/templates/users/user_list.html:38
msgid "Delete selected" msgid "Delete selected"
msgstr "批量删除" msgstr "批量删除"
#: assets/templates/assets/asset_list.html:101 #: assets/templates/assets/asset_list.html:106
#: users/templates/users/user_list.html:39 #: users/templates/users/user_list.html:39
msgid "Update selected" msgid "Update selected"
msgstr "批量更新" msgstr "批量更新"
#: assets/templates/assets/asset_list.html:102 #: assets/templates/assets/asset_list.html:107
msgid "Remove from this node" msgid "Remove from this node"
msgstr "从节点移除" msgstr "从节点移除"
#: assets/templates/assets/asset_list.html:103 #: assets/templates/assets/asset_list.html:108
#: users/templates/users/user_list.html:40 #: users/templates/users/user_list.html:40
msgid "Deactive selected" msgid "Deactive selected"
msgstr "禁用所选" msgstr "禁用所选"
#: assets/templates/assets/asset_list.html:104 #: assets/templates/assets/asset_list.html:109
#: users/templates/users/user_list.html:41 #: users/templates/users/user_list.html:41
msgid "Active selected" msgid "Active selected"
msgstr "激活所选" msgstr "激活所选"
#: assets/templates/assets/asset_list.html:121 #: assets/templates/assets/asset_list.html:126
msgid "Add node" msgid "Add node"
msgstr "新建节点" msgstr "新建节点"
#: assets/templates/assets/asset_list.html:122 #: assets/templates/assets/asset_list.html:127
msgid "Rename node" msgid "Rename node"
msgstr "重命名节点" msgstr "重命名节点"
#: assets/templates/assets/asset_list.html:123 #: assets/templates/assets/asset_list.html:128
msgid "Delete node" msgid "Delete node"
msgstr "删除节点" msgstr "删除节点"
#: assets/templates/assets/asset_list.html:125 #: assets/templates/assets/asset_list.html:130
msgid "Add assets to node" msgid "Add assets to node"
msgstr "添加资产到节点" msgstr "添加资产到节点"
#: assets/templates/assets/asset_list.html:126 #: assets/templates/assets/asset_list.html:131
msgid "Move assets to node" msgid "Move assets to node"
msgstr "移动资产到节点" msgstr "移动资产到节点"
#: assets/templates/assets/asset_list.html:128 #: assets/templates/assets/asset_list.html:133
msgid "Refresh node hardware info" msgid "Refresh node hardware info"
msgstr "更新节点资产硬件信息" msgstr "更新节点资产硬件信息"
#: assets/templates/assets/asset_list.html:129 #: assets/templates/assets/asset_list.html:134
msgid "Test node connective" msgid "Test node connective"
msgstr "测试节点资产可连接性" msgstr "测试节点资产可连接性"
#: assets/templates/assets/asset_list.html:131 #: assets/templates/assets/asset_list.html:136
msgid "Display only current node assets" msgid "Display only current node assets"
msgstr "仅显示当前节点资产" msgstr "仅显示当前节点资产"
#: assets/templates/assets/asset_list.html:132 #: assets/templates/assets/asset_list.html:137
msgid "Displays all child node assets" msgid "Displays all child node assets"
msgstr "显示所有子节点资产" msgstr "显示所有子节点资产"
#: assets/templates/assets/asset_list.html:218 #: assets/templates/assets/asset_list.html:223
msgid "Create node failed" msgid "Create node failed"
msgstr "创建节点失败" msgstr "创建节点失败"
#: assets/templates/assets/asset_list.html:230 #: assets/templates/assets/asset_list.html:235
msgid "Have child node, cancel" msgid "Have child node, cancel"
msgstr "存在子节点,不能删除" msgstr "存在子节点,不能删除"
#: assets/templates/assets/asset_list.html:232 #: assets/templates/assets/asset_list.html:237
msgid "Have assets, cancel" msgid "Have assets, cancel"
msgstr "存在资产,不能删除" msgstr "存在资产,不能删除"
#: assets/templates/assets/asset_list.html:633 #: assets/templates/assets/asset_list.html:641
#: assets/templates/assets/system_user_list.html:134 #: assets/templates/assets/system_user_list.html:134
#: users/templates/users/user_detail.html:369 #: users/templates/users/user_detail.html:369
#: users/templates/users/user_detail.html:394 #: users/templates/users/user_detail.html:394
#: users/templates/users/user_detail.html:461 #: users/templates/users/user_detail.html:461
#: users/templates/users/user_group_list.html:81 #: users/templates/users/user_group_list.html:81
#: users/templates/users/user_list.html:195 #: users/templates/users/user_list.html:195
#: xpack/templates/orgs/org_list.html:81
msgid "Are you sure?" msgid "Are you sure?"
msgstr "你确认吗?" msgstr "你确认吗?"
#: assets/templates/assets/asset_list.html:634 #: assets/templates/assets/asset_list.html:642
msgid "This will delete the selected assets !!!" msgid "This will delete the selected assets !!!"
msgstr "删除选择资产" msgstr "删除选择资产"
#: assets/templates/assets/asset_list.html:642 #: assets/templates/assets/asset_list.html:650
msgid "Asset Deleted." msgid "Asset Deleted."
msgstr "已被删除" msgstr "已被删除"
#: assets/templates/assets/asset_list.html:643 #: assets/templates/assets/asset_list.html:651
#: assets/templates/assets/asset_list.html:648 #: assets/templates/assets/asset_list.html:656
msgid "Asset Delete" msgid "Asset Delete"
msgstr "删除" msgstr "删除"
#: assets/templates/assets/asset_list.html:647 #: assets/templates/assets/asset_list.html:655
msgid "Asset Deleting failed." msgid "Asset Deleting failed."
msgstr "删除失败" msgstr "删除失败"
...@@ -1219,22 +1253,22 @@ msgstr "资产管理" ...@@ -1219,22 +1253,22 @@ msgstr "资产管理"
msgid "System user asset" msgid "System user asset"
msgstr "系统用户集群资产" msgstr "系统用户集群资产"
#: audits/models.py:10 audits/templates/audits/ftp_log_list.html:74 #: audits/models.py:12 audits/templates/audits/ftp_log_list.html:74
#: terminal/models.py:130 terminal/templates/terminal/session_list.html:74 #: terminal/models.py:131 terminal/templates/terminal/session_list.html:74
#: terminal/templates/terminal/terminal_detail.html:47 #: terminal/templates/terminal/terminal_detail.html:47
msgid "Remote addr" msgid "Remote addr"
msgstr "远端地址" msgstr "远端地址"
#: audits/models.py:13 audits/templates/audits/ftp_log_list.html:75 #: audits/models.py:15 audits/templates/audits/ftp_log_list.html:75
msgid "Operate" msgid "Operate"
msgstr "操作" msgstr "操作"
#: audits/models.py:14 audits/templates/audits/ftp_log_list.html:56 #: audits/models.py:16 audits/templates/audits/ftp_log_list.html:56
#: audits/templates/audits/ftp_log_list.html:76 #: audits/templates/audits/ftp_log_list.html:76
msgid "Filename" msgid "Filename"
msgstr "文件名" msgstr "文件名"
#: audits/models.py:15 audits/templates/audits/ftp_log_list.html:77 #: audits/models.py:17 audits/templates/audits/ftp_log_list.html:77
#: ops/templates/ops/task_list.html:39 users/models/authentication.py:66 #: ops/templates/ops/task_list.html:39 users/models/authentication.py:66
#: users/templates/users/user_detail.html:443 #: users/templates/users/user_detail.html:443
msgid "Success" msgid "Success"
...@@ -1243,17 +1277,17 @@ msgstr "成功" ...@@ -1243,17 +1277,17 @@ msgstr "成功"
#: audits/templates/audits/ftp_log_list.html:78 #: audits/templates/audits/ftp_log_list.html:78
#: ops/templates/ops/adhoc_history.html:52 #: ops/templates/ops/adhoc_history.html:52
#: ops/templates/ops/adhoc_history_detail.html:61 #: ops/templates/ops/adhoc_history_detail.html:61
#: ops/templates/ops/task_history.html:58 perms/models.py:36 #: ops/templates/ops/task_history.html:58 perms/models.py:35
#: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:137 #: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:138
#: terminal/templates/terminal/session_list.html:78 #: terminal/templates/terminal/session_list.html:78
msgid "Date start" msgid "Date start"
msgstr "开始日期" msgstr "开始日期"
#: audits/views.py:50 templates/_nav.html:64 #: audits/views.py:51 templates/_nav.html:68
msgid "Audits" msgid "Audits"
msgstr "日志审计" msgstr "日志审计"
#: audits/views.py:51 templates/_nav.html:67 #: audits/views.py:52 templates/_nav.html:71
msgid "FTP log" msgid "FTP log"
msgstr "FTP日志" msgstr "FTP日志"
...@@ -1401,7 +1435,7 @@ msgid "Public key auth" ...@@ -1401,7 +1435,7 @@ msgid "Public key auth"
msgstr "密钥认证" msgstr "密钥认证"
#: common/forms.py:159 common/templates/common/terminal_setting.html:68 #: common/forms.py:159 common/templates/common/terminal_setting.html:68
#: terminal/forms.py:30 terminal/models.py:20 #: terminal/forms.py:30 terminal/models.py:21
msgid "Command storage" msgid "Command storage"
msgstr "命令存储" msgstr "命令存储"
...@@ -1412,7 +1446,7 @@ msgid "" ...@@ -1412,7 +1446,7 @@ msgid ""
msgstr "设置终端命令存储,default是默认用的存储方式" msgstr "设置终端命令存储,default是默认用的存储方式"
#: common/forms.py:165 common/templates/common/terminal_setting.html:86 #: common/forms.py:165 common/templates/common/terminal_setting.html:86
#: terminal/forms.py:35 terminal/models.py:21 #: terminal/forms.py:35 terminal/models.py:22
msgid "Replay storage" msgid "Replay storage"
msgstr "录像存储" msgstr "录像存储"
...@@ -1491,11 +1525,11 @@ msgid "" ...@@ -1491,11 +1525,11 @@ msgid ""
"characters" "characters"
msgstr "开启后,用户密码修改、重置必须包含特殊字符" msgstr "开启后,用户密码修改、重置必须包含特殊字符"
#: common/mixins.py:29 #: common/mixins.py:28
msgid "is discard" msgid "is discard"
msgstr "" msgstr ""
#: common/mixins.py:30 #: common/mixins.py:29
msgid "discard time" msgid "discard time"
msgstr "" msgstr ""
...@@ -1509,7 +1543,7 @@ msgstr "启用" ...@@ -1509,7 +1543,7 @@ msgstr "启用"
#: common/templates/common/ldap_setting.html:15 #: common/templates/common/ldap_setting.html:15
#: common/templates/common/security_setting.html:15 #: common/templates/common/security_setting.html:15
#: common/templates/common/terminal_setting.html:16 #: common/templates/common/terminal_setting.html:16
#: common/templates/common/terminal_setting.html:46 common/views.py:22 #: common/templates/common/terminal_setting.html:46 common/views.py:20
msgid "Basic setting" msgid "Basic setting"
msgstr "基本设置" msgstr "基本设置"
...@@ -1517,7 +1551,7 @@ msgstr "基本设置" ...@@ -1517,7 +1551,7 @@ msgstr "基本设置"
#: common/templates/common/email_setting.html:18 #: common/templates/common/email_setting.html:18
#: common/templates/common/ldap_setting.html:18 #: common/templates/common/ldap_setting.html:18
#: common/templates/common/security_setting.html:18 #: common/templates/common/security_setting.html:18
#: common/templates/common/terminal_setting.html:20 common/views.py:48 #: common/templates/common/terminal_setting.html:20 common/views.py:46
msgid "Email setting" msgid "Email setting"
msgstr "邮件设置" msgstr "邮件设置"
...@@ -1525,7 +1559,7 @@ msgstr "邮件设置" ...@@ -1525,7 +1559,7 @@ msgstr "邮件设置"
#: common/templates/common/email_setting.html:21 #: common/templates/common/email_setting.html:21
#: common/templates/common/ldap_setting.html:21 #: common/templates/common/ldap_setting.html:21
#: common/templates/common/security_setting.html:21 #: common/templates/common/security_setting.html:21
#: common/templates/common/terminal_setting.html:24 common/views.py:74 #: common/templates/common/terminal_setting.html:24 common/views.py:72
msgid "LDAP setting" msgid "LDAP setting"
msgstr "LDAP设置" msgstr "LDAP设置"
...@@ -1533,7 +1567,7 @@ msgstr "LDAP设置" ...@@ -1533,7 +1567,7 @@ msgstr "LDAP设置"
#: common/templates/common/email_setting.html:24 #: common/templates/common/email_setting.html:24
#: common/templates/common/ldap_setting.html:24 #: common/templates/common/ldap_setting.html:24
#: common/templates/common/security_setting.html:24 #: common/templates/common/security_setting.html:24
#: common/templates/common/terminal_setting.html:28 common/views.py:104 #: common/templates/common/terminal_setting.html:28 common/views.py:102
msgid "Terminal setting" msgid "Terminal setting"
msgstr "终端设置" msgstr "终端设置"
...@@ -1541,7 +1575,7 @@ msgstr "终端设置" ...@@ -1541,7 +1575,7 @@ msgstr "终端设置"
#: common/templates/common/email_setting.html:27 #: common/templates/common/email_setting.html:27
#: common/templates/common/ldap_setting.html:27 #: common/templates/common/ldap_setting.html:27
#: common/templates/common/security_setting.html:27 #: common/templates/common/security_setting.html:27
#: common/templates/common/terminal_setting.html:31 common/views.py:132 #: common/templates/common/terminal_setting.html:31 common/views.py:130
msgid "Security setting" msgid "Security setting"
msgstr "安全设置" msgstr "安全设置"
...@@ -1563,17 +1597,17 @@ msgstr "类型" ...@@ -1563,17 +1597,17 @@ msgstr "类型"
msgid "Special char not allowed" msgid "Special char not allowed"
msgstr "不能包含特殊字符" msgstr "不能包含特殊字符"
#: common/views.py:21 common/views.py:47 common/views.py:73 common/views.py:103 #: common/views.py:19 common/views.py:45 common/views.py:71 common/views.py:101
#: common/views.py:131 templates/_nav.html:81 #: common/views.py:129 templates/_nav.html:98
msgid "Settings" msgid "Settings"
msgstr "系统设置" msgstr "系统设置"
#: common/views.py:32 common/views.py:58 common/views.py:86 common/views.py:116 #: common/views.py:30 common/views.py:56 common/views.py:84 common/views.py:114
#: common/views.py:142 #: common/views.py:140
msgid "Update setting successfully, please restart program" msgid "Update setting successfully, please restart program"
msgstr "更新设置成功, 请手动重启程序" msgstr "更新设置成功, 请手动重启程序"
#: ops/api.py:79 #: ops/api.py:81
msgid "Waiting ..." msgid "Waiting ..."
msgstr "" msgstr ""
...@@ -1626,39 +1660,40 @@ msgid "Become" ...@@ -1626,39 +1660,40 @@ msgid "Become"
msgstr "Become" msgstr "Become"
#: ops/models/adhoc.py:161 users/templates/users/user_group_detail.html:59 #: ops/models/adhoc.py:161 users/templates/users/user_group_detail.html:59
#: xpack/plugins/orgs/templates/orgs/org_detail.html:56
msgid "Create by" msgid "Create by"
msgstr "创建者" msgstr "创建者"
#: ops/models/adhoc.py:323 #: ops/models/adhoc.py:324
msgid "Start time" msgid "Start time"
msgstr "开始时间" msgstr "开始时间"
#: ops/models/adhoc.py:324 #: ops/models/adhoc.py:325
msgid "End time" msgid "End time"
msgstr "完成时间" msgstr "完成时间"
#: ops/models/adhoc.py:325 ops/templates/ops/adhoc_history.html:57 #: ops/models/adhoc.py:326 ops/templates/ops/adhoc_history.html:57
#: ops/templates/ops/task_history.html:63 ops/templates/ops/task_list.html:41 #: ops/templates/ops/task_history.html:63 ops/templates/ops/task_list.html:41
msgid "Time" msgid "Time"
msgstr "时间" msgstr "时间"
#: ops/models/adhoc.py:326 ops/templates/ops/adhoc_detail.html:106 #: ops/models/adhoc.py:327 ops/templates/ops/adhoc_detail.html:106
#: ops/templates/ops/adhoc_history.html:55 #: ops/templates/ops/adhoc_history.html:55
#: ops/templates/ops/adhoc_history_detail.html:69 #: ops/templates/ops/adhoc_history_detail.html:69
#: ops/templates/ops/task_detail.html:83 ops/templates/ops/task_history.html:61 #: ops/templates/ops/task_detail.html:83 ops/templates/ops/task_history.html:61
msgid "Is finished" msgid "Is finished"
msgstr "是否完成" msgstr "是否完成"
#: ops/models/adhoc.py:327 ops/templates/ops/adhoc_history.html:56 #: ops/models/adhoc.py:328 ops/templates/ops/adhoc_history.html:56
#: ops/templates/ops/task_history.html:62 #: ops/templates/ops/task_history.html:62
msgid "Is success" msgid "Is success"
msgstr "是否成功" msgstr "是否成功"
#: ops/models/adhoc.py:328 #: ops/models/adhoc.py:329
msgid "Adhoc raw result" msgid "Adhoc raw result"
msgstr "结果" msgstr "结果"
#: ops/models/adhoc.py:329 #: ops/models/adhoc.py:330
msgid "Adhoc result summary" msgid "Adhoc result summary"
msgstr "汇总" msgstr "汇总"
...@@ -1738,7 +1773,7 @@ msgid "Run history detail" ...@@ -1738,7 +1773,7 @@ msgid "Run history detail"
msgstr "执行历史详情" msgstr "执行历史详情"
#: ops/templates/ops/adhoc_history_detail.html:22 #: ops/templates/ops/adhoc_history_detail.html:22
#: terminal/backends/command/models.py:14 #: terminal/backends/command/models.py:16
msgid "Output" msgid "Output"
msgstr "输出" msgstr "输出"
...@@ -1808,6 +1843,7 @@ msgstr "内容" ...@@ -1808,6 +1843,7 @@ msgstr "内容"
#: ops/templates/ops/task_list.html:21 ops/templates/ops/task_list.html:26 #: ops/templates/ops/task_list.html:21 ops/templates/ops/task_list.html:26
#: templates/_base_list.html:43 templates/_header_bar.html:8 #: templates/_base_list.html:43 templates/_header_bar.html:8
#: terminal/templates/terminal/command_list.html:60 #: terminal/templates/terminal/command_list.html:60
#: terminal/templates/terminal/session_list.html:61
#: users/templates/users/login_log_list.html:35 #: users/templates/users/login_log_list.html:35
#: users/templates/users/login_log_list.html:40 #: users/templates/users/login_log_list.html:40
msgid "Search" msgid "Search"
...@@ -1835,7 +1871,7 @@ msgstr "任务开始: " ...@@ -1835,7 +1871,7 @@ msgstr "任务开始: "
msgid "Ops" msgid "Ops"
msgstr "作业中心" msgstr "作业中心"
#: ops/views.py:37 templates/_nav.html:59 #: ops/views.py:37 templates/_nav.html:62
msgid "Task list" msgid "Task list"
msgstr "任务列表" msgstr "任务列表"
...@@ -1843,37 +1879,37 @@ msgstr "任务列表" ...@@ -1843,37 +1879,37 @@ msgstr "任务列表"
msgid "Task run history" msgid "Task run history"
msgstr "执行历史" msgstr "执行历史"
#: perms/forms.py:18 users/forms.py:239 users/forms.py:244 users/forms.py:256 #: perms/forms.py:20 users/forms.py:265 users/forms.py:270 users/forms.py:282
#: users/forms.py:286 #: users/forms.py:316 xpack/plugins/orgs/forms.py:30
msgid "Select users" msgid "Select users"
msgstr "选择用户" msgstr "选择用户"
#: perms/forms.py:34 perms/models.py:31 perms/models.py:77 #: perms/forms.py:44 perms/models.py:30 perms/models.py:79
#: perms/templates/perms/asset_permission_list.html:55 #: perms/templates/perms/asset_permission_list.html:55
#: perms/templates/perms/asset_permission_list.html:136 templates/_nav.html:14 #: perms/templates/perms/asset_permission_list.html:136 templates/_nav.html:14
#: users/models/group.py:23 users/models/user.py:55 #: users/models/group.py:26 users/models/user.py:57
#: users/templates/users/_select_user_modal.html:16 #: users/templates/users/_select_user_modal.html:16
#: users/templates/users/user_detail.html:200 #: users/templates/users/user_detail.html:200
#: users/templates/users/user_list.html:26 #: users/templates/users/user_list.html:26
msgid "User group" msgid "User group"
msgstr "用户组" msgstr "用户组"
#: perms/forms.py:56 #: perms/forms.py:66
msgid "User or group at least one required" msgid "User or group at least one required"
msgstr "" msgstr ""
#: perms/forms.py:65 #: perms/forms.py:75
msgid "Asset or group at least one required" msgid "Asset or group at least one required"
msgstr "" msgstr ""
#: perms/models.py:37 perms/models.py:80 #: perms/models.py:36 perms/models.py:82
#: perms/templates/perms/asset_permission_detail.html:90 #: perms/templates/perms/asset_permission_detail.html:90
#: users/models/user.py:87 users/templates/users/user_detail.html:107 #: users/models/user.py:89 users/templates/users/user_detail.html:107
#: users/templates/users/user_profile.html:112 #: users/templates/users/user_profile.html:112
msgid "Date expired" msgid "Date expired"
msgstr "失效日期" msgstr "失效日期"
#: perms/models.py:90 templates/_nav.html:34 #: perms/models.py:92 templates/_nav.html:34
msgid "Asset permission" msgid "Asset permission"
msgstr "资产授权" msgstr "资产授权"
...@@ -1898,6 +1934,8 @@ msgstr "添加资产" ...@@ -1898,6 +1934,8 @@ msgstr "添加资产"
#: perms/templates/perms/asset_permission_user.html:97 #: perms/templates/perms/asset_permission_user.html:97
#: perms/templates/perms/asset_permission_user.html:125 #: perms/templates/perms/asset_permission_user.html:125
#: users/templates/users/user_group_detail.html:95 #: users/templates/users/user_group_detail.html:95
#: xpack/plugins/orgs/templates/orgs/org_detail.html:93
#: xpack/plugins/orgs/templates/orgs/org_detail.html:130
msgid "Add" msgid "Add"
msgstr "添加" msgstr "添加"
...@@ -1963,28 +2001,28 @@ msgstr "添加用户组" ...@@ -1963,28 +2001,28 @@ msgstr "添加用户组"
msgid "Select user groups" msgid "Select user groups"
msgstr "选择用户组" msgstr "选择用户组"
#: perms/views.py:25 perms/views.py:55 perms/views.py:70 perms/views.py:85 #: perms/views.py:23 perms/views.py:53 perms/views.py:68 perms/views.py:83
#: perms/views.py:120 perms/views.py:151 templates/_nav.html:31 #: perms/views.py:118 perms/views.py:150 templates/_nav.html:31
msgid "Perms" msgid "Perms"
msgstr "权限管理" msgstr "权限管理"
#: perms/views.py:26 #: perms/views.py:24
msgid "Asset permission list" msgid "Asset permission list"
msgstr "资产授权列表" msgstr "资产授权列表"
#: perms/views.py:56 #: perms/views.py:54
msgid "Create asset permission" msgid "Create asset permission"
msgstr "创建权限规则" msgstr "创建权限规则"
#: perms/views.py:71 perms/views.py:86 #: perms/views.py:69 perms/views.py:84
msgid "Update asset permission" msgid "Update asset permission"
msgstr "更新资产授权" msgstr "更新资产授权"
#: perms/views.py:121 #: perms/views.py:119
msgid "Asset permission user list" msgid "Asset permission user list"
msgstr "资产授权用户列表" msgstr "资产授权用户列表"
#: perms/views.py:152 #: perms/views.py:151
msgid "Asset permission asset list" msgid "Asset permission asset list"
msgstr "资产授权资产列表" msgstr "资产授权资产列表"
...@@ -1996,14 +2034,14 @@ msgstr "商业支持" ...@@ -1996,14 +2034,14 @@ msgstr "商业支持"
msgid "Docs" msgid "Docs"
msgstr "文档" msgstr "文档"
#: templates/_header_bar.html:37 templates/_nav_user.html:9 users/forms.py:122 #: templates/_header_bar.html:37 templates/_nav_user.html:9 users/forms.py:148
#: users/templates/users/_user.html:39 #: users/templates/users/_user.html:39
#: users/templates/users/first_login.html:39 #: users/templates/users/first_login.html:39
#: users/templates/users/user_password_update.html:39 #: users/templates/users/user_password_update.html:39
#: users/templates/users/user_profile.html:17 #: users/templates/users/user_profile.html:17
#: users/templates/users/user_profile_update.html:37 #: users/templates/users/user_profile_update.html:37
#: users/templates/users/user_profile_update.html:57 #: users/templates/users/user_profile_update.html:57
#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:349 #: users/templates/users/user_pubkey_update.html:37 users/views/user.py:362
msgid "Profile" msgid "Profile"
msgstr "个人信息" msgstr "个人信息"
...@@ -2054,19 +2092,15 @@ msgstr "" ...@@ -2054,19 +2092,15 @@ msgstr ""
"\"%(user_pubkey_update)s\"> 链接 </a> 更新\n" "\"%(user_pubkey_update)s\"> 链接 </a> 更新\n"
" " " "
#: templates/_modal.html:21 #: templates/_nav.html:10 users/views/group.py:27 users/views/group.py:43
msgid "Close" #: users/views/group.py:61 users/views/group.py:78 users/views/group.py:94
msgstr "关闭" #: users/views/login.py:333 users/views/login.py:397 users/views/user.py:68
#: users/views/user.py:83 users/views/user.py:111 users/views/user.py:193
#: templates/_nav.html:10 users/views/group.py:28 users/views/group.py:44 #: users/views/user.py:349 users/views/user.py:399 users/views/user.py:434
#: users/views/group.py:62 users/views/group.py:79 users/views/group.py:95
#: users/views/login.py:332 users/views/login.py:390 users/views/user.py:67
#: users/views/user.py:82 users/views/user.py:104 users/views/user.py:180
#: users/views/user.py:336 users/views/user.py:386 users/views/user.py:421
msgid "Users" msgid "Users"
msgstr "用户管理" msgstr "用户管理"
#: templates/_nav.html:13 users/views/user.py:68 #: templates/_nav.html:13 users/views/user.py:69
msgid "User list" msgid "User list"
msgstr "用户列表" msgstr "用户列表"
...@@ -2094,17 +2128,21 @@ msgstr "命令记录" ...@@ -2094,17 +2128,21 @@ msgstr "命令记录"
msgid "Web terminal" msgid "Web terminal"
msgstr "Web终端" msgstr "Web终端"
#: templates/_nav.html:51 terminal/views/command.py:49 #: templates/_nav.html:52 terminal/views/command.py:50
#: terminal/views/session.py:75 terminal/views/session.py:93 #: terminal/views/session.py:75 terminal/views/session.py:93
#: terminal/views/session.py:115 terminal/views/terminal.py:31 #: terminal/views/session.py:115 terminal/views/terminal.py:31
#: terminal/views/terminal.py:46 terminal/views/terminal.py:58 #: terminal/views/terminal.py:46 terminal/views/terminal.py:58
msgid "Terminal" msgid "Terminal"
msgstr "终端管理" msgstr "终端管理"
#: templates/_nav.html:56 #: templates/_nav.html:59
msgid "Job Center" msgid "Job Center"
msgstr "作业中心" msgstr "作业中心"
#: templates/_nav.html:86
msgid "XPack"
msgstr ""
#: templates/captcha/image.html:3 #: templates/captcha/image.html:3
msgid "Play CAPTCHA as audio file" msgid "Play CAPTCHA as audio file"
msgstr "语言播放验证码" msgstr "语言播放验证码"
...@@ -2117,11 +2155,11 @@ msgstr "验证码" ...@@ -2117,11 +2155,11 @@ msgstr "验证码"
msgid "Filters" msgid "Filters"
msgstr "过滤" msgstr "过滤"
#: terminal/backends/command/models.py:13 #: terminal/backends/command/models.py:15
msgid "Input" msgid "Input"
msgstr "输入" msgstr "输入"
#: terminal/backends/command/models.py:15 #: terminal/backends/command/models.py:17
#: terminal/templates/terminal/command_list.html:75 #: terminal/templates/terminal/command_list.html:75
#: terminal/templates/terminal/terminal_list.html:33 #: terminal/templates/terminal/terminal_list.html:33
msgid "Session" msgid "Session"
...@@ -2140,62 +2178,62 @@ msgstr "" ...@@ -2140,62 +2178,62 @@ msgstr ""
"录像文件支持存储到服务器端硬盘、AWS S3、 阿里云 OSS 中,默认存储到服务器端硬" "录像文件支持存储到服务器端硬盘、AWS S3、 阿里云 OSS 中,默认存储到服务器端硬"
"盘, 更多查看文档" "盘, 更多查看文档"
#: terminal/models.py:17 #: terminal/models.py:18
msgid "Remote Address" msgid "Remote Address"
msgstr "远端地址" msgstr "远端地址"
#: terminal/models.py:18 #: terminal/models.py:19
msgid "SSH Port" msgid "SSH Port"
msgstr "SSH端口" msgstr "SSH端口"
#: terminal/models.py:19 #: terminal/models.py:20
msgid "HTTP Port" msgid "HTTP Port"
msgstr "HTTP端口" msgstr "HTTP端口"
#: terminal/models.py:98 #: terminal/models.py:99
msgid "Session Online" msgid "Session Online"
msgstr "在线会话" msgstr "在线会话"
#: terminal/models.py:99 #: terminal/models.py:100
msgid "CPU Usage" msgid "CPU Usage"
msgstr "CPU使用" msgstr "CPU使用"
#: terminal/models.py:100 #: terminal/models.py:101
msgid "Memory Used" msgid "Memory Used"
msgstr "内存使用" msgstr "内存使用"
#: terminal/models.py:101 #: terminal/models.py:102
msgid "Connections" msgid "Connections"
msgstr "连接数" msgstr "连接数"
#: terminal/models.py:102 #: terminal/models.py:103
msgid "Threads" msgid "Threads"
msgstr "线程数" msgstr "线程数"
#: terminal/models.py:103 #: terminal/models.py:104
msgid "Boot Time" msgid "Boot Time"
msgstr "运行时间" msgstr "运行时间"
#: terminal/models.py:132 terminal/templates/terminal/session_list.html:104 #: terminal/models.py:133 terminal/templates/terminal/session_list.html:104
msgid "Replay" msgid "Replay"
msgstr "回放" msgstr "回放"
#: terminal/models.py:133 terminal/templates/terminal/command_list.html:55 #: terminal/models.py:134 terminal/templates/terminal/command_list.html:55
#: terminal/templates/terminal/command_list.html:71 #: terminal/templates/terminal/command_list.html:71
#: terminal/templates/terminal/session_detail.html:48 #: terminal/templates/terminal/session_detail.html:48
#: terminal/templates/terminal/session_list.html:77 #: terminal/templates/terminal/session_list.html:77
msgid "Command" msgid "Command"
msgstr "命令" msgstr "命令"
#: terminal/models.py:136 #: terminal/models.py:137
msgid "Date last active" msgid "Date last active"
msgstr "最后活跃日期" msgstr "最后活跃日期"
#: terminal/models.py:138 #: terminal/models.py:139
msgid "Date end" msgid "Date end"
msgstr "结束日期" msgstr "结束日期"
#: terminal/models.py:155 #: terminal/models.py:156
msgid "Args" msgid "Args"
msgstr "参数" msgstr "参数"
...@@ -2213,7 +2251,7 @@ msgid "Session detail" ...@@ -2213,7 +2251,7 @@ msgid "Session detail"
msgstr "会话详情" msgstr "会话详情"
#: terminal/templates/terminal/session_detail.html:28 #: terminal/templates/terminal/session_detail.html:28
#: terminal/views/command.py:50 #: terminal/views/command.py:51
msgid "Command list" msgid "Command list"
msgstr "命令记录列表" msgstr "命令记录列表"
...@@ -2329,7 +2367,7 @@ msgid "" ...@@ -2329,7 +2367,7 @@ msgid ""
"You should use your ssh client tools connect terminal: {} <br /> <br />{}" "You should use your ssh client tools connect terminal: {} <br /> <br />{}"
msgstr "你可以使用ssh客户端工具连接终端" msgstr "你可以使用ssh客户端工具连接终端"
#: users/api.py:226 users/templates/users/login.html:50 #: users/api.py:232 users/templates/users/login.html:50
msgid "Log in frequently and try again later" msgid "Log in frequently and try again later"
msgstr "登录频繁, 稍后重试" msgstr "登录频繁, 稍后重试"
...@@ -2384,11 +2422,11 @@ msgstr "" ...@@ -2384,11 +2422,11 @@ msgstr ""
msgid "Invalid token or cache refreshed." msgid "Invalid token or cache refreshed."
msgstr "" msgstr ""
#: users/forms.py:39 #: users/forms.py:41
msgid "MFA code" msgid "MFA code"
msgstr "MFA 验证码" msgstr "MFA 验证码"
#: users/forms.py:50 users/models/user.py:59 #: users/forms.py:52 users/models/user.py:61
#: users/templates/users/_select_user_modal.html:15 #: users/templates/users/_select_user_modal.html:15
#: users/templates/users/user_detail.html:87 #: users/templates/users/user_detail.html:87
#: users/templates/users/user_list.html:25 #: users/templates/users/user_list.html:25
...@@ -2396,31 +2434,31 @@ msgstr "MFA 验证码" ...@@ -2396,31 +2434,31 @@ msgstr "MFA 验证码"
msgid "Role" msgid "Role"
msgstr "角色" msgstr "角色"
#: users/forms.py:53 users/forms.py:202 #: users/forms.py:55 users/forms.py:228
msgid "ssh public key" msgid "ssh public key"
msgstr "ssh公钥" msgstr "ssh公钥"
#: users/forms.py:54 users/forms.py:203 #: users/forms.py:56 users/forms.py:229
msgid "ssh-rsa AAAA..." msgid "ssh-rsa AAAA..."
msgstr "" msgstr ""
#: users/forms.py:55 #: users/forms.py:57
msgid "Paste user id_rsa.pub here." msgid "Paste user id_rsa.pub here."
msgstr "复制用户公钥到这里" msgstr "复制用户公钥到这里"
#: users/forms.py:73 users/templates/users/user_detail.html:208 #: users/forms.py:76 users/templates/users/user_detail.html:208
msgid "Join user groups" msgid "Join user groups"
msgstr "添加到用户组" msgstr "添加到用户组"
#: users/forms.py:84 users/forms.py:217 #: users/forms.py:110 users/forms.py:243
msgid "Public key should not be the same as your old one." msgid "Public key should not be the same as your old one."
msgstr "不能和原来的密钥相同" msgstr "不能和原来的密钥相同"
#: users/forms.py:88 users/forms.py:221 users/serializers.py:48 #: users/forms.py:114 users/forms.py:247 users/serializers.py:48
msgid "Not a valid ssh public key" msgid "Not a valid ssh public key"
msgstr "ssh密钥不合法" msgstr "ssh密钥不合法"
#: users/forms.py:128 #: users/forms.py:154
msgid "" msgid ""
"Tip: when enabled, you will enter the MFA binding process the next time you " "Tip: when enabled, you will enter the MFA binding process the next time you "
"log in. you can also directly bind in \"personal information -> quick " "log in. you can also directly bind in \"personal information -> quick "
...@@ -2429,17 +2467,17 @@ msgstr "" ...@@ -2429,17 +2467,17 @@ msgstr ""
"提示:启用之后您将会在下次登录时进入MFA绑定流程;您也可以在(个人信息->快速修" "提示:启用之后您将会在下次登录时进入MFA绑定流程;您也可以在(个人信息->快速修"
"改->更改MFA设置)中直接绑定!" "改->更改MFA设置)中直接绑定!"
#: users/forms.py:138 #: users/forms.py:164
msgid "* Enable MFA authentication to make the account more secure." msgid "* Enable MFA authentication to make the account more secure."
msgstr "* 启用MFA认证,使账号更加安全." msgstr "* 启用MFA认证,使账号更加安全."
#: users/forms.py:143 users/models/authentication.py:75 users/models/user.py:71 #: users/forms.py:169 users/models/authentication.py:75 users/models/user.py:73
#: users/templates/users/first_login.html:45 #: users/templates/users/first_login.html:45
#: users/templates/users/login_log_list.html:54 #: users/templates/users/login_log_list.html:54
msgid "MFA" msgid "MFA"
msgstr "MFA" msgstr "MFA"
#: users/forms.py:148 #: users/forms.py:174
msgid "" msgid ""
"In order to protect you and your company, please keep your account, password " "In order to protect you and your company, please keep your account, password "
"and key sensitive information properly. (for example: setting complex " "and key sensitive information properly. (for example: setting complex "
...@@ -2448,41 +2486,41 @@ msgstr "" ...@@ -2448,41 +2486,41 @@ msgstr ""
"为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:" "为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:"
"设置复杂密码,启用MFA认证)" "设置复杂密码,启用MFA认证)"
#: users/forms.py:155 users/templates/users/first_login.html:48 #: users/forms.py:181 users/templates/users/first_login.html:48
#: users/templates/users/first_login.html:107 #: users/templates/users/first_login.html:107
#: users/templates/users/first_login.html:130 #: users/templates/users/first_login.html:130
msgid "Finish" msgid "Finish"
msgstr "完成" msgstr "完成"
#: users/forms.py:161 #: users/forms.py:187
msgid "Old password" msgid "Old password"
msgstr "原来密码" msgstr "原来密码"
#: users/forms.py:166 #: users/forms.py:192
msgid "New password" msgid "New password"
msgstr "新密码" msgstr "新密码"
#: users/forms.py:171 #: users/forms.py:197
msgid "Confirm password" msgid "Confirm password"
msgstr "确认密码" msgstr "确认密码"
#: users/forms.py:181 #: users/forms.py:207
msgid "Old password error" msgid "Old password error"
msgstr "原来密码错误" msgstr "原来密码错误"
#: users/forms.py:189 #: users/forms.py:215
msgid "Password does not match" msgid "Password does not match"
msgstr "密码不一致" msgstr "密码不一致"
#: users/forms.py:200 #: users/forms.py:226
msgid "Automatically configure and download the SSH key" msgid "Automatically configure and download the SSH key"
msgstr "自动配置并下载SSH密钥" msgstr "自动配置并下载SSH密钥"
#: users/forms.py:204 #: users/forms.py:230
msgid "Paste your id_rsa.pub here." msgid "Paste your id_rsa.pub here."
msgstr "复制你的公钥到这里" msgstr "复制你的公钥到这里"
#: users/forms.py:232 users/models/user.py:79 #: users/forms.py:258 users/models/user.py:81
#: users/templates/users/first_login.html:42 #: users/templates/users/first_login.html:42
#: users/templates/users/user_password_update.html:45 #: users/templates/users/user_password_update.html:45
#: users/templates/users/user_profile.html:68 #: users/templates/users/user_profile.html:68
...@@ -2545,49 +2583,49 @@ msgstr "状态" ...@@ -2545,49 +2583,49 @@ msgstr "状态"
msgid "Date login" msgid "Date login"
msgstr "登录日期" msgstr "登录日期"
#: users/models/user.py:30 users/models/user.py:341 #: users/models/user.py:32 users/models/user.py:356
msgid "Administrator" msgid "Administrator"
msgstr "管理员" msgstr "管理员"
#: users/models/user.py:32 #: users/models/user.py:34
msgid "Application" msgid "Application"
msgstr "应用程序" msgstr "应用程序"
#: users/models/user.py:35 users/templates/users/user_profile.html:92 #: users/models/user.py:37 users/templates/users/user_profile.html:92
#: users/templates/users/user_profile.html:163 #: users/templates/users/user_profile.html:163
#: users/templates/users/user_profile.html:166 #: users/templates/users/user_profile.html:166
msgid "Disable" msgid "Disable"
msgstr "禁用" msgstr "禁用"
#: users/models/user.py:36 users/templates/users/user_profile.html:90 #: users/models/user.py:38 users/templates/users/user_profile.html:90
#: users/templates/users/user_profile.html:170 #: users/templates/users/user_profile.html:170
msgid "Enable" msgid "Enable"
msgstr "启用" msgstr "启用"
#: users/models/user.py:37 users/templates/users/user_profile.html:88 #: users/models/user.py:39 users/templates/users/user_profile.html:88
msgid "Force enable" msgid "Force enable"
msgstr "强制启用" msgstr "强制启用"
#: users/models/user.py:51 users/templates/users/user_detail.html:71 #: users/models/user.py:53 users/templates/users/user_detail.html:71
#: users/templates/users/user_profile.html:59 #: users/templates/users/user_profile.html:59
msgid "Email" msgid "Email"
msgstr "邮件" msgstr "邮件"
#: users/models/user.py:62 #: users/models/user.py:64
msgid "Avatar" msgid "Avatar"
msgstr "头像" msgstr "头像"
#: users/models/user.py:65 users/templates/users/user_detail.html:82 #: users/models/user.py:67 users/templates/users/user_detail.html:82
msgid "Wechat" msgid "Wechat"
msgstr "微信" msgstr "微信"
#: users/models/user.py:94 users/templates/users/user_detail.html:103 #: users/models/user.py:96 users/templates/users/user_detail.html:103
#: users/templates/users/user_list.html:27 #: users/templates/users/user_list.html:27
#: users/templates/users/user_profile.html:100 #: users/templates/users/user_profile.html:100
msgid "Source" msgid "Source"
msgstr "用户来源" msgstr "用户来源"
#: users/models/user.py:344 #: users/models/user.py:359
msgid "Administrator is the super user of system" msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员" msgstr "Administrator是初始的超级管理员"
...@@ -2729,8 +2767,44 @@ msgstr "再次输入密码" ...@@ -2729,8 +2767,44 @@ msgstr "再次输入密码"
msgid "Setting" msgid "Setting"
msgstr "设置" msgstr "设置"
#: users/templates/users/reset_password.html:105
#: users/templates/users/user_password_update.html:98
#: users/templates/users/user_update.html:34
msgid "Very weak"
msgstr "很弱"
#: users/templates/users/reset_password.html:106
#: users/templates/users/user_password_update.html:99
#: users/templates/users/user_update.html:35
msgid "Weak"
msgstr "弱"
#: users/templates/users/reset_password.html:107
#: users/templates/users/user_password_update.html:100
#: users/templates/users/user_update.html:36
msgid "Normal"
msgstr "正常"
#: users/templates/users/reset_password.html:108
#: users/templates/users/user_password_update.html:101
#: users/templates/users/user_update.html:37
msgid "Medium"
msgstr "一般"
#: users/templates/users/reset_password.html:109
#: users/templates/users/user_password_update.html:102
#: users/templates/users/user_update.html:38
msgid "Strong"
msgstr "强"
#: users/templates/users/reset_password.html:110
#: users/templates/users/user_password_update.html:103
#: users/templates/users/user_update.html:39
msgid "Very strong"
msgstr "很强"
#: users/templates/users/user_create.html:4 #: users/templates/users/user_create.html:4
#: users/templates/users/user_list.html:16 users/views/user.py:82 #: users/templates/users/user_list.html:16 users/views/user.py:83
msgid "Create user" msgid "Create user"
msgstr "创建用户" msgstr "创建用户"
...@@ -2739,7 +2813,7 @@ msgid "Reset link will be generated and sent to the user. " ...@@ -2739,7 +2813,7 @@ msgid "Reset link will be generated and sent to the user. "
msgstr "生成重置密码连接,通过邮件发送给用户" msgstr "生成重置密码连接,通过邮件发送给用户"
#: users/templates/users/user_detail.html:19 #: users/templates/users/user_detail.html:19
#: users/templates/users/user_granted_asset.html:18 users/views/user.py:181 #: users/templates/users/user_granted_asset.html:18 users/views/user.py:194
msgid "User detail" msgid "User detail"
msgstr "用户详情" msgstr "用户详情"
...@@ -2828,37 +2902,44 @@ msgid "After unlocking the user, the user can log in normally." ...@@ -2828,37 +2902,44 @@ msgid "After unlocking the user, the user can log in normally."
msgstr "解除用户登录限制后,此用户即可正常登录" msgstr "解除用户登录限制后,此用户即可正常登录"
#: users/templates/users/user_group_create_update.html:31 #: users/templates/users/user_group_create_update.html:31
#: xpack/plugins/orgs/templates/orgs/org_create_update.html:32
msgid "Cancel" msgid "Cancel"
msgstr "取消" msgstr "取消"
#: users/templates/users/user_group_detail.html:22 #: users/templates/users/user_group_detail.html:22
#: users/templates/users/user_group_granted_asset.html:18 #: users/templates/users/user_group_granted_asset.html:18
#: users/views/group.py:80 #: users/views/group.py:79
msgid "User group detail" msgid "User group detail"
msgstr "用户组详情" msgstr "用户组详情"
#: users/templates/users/user_group_detail.html:86 #: users/templates/users/user_group_detail.html:86
#: xpack/plugins/orgs/templates/orgs/org_detail.html:121
msgid "Add user" msgid "Add user"
msgstr "添加用户" msgstr "添加用户"
#: users/templates/users/user_group_list.html:5 users/views/group.py:45 #: users/templates/users/user_group_list.html:5 users/views/group.py:44
#: xpack/templates/orgs/org_list.html:5
msgid "Create user group" msgid "Create user group"
msgstr "创建用户组" msgstr "创建用户组"
#: users/templates/users/user_group_list.html:82 #: users/templates/users/user_group_list.html:82
#: xpack/templates/orgs/org_list.html:82
msgid "This will delete the selected groups !!!" msgid "This will delete the selected groups !!!"
msgstr "删除选择组" msgstr "删除选择组"
#: users/templates/users/user_group_list.html:90 #: users/templates/users/user_group_list.html:90
#: xpack/templates/orgs/org_list.html:90
msgid "UserGroups Deleted." msgid "UserGroups Deleted."
msgstr "用户组删除" msgstr "用户组删除"
#: users/templates/users/user_group_list.html:91 #: users/templates/users/user_group_list.html:91
#: users/templates/users/user_group_list.html:96 #: users/templates/users/user_group_list.html:96
#: xpack/templates/orgs/org_list.html:91 xpack/templates/orgs/org_list.html:96
msgid "UserGroups Delete" msgid "UserGroups Delete"
msgstr "用户组删除" msgstr "用户组删除"
#: users/templates/users/user_group_list.html:95 #: users/templates/users/user_group_list.html:95
#: xpack/templates/orgs/org_list.html:95
msgid "UserGroup Deleting failed." msgid "UserGroup Deleting failed."
msgstr "用户组删除失败" msgstr "用户组删除失败"
...@@ -2883,8 +2964,8 @@ msgstr "用户删除失败" ...@@ -2883,8 +2964,8 @@ msgstr "用户删除失败"
msgid "Administrator Settings force MFA login" msgid "Administrator Settings force MFA login"
msgstr "管理员设置强制使用MFA登录" msgstr "管理员设置强制使用MFA登录"
#: users/templates/users/user_profile.html:116 users/views/user.py:211 #: users/templates/users/user_profile.html:116 users/views/user.py:224
#: users/views/user.py:265 #: users/views/user.py:278
msgid "User groups" msgid "User groups"
msgstr "用户组" msgstr "用户组"
...@@ -2930,7 +3011,7 @@ msgid "" ...@@ -2930,7 +3011,7 @@ msgid ""
"corresponding private key." "corresponding private key."
msgstr "新的公钥已设置成功,请下载对应的私钥" msgstr "新的公钥已设置成功,请下载对应的私钥"
#: users/templates/users/user_update.html:4 users/views/user.py:105 #: users/templates/users/user_update.html:4 users/views/user.py:112
msgid "Update user" msgid "Update user"
msgstr "更新用户" msgstr "更新用户"
...@@ -3072,119 +3153,170 @@ msgstr "密码或密钥不合法" ...@@ -3072,119 +3153,170 @@ msgstr "密码或密钥不合法"
msgid "Bit" msgid "Bit"
msgstr " 位" msgstr " 位"
#: users/views/group.py:29 #: users/views/group.py:28
msgid "User group list" msgid "User group list"
msgstr "用户组列表" msgstr "用户组列表"
#: users/views/group.py:63 #: users/views/group.py:62
msgid "Update user group" msgid "Update user group"
msgstr "更新用户组" msgstr "更新用户组"
#: users/views/group.py:96 #: users/views/group.py:95
msgid "User group granted asset" msgid "User group granted asset"
msgstr "用户组授权资产" msgstr "用户组授权资产"
#: users/views/login.py:76 #: users/views/login.py:77
msgid "Please enable cookies and try again." msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie" msgstr "设置你的浏览器支持cookie"
#: users/views/login.py:180 users/views/user.py:506 users/views/user.py:531 #: users/views/login.py:181 users/views/user.py:519 users/views/user.py:544
msgid "MFA code invalid" msgid "MFA code invalid"
msgstr "MFA码认证失败" msgstr "MFA码认证失败"
#: users/views/login.py:209 #: users/views/login.py:210
msgid "Logout success" msgid "Logout success"
msgstr "退出登录成功" msgstr "退出登录成功"
#: users/views/login.py:210 #: users/views/login.py:211
msgid "Logout success, return login page" msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面" msgstr "退出登录成功,返回到登录页面"
#: users/views/login.py:226 #: users/views/login.py:227
msgid "Email address invalid, please input again" msgid "Email address invalid, please input again"
msgstr "邮箱地址错误,重新输入" msgstr "邮箱地址错误,重新输入"
#: users/views/login.py:239 #: users/views/login.py:240
msgid "Send reset password message" msgid "Send reset password message"
msgstr "发送重置密码邮件" msgstr "发送重置密码邮件"
#: users/views/login.py:240 #: users/views/login.py:241
msgid "Send reset password mail success, login your mail box and follow it " msgid "Send reset password mail success, login your mail box and follow it "
msgstr "" msgstr ""
"发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)" "发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)"
#: users/views/login.py:253 #: users/views/login.py:254
msgid "Reset password success" msgid "Reset password success"
msgstr "重置密码成功" msgstr "重置密码成功"
#: users/views/login.py:254 #: users/views/login.py:255
msgid "Reset password success, return to login page" msgid "Reset password success, return to login page"
msgstr "重置密码成功,返回到登录页面" msgstr "重置密码成功,返回到登录页面"
#: users/views/login.py:275 users/views/login.py:288 #: users/views/login.py:276 users/views/login.py:289
msgid "Token invalid or expired" msgid "Token invalid or expired"
msgstr "Token错误或失效" msgstr "Token错误或失效"
#: users/views/login.py:284 #: users/views/login.py:285
msgid "Password not same" msgid "Password not same"
msgstr "密码不一致" msgstr "密码不一致"
#: users/views/login.py:294 users/views/user.py:120 users/views/user.py:404 #: users/views/login.py:295 users/views/user.py:127 users/views/user.py:417
msgid "* Your password does not meet the requirements" msgid "* Your password does not meet the requirements"
msgstr "* 您的密码不符合要求" msgstr "* 您的密码不符合要求"
#: users/views/login.py:332 #: users/views/login.py:333
msgid "First login" msgid "First login"
msgstr "首次登陆" msgstr "首次登陆"
#: users/views/login.py:391 #: users/views/login.py:398
msgid "Login log list" msgid "Login log list"
msgstr "登录日志" msgstr "登录日志"
#: users/views/user.py:131 #: users/views/user.py:144
msgid "Bulk update user success" msgid "Bulk update user success"
msgstr "批量更新用户成功" msgstr "批量更新用户成功"
#: users/views/user.py:240 #: users/views/user.py:253
msgid "Invalid file." msgid "Invalid file."
msgstr "文件不合法" msgstr "文件不合法"
#: users/views/user.py:337 #: users/views/user.py:350
msgid "User granted assets" msgid "User granted assets"
msgstr "用户授权资产" msgstr "用户授权资产"
#: users/views/user.py:368 #: users/views/user.py:381
msgid "Profile setting" msgid "Profile setting"
msgstr "个人信息设置" msgstr "个人信息设置"
#: users/views/user.py:387 #: users/views/user.py:400
msgid "Password update" msgid "Password update"
msgstr "密码更新" msgstr "密码更新"
#: users/views/user.py:422 #: users/views/user.py:435
msgid "Public key update" msgid "Public key update"
msgstr "密钥更新" msgstr "密钥更新"
#: users/views/user.py:463 #: users/views/user.py:476
msgid "Password invalid" msgid "Password invalid"
msgstr "用户名或密码无效" msgstr "用户名或密码无效"
#: users/views/user.py:557 #: users/views/user.py:570
msgid "MFA enable success" msgid "MFA enable success"
msgstr "MFA 绑定成功" msgstr "MFA 绑定成功"
#: users/views/user.py:558 #: users/views/user.py:571
msgid "MFA enable success, return login page" msgid "MFA enable success, return login page"
msgstr "MFA 绑定成功,返回到登录页面" msgstr "MFA 绑定成功,返回到登录页面"
#: users/views/user.py:560 #: users/views/user.py:573
msgid "MFA disable success" msgid "MFA disable success"
msgstr "MFA 解绑成功" msgstr "MFA 解绑成功"
#: users/views/user.py:561 #: users/views/user.py:574
msgid "MFA disable success, return login page" msgid "MFA disable success, return login page"
msgstr "MFA 解绑成功,返回登录页面" msgstr "MFA 解绑成功,返回登录页面"
#: xpack/plugins/orgs/forms.py:14
#: xpack/plugins/orgs/templates/orgs/org_detail.html:76
#: xpack/plugins/orgs/templates/orgs/org_list.html:13
msgid "Admin"
msgstr "管理员"
#: xpack/plugins/orgs/forms.py:18
msgid "Select admins"
msgstr "选择管理员"
#: xpack/plugins/orgs/meta.py:7
msgid "Organization"
msgstr "组织管理"
#: xpack/plugins/orgs/templates/orgs/org_detail.html:22
#: xpack/plugins/orgs/views.py:73
msgid "Org detail"
msgstr "组织详情"
#: xpack/plugins/orgs/templates/orgs/org_detail.html:84
msgid "Add admin"
msgstr "添加管理员"
#: xpack/plugins/orgs/templates/orgs/org_list.html:5
msgid "Create organization "
msgstr "创建组织"
#: xpack/plugins/orgs/views.py:24
msgid "Org"
msgstr "组织"
#: xpack/plugins/orgs/views.py:25
msgid "Org list"
msgstr "组织列表"
#: xpack/plugins/orgs/views.py:40 xpack/plugins/orgs/views.py:56
#: xpack/plugins/orgs/views.py:72
msgid "Orgs"
msgstr "组织"
#: xpack/plugins/orgs/views.py:41
msgid "Create org"
msgstr "创建组织"
#: xpack/plugins/orgs/views.py:57
msgid "Update org"
msgstr "更新组织"
#~ msgid "* required Must set exact system platform, Windows, Linux ..."
#~ msgstr "* required 必须准确设置操作系统平台,如Windows, Linux ..."
#~ msgid "Unblock user successfully. " #~ msgid "Unblock user successfully. "
#~ msgstr "解除登录限制成功" #~ msgstr "解除登录限制成功"
......
...@@ -56,6 +56,7 @@ ALLOWED_HOSTS = CONFIG.ALLOWED_HOSTS or [] ...@@ -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 = [ ...@@ -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 = [ ...@@ -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 = [ ...@@ -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 = [ ...@@ -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 = { ...@@ -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 = { ...@@ -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 = { ...@@ -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 ...@@ -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'
}
},
}
\ No newline at end of file
# ~*~ 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(
openapi.Info(
title="Jumpserver API Docs",
default_version='v1',
description="Jumpserver Restful api docs",
terms_of_service="https://www.jumpserver.org",
contact=openapi.Contact(email="support@fit2cloud.com"),
license=openapi.License(name="GPLv2 License"),
),
public=True,
permission_classes=(permissions.AllowAny,),
)
api_url_pattern = re.compile(r'^/api/(?P<version>\w+)/(?P<app>\w+)/(?P<extra>.*)$')
class HttpResponseTemporaryRedirect(HttpResponse):
status_code = 307
def __init__(self, redirect_to):
HttpResponse.__init__(self)
self['Location'] = iri_to_uri(redirect_to)
@csrf_exempt
def redirect_format_api(request, *args, **kwargs):
_path, query = request.path, request.GET.urlencode()
matched = api_url_pattern.match(_path)
if matched:
version, app, extra = matched.groups()
_path = '/api/{app}/{version}/{extra}?{query}'.format(**{
"app": app, "version": version, "extra": extra,
"query": query
})
return HttpResponseTemporaryRedirect(_path)
else:
return Response({"msg": "Redirect url failed: {}".format(_path)}, status=404)
v1_api_patterns = [
path('users/v1/', include('users.urls.api_urls', namespace='api-users')),
path('assets/v1/', include('assets.urls.api_urls', namespace='api-assets')),
path('perms/v1/', include('perms.urls.api_urls', namespace='api-perms')),
path('terminal/v1/', include('terminal.urls.api_urls', namespace='api-terminal')),
path('ops/v1/', include('ops.urls.api_urls', namespace='api-ops')),
path('audits/v1/', include('audits.urls.api_urls', namespace='api-audits')),
path('orgs/v1/', include('orgs.urls.api_urls', namespace='api-orgs')),
path('common/v1/', include('common.urls.api_urls', namespace='api-common')),
]
app_view_patterns = [
path('users/', include('users.urls.views_urls', namespace='users')),
path('assets/', include('assets.urls.views_urls', namespace='assets')),
path('perms/', include('perms.urls.views_urls', namespace='perms')),
path('terminal/', include('terminal.urls.views_urls', namespace='terminal')),
path('ops/', include('ops.urls.view_urls', namespace='ops')),
path('audits/', include('audits.urls.view_urls', namespace='audits')),
path('orgs/', include('orgs.urls.views_urls', namespace='orgs')),
]
if settings.XPACK_ENABLED:
app_view_patterns.append(path('xpack/', include('xpack.urls', namespace='xpack')))
urlpatterns = [ urlpatterns = [
url(r'^$', IndexView.as_view(), name='index'), path('', IndexView.as_view(), name='index'),
url(r'^luna/', LunaView.as_view(), name='luna-error'), path('luna/', LunaView.as_view(), name='luna-error'),
url(r'^users/', include('users.urls.views_urls', namespace='users')), path('settings/', include('common.urls.view_urls', namespace='settings')),
url(r'^assets/', include('assets.urls.views_urls', namespace='assets')), path('common/', include('common.urls.view_urls', namespace='common')),
url(r'^perms/', include('perms.urls.views_urls', namespace='perms')), path('api/v1/', redirect_format_api),
url(r'^terminal/', include('terminal.urls.views_urls', namespace='terminal')), path('api/', include(v1_api_patterns)),
url(r'^ops/', include('ops.urls.view_urls', namespace='ops')),
url(r'^audits/', include('audits.urls.view_urls', namespace='audits')),
url(r'^settings/', include('common.urls.view_urls', namespace='settings')),
url(r'^common/', include('common.urls.view_urls', namespace='common')),
# 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 # External apps url
url(r'^captcha/', include('captcha.urls')), 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'),
] ]
...@@ -4,12 +4,13 @@ from django.http import HttpResponse ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -175,4 +177,7 @@ class LunaView(View):
Luna是单独部署的一个程序,你需要部署luna,coco,配置nginx做url分发, Luna是单独部署的一个程序,你需要部署luna,coco,配置nginx做url分发,
如果你看到了这个页面,证明你访问的不是nginx监听的端口,祝你好运 如果你看到了这个页面,证明你访问的不是nginx监听的端口,祝你好运
""" """
return HttpResponse(msg) return HttpResponse(msg)
\ No newline at end of file
...@@ -8,7 +8,7 @@ from django.utils.translation import ugettext as _ ...@@ -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 ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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()
......
...@@ -7,5 +7,9 @@ class OpsConfig(AppConfig): ...@@ -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
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from users.permissions import IsSuperUser
from users.utils import AdminUserRequiredMixin
\ No newline at end of file
...@@ -263,7 +263,8 @@ class AdHoc(models.Model): ...@@ -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):
......
# ~*~ 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 ...@@ -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
# ~*~ 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"] ...@@ -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'),
] ]
...@@ -6,7 +6,7 @@ from django.views.generic import ListView, DetailView, TemplateView ...@@ -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):
......
from django.contrib import admin
# Register your models here.
# -*- 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,)
from django.apps import AppConfig
class OrgsConfig(AppConfig):
name = 'orgs'
# -*- 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
# -*- 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
# -*- 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()
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
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']
from django.test import TestCase
# Create your tests here.
# -*- coding: utf-8 -*-
#
# -*- 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
# -*- 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')
]
# -*- 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'))
from django.shortcuts import redirect, reverse
from django.http import HttpResponseForbidden
from django.views.generic import DetailView, View
from .models import Organization
class SwitchOrgView(DetailView):
model = Organization
object = None
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
self.object = Organization.get_instance(pk)
request.session['oid'] = self.object.id.__str__()
return redirect('index')
class SwitchToAOrgView(View):
def get(self, request, *args, **kwargs):
admin_orgs = Organization.get_user_admin_orgs(request.user)
if not admin_orgs:
return HttpResponseForbidden()
default_org = Organization.default()
if default_org in admin_orgs:
redirect_org = default_org
else:
redirect_org = admin_orgs[0]
return redirect(reverse('orgs:org-switch', kwargs={'pk': redirect_org.id}))
...@@ -7,7 +7,8 @@ from rest_framework.generics import ListAPIView, get_object_or_404, RetrieveUpda ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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()
......
...@@ -4,11 +4,13 @@ from __future__ import absolute_import, unicode_literals ...@@ -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): ...@@ -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(
......
# ~*~ 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
......
...@@ -6,6 +6,8 @@ from django.utils import timezone ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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"))
......
...@@ -80,12 +80,12 @@ function onSelected(event, treeNode) { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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(){ ...@@ -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(",");
......
# 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
......
# 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'),
] ]
...@@ -13,7 +13,7 @@ logger = get_logger(__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: ...@@ -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)
......
...@@ -3,22 +3,20 @@ ...@@ -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): ...@@ -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, ...@@ -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, ...@@ -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):
......
...@@ -335,13 +335,13 @@ div.dataTables_wrapper div.dataTables_filter { ...@@ -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;
} }
......
...@@ -179,7 +179,7 @@ function APIUpdateAttr(props) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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 ...@@ -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);
} }
<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>
......
...@@ -35,7 +35,7 @@ ...@@ -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 %}
......
...@@ -2,7 +2,7 @@ ...@@ -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' %}
......
{% 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 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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>
\ No newline at end of file {% endif %}
<script>
$(document).ready(function () {
var current_org = '{{ CURRENT_ORG.name }}';
console.log(current_org);
})
</script>
\ No newline at end of file
{% 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 () {
......
...@@ -25,8 +25,7 @@ from .hands import SystemUser ...@@ -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__) ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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):
......
...@@ -21,7 +21,7 @@ class CommandStore(CommandBase): ...@@ -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): ...@@ -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)
......
...@@ -4,8 +4,10 @@ import uuid ...@@ -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"))
......
...@@ -12,5 +12,6 @@ class SessionCommandSerializer(serializers.Serializer): ...@@ -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()
...@@ -2,7 +2,4 @@ ...@@ -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
\ No newline at end of file
...@@ -8,6 +8,7 @@ from django.utils import timezone ...@@ -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): ...@@ -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'),
......
...@@ -150,12 +150,7 @@ ...@@ -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"
......
...@@ -2,7 +2,7 @@ ...@@ -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 ...@@ -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
...@@ -2,7 +2,7 @@ ...@@ -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' ...@@ -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')
] ]
...@@ -8,7 +8,8 @@ from django.http import HttpResponse ...@@ -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
......
...@@ -7,7 +7,7 @@ from django.utils.translation import ugettext as _ ...@@ -7,7 +7,7 @@ from django.utils.translation import ugettext as _
from django.utils import timezone from django.utils import timezone
from django.conf import settings from django.conf import settings
from users.utils import AdminUserRequiredMixin from common.permissions import AdminUserRequiredMixin
from common.mixins import DatetimeSearchMixin from common.mixins import DatetimeSearchMixin
from ..models import Session, Command, Terminal from ..models import Session, Command, Terminal
from ..backends import get_multi_command_storage from ..backends import get_multi_command_storage
......
...@@ -10,7 +10,7 @@ from django.urls import reverse_lazy, reverse ...@@ -10,7 +10,7 @@ from django.urls import reverse_lazy, reverse
from common.mixins import JSONResponseMixin from common.mixins import JSONResponseMixin
from ..models import Terminal from ..models import Terminal
from ..forms import TerminalForm from ..forms import TerminalForm
from ..hands import AdminUserRequiredMixin from common.permissions import AdminUserRequiredMixin
__all__ = [ __all__ = [
......
...@@ -3,6 +3,7 @@ import uuid ...@@ -3,6 +3,7 @@ import uuid
from django.core.cache import cache from django.core.cache import cache
from django.urls import reverse from django.urls import reverse
from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from rest_framework import generics from rest_framework import generics
...@@ -16,10 +17,12 @@ from .serializers import UserSerializer, UserGroupSerializer, \ ...@@ -16,10 +17,12 @@ from .serializers import UserSerializer, UserGroupSerializer, \
UserUpdateGroupSerializer, ChangeUserPasswordSerializer UserUpdateGroupSerializer, ChangeUserPasswordSerializer
from .tasks import write_login_log_async from .tasks import write_login_log_async
from .models import User, UserGroup, LoginLog from .models import User, UserGroup, LoginLog
from .permissions import IsSuperUser, IsValidUser, IsCurrentUserOrReadOnly, \
IsSuperUserOrAppUser
from .utils import check_user_valid, generate_token, get_login_ip, \ from .utils import check_user_valid, generate_token, get_login_ip, \
check_otp_code, set_user_login_failed_count_to_cache, is_block_login check_otp_code, set_user_login_failed_count_to_cache, is_block_login
from .hands import Asset, SystemUser
from orgs.utils import current_org
from common.permissions import IsOrgAdmin, IsCurrentUserOrReadOnly, IsOrgAdminOrAppUser
from .hands import Asset, SystemUser
from common.mixins import IDInFilterMixin from common.mixins import IDInFilterMixin
from common.utils import get_logger from common.utils import get_logger
...@@ -30,17 +33,23 @@ logger = get_logger(__name__) ...@@ -30,17 +33,23 @@ logger = get_logger(__name__)
class UserViewSet(IDInFilterMixin, BulkModelViewSet): class UserViewSet(IDInFilterMixin, BulkModelViewSet):
queryset = User.objects.exclude(role="App") queryset = User.objects.exclude(role="App")
serializer_class = UserSerializer serializer_class = UserSerializer
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
filter_fields = ('username', 'email', 'name', 'id') filter_fields = ('username', 'email', 'name', 'id')
def get_queryset(self):
queryset = super().get_queryset()
org_users = current_org.get_org_users().values_list('id', flat=True)
queryset = queryset.filter(id__in=org_users)
return queryset
def get_permissions(self): def get_permissions(self):
if self.action == "retrieve": if self.action == "retrieve":
self.permission_classes = (IsSuperUserOrAppUser,) self.permission_classes = (IsOrgAdminOrAppUser,)
return super().get_permissions() return super().get_permissions()
class ChangeUserPasswordApi(generics.RetrieveUpdateAPIView): class ChangeUserPasswordApi(generics.RetrieveUpdateAPIView):
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
queryset = User.objects.all() queryset = User.objects.all()
serializer_class = ChangeUserPasswordSerializer serializer_class = ChangeUserPasswordSerializer
...@@ -53,7 +62,7 @@ class ChangeUserPasswordApi(generics.RetrieveUpdateAPIView): ...@@ -53,7 +62,7 @@ class ChangeUserPasswordApi(generics.RetrieveUpdateAPIView):
class UserUpdateGroupApi(generics.RetrieveUpdateAPIView): class UserUpdateGroupApi(generics.RetrieveUpdateAPIView):
queryset = User.objects.all() queryset = User.objects.all()
serializer_class = UserUpdateGroupSerializer serializer_class = UserUpdateGroupSerializer
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
class UserResetPasswordApi(generics.UpdateAPIView): class UserResetPasswordApi(generics.UpdateAPIView):
...@@ -97,7 +106,7 @@ class UserUpdatePKApi(generics.UpdateAPIView): ...@@ -97,7 +106,7 @@ class UserUpdatePKApi(generics.UpdateAPIView):
class UserUnblockPKApi(generics.UpdateAPIView): class UserUnblockPKApi(generics.UpdateAPIView):
queryset = User.objects.all() queryset = User.objects.all()
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
serializer_class = UserSerializer serializer_class = UserSerializer
key_prefix_limit = "_LOGIN_LIMIT_{}_{}" key_prefix_limit = "_LOGIN_LIMIT_{}_{}"
key_prefix_block = "_LOGIN_BLOCK_{}" key_prefix_block = "_LOGIN_BLOCK_{}"
...@@ -114,13 +123,13 @@ class UserUnblockPKApi(generics.UpdateAPIView): ...@@ -114,13 +123,13 @@ class UserUnblockPKApi(generics.UpdateAPIView):
class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet): class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet):
queryset = UserGroup.objects.all() queryset = UserGroup.objects.all()
serializer_class = UserGroupSerializer serializer_class = UserGroupSerializer
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
class UserGroupUpdateUserApi(generics.RetrieveUpdateAPIView): class UserGroupUpdateUserApi(generics.RetrieveUpdateAPIView):
queryset = UserGroup.objects.all() queryset = UserGroup.objects.all()
serializer_class = UserGroupUpdateMemeberSerializer serializer_class = UserGroupUpdateMemeberSerializer
permission_classes = (IsSuperUser,) permission_classes = (IsOrgAdmin,)
class UserToken(APIView): class UserToken(APIView):
...@@ -298,17 +307,23 @@ class UserAuthApi(APIView): ...@@ -298,17 +307,23 @@ class UserAuthApi(APIView):
class UserConnectionTokenApi(APIView): class UserConnectionTokenApi(APIView):
permission_classes = (IsSuperUserOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
def post(self, request): def post(self, request):
user_id = request.data.get('user', '') user_id = request.data.get('user', '')
asset_id = request.data.get('asset', '') asset_id = request.data.get('asset', '')
system_user_id = request.data.get('system_user', '') system_user_id = request.data.get('system_user', '')
token = str(uuid.uuid4()) token = str(uuid.uuid4())
user = get_object_or_404(User, id=user_id)
asset = get_object_or_404(Asset, id=asset_id)
system_user = get_object_or_404(SystemUser, id=system_user_id)
value = { value = {
'user': user_id, 'user': user_id,
'username': user.username,
'asset': asset_id, 'asset': asset_id,
'system_user': system_user_id 'hostname': asset.hostname,
'system_user': system_user_id,
'system_user_name': system_user.name
} }
cache.set(token, value, timeout=20) cache.set(token, value, timeout=20)
return Response({"token": token}, status=201) return Response({"token": token}, status=201)
......
...@@ -6,6 +6,8 @@ from django.utils.translation import gettext_lazy as _ ...@@ -6,6 +6,8 @@ from django.utils.translation import gettext_lazy as _
from captcha.fields import CaptchaField from captcha.fields import CaptchaField
from common.utils import validate_ssh_public_key from common.utils import validate_ssh_public_key
from orgs.mixins import OrgModelForm
from orgs.utils import current_org
from .models import User, UserGroup from .models import User, UserGroup
...@@ -39,7 +41,7 @@ class UserCheckOtpCodeForm(forms.Form): ...@@ -39,7 +41,7 @@ class UserCheckOtpCodeForm(forms.Form):
otp_code = forms.CharField(label=_('MFA code'), max_length=6) otp_code = forms.CharField(label=_('MFA code'), max_length=6)
class UserCreateUpdateForm(forms.ModelForm): class UserCreateUpdateForm(OrgModelForm):
role_choices = ((i, n) for i, n in User.ROLE_CHOICES if i != User.ROLE_APP) role_choices = ((i, n) for i, n in User.ROLE_CHOICES if i != User.ROLE_APP)
password = forms.CharField( password = forms.CharField(
label=_('Password'), widget=forms.PasswordInput, label=_('Password'), widget=forms.PasswordInput,
...@@ -67,15 +69,39 @@ class UserCreateUpdateForm(forms.ModelForm): ...@@ -67,15 +69,39 @@ class UserCreateUpdateForm(forms.ModelForm):
'email': '* required', 'email': '* required',
} }
widgets = { widgets = {
'otp_level': forms.RadioSelect(),
'groups': forms.SelectMultiple( 'groups': forms.SelectMultiple(
attrs={ attrs={
'class': 'select2', 'class': 'select2',
'data-placeholder': _('Join user groups') 'data-placeholder': _('Join user groups')
} }
), )
'otp_level': forms.RadioSelect(),
} }
def __init__(self, *args, **kwargs):
self.request = kwargs.pop("request", None)
super(UserCreateUpdateForm, self).__init__(*args, **kwargs)
roles = []
# Super admin user
if self.request.user.is_superuser:
roles.append((User.ROLE_ADMIN, dict(User.ROLE_CHOICES).get(User.ROLE_ADMIN)))
roles.append((User.ROLE_USER, dict(User.ROLE_CHOICES).get(User.ROLE_USER)))
# Org admin user
else:
user = kwargs.get('instance')
# Update
if user:
role = kwargs.get('instance').role
roles.append((role, dict(User.ROLE_CHOICES).get(role)))
# Create
else:
roles.append((User.ROLE_USER, dict(User.ROLE_CHOICES).get(User.ROLE_USER)))
field = self.fields['role']
field.choices = set(roles)
def clean_public_key(self): def clean_public_key(self):
public_key = self.cleaned_data['public_key'] public_key = self.cleaned_data['public_key']
if not public_key: if not public_key:
...@@ -237,7 +263,7 @@ class UserBulkUpdateForm(forms.ModelForm): ...@@ -237,7 +263,7 @@ class UserBulkUpdateForm(forms.ModelForm):
required=True, required=True,
help_text='* required', help_text='* required',
label=_('Select users'), label=_('Select users'),
queryset=User.objects.all(), queryset = User.objects.all(),
widget=forms.SelectMultiple( widget=forms.SelectMultiple(
attrs={ attrs={
'class': 'select2', 'class': 'select2',
...@@ -276,6 +302,10 @@ class UserBulkUpdateForm(forms.ModelForm): ...@@ -276,6 +302,10 @@ class UserBulkUpdateForm(forms.ModelForm):
return users return users
def user_limit_to():
return {"orgs": current_org}
class UserGroupForm(forms.ModelForm): class UserGroupForm(forms.ModelForm):
users = forms.ModelMultipleChoiceField( users = forms.ModelMultipleChoiceField(
queryset=User.objects.exclude(role=User.ROLE_APP), queryset=User.objects.exclude(role=User.ROLE_APP),
...@@ -287,17 +317,21 @@ class UserGroupForm(forms.ModelForm): ...@@ -287,17 +317,21 @@ class UserGroupForm(forms.ModelForm):
} }
), ),
required=False, required=False,
limit_choices_to=user_limit_to
) )
def __init__(self, **kwargs): def __init__(self, **kwargs):
instance = kwargs.get('instance') instance = kwargs.get('instance')
if instance: if instance:
initial = kwargs.get('initial', {}) initial = kwargs.get('initial', {})
initial.update({ initial.update({'users': instance.users.all()})
'users': instance.users.all(),
})
kwargs['initial'] = initial kwargs['initial'] = initial
super().__init__(**kwargs) super().__init__(**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()
def save(self, commit=True): def save(self, commit=True):
group = super().save(commit=commit) group = super().save(commit=commit)
...@@ -315,30 +349,11 @@ class UserGroupForm(forms.ModelForm): ...@@ -315,30 +349,11 @@ class UserGroupForm(forms.ModelForm):
} }
# class UserGroupPrivateAssetPermissionForm(forms.ModelForm): class OrgUserField(forms.ModelMultipleChoiceField):
# def save(self, commit=True):
# self.instance = super(UserGroupPrivateAssetPermissionForm, self)\ def get_limit_choices_to(self):
# .save(commit=commit)
# self.instance.user_groups = [self.user_group] return {"orgs"}
# self.instance.save()
# return self.instance
#
# class Meta:
# model = AssetPermission
# fields = [
# 'assets', 'asset_groups', 'system_users', 'name',
# ]
# widgets = {
# 'assets': forms.SelectMultiple(
# attrs={'class': 'select2',
# 'data-placeholder': _('Select assets')}),
# 'asset_groups': forms.SelectMultiple(
# attrs={'class': 'select2',
# 'data-placeholder': _('Select asset groups')}),
# 'system_users': forms.SelectMultiple(
# attrs={'class': 'select2',
# 'data-placeholder': _('Select system users')}),
# }
class FileForm(forms.Form): class FileForm(forms.Form):
......
...@@ -15,3 +15,4 @@ ...@@ -15,3 +15,4 @@
# from users.models import User # from users.models import User
# from perms.models import AssetPermission # from perms.models import AssetPermission
# from perms.utils import get_user_granted_assets, get_user_granted_asset_groups # from perms.utils import get_user_granted_assets, get_user_granted_asset_groups
from assets.models import Asset, SystemUser
...@@ -4,12 +4,14 @@ import uuid ...@@ -4,12 +4,14 @@ import uuid
from django.db import models, IntegrityError from django.db import models, IntegrityError
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelMixin
__all__ = ['UserGroup'] __all__ = ['UserGroup']
class UserGroup(models.Model): class UserGroup(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'))
comment = models.TextField(blank=True, verbose_name=_('Comment')) comment = models.TextField(blank=True, verbose_name=_('Comment'))
date_created = models.DateTimeField(auto_now_add=True, null=True, date_created = models.DateTimeField(auto_now_add=True, null=True,
verbose_name=_('Date created')) verbose_name=_('Date created'))
...@@ -20,6 +22,7 @@ class UserGroup(models.Model): ...@@ -20,6 +22,7 @@ class UserGroup(models.Model):
class Meta: class Meta:
ordering = ['name'] ordering = ['name']
unique_together = [('org_id', 'name')]
verbose_name = _("User group") verbose_name = _("User group")
@classmethod @classmethod
......
...@@ -6,7 +6,7 @@ from collections import OrderedDict ...@@ -6,7 +6,7 @@ from collections import OrderedDict
from django.conf import settings from django.conf import settings
from django.contrib.auth.hashers import make_password from django.contrib.auth.hashers import make_password
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser, UserManager
from django.core import signing from django.core import signing
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 _
...@@ -15,6 +15,8 @@ from django.shortcuts import reverse ...@@ -15,6 +15,8 @@ from django.shortcuts import reverse
from common.utils import get_signer, date_expired_default from common.utils import get_signer, date_expired_default
from common.models import Setting from common.models import Setting
from orgs.mixins import OrgManager
from orgs.utils import current_org
__all__ = ['User'] __all__ = ['User']
...@@ -116,7 +118,7 @@ class User(AbstractUser): ...@@ -116,7 +118,7 @@ class User(AbstractUser):
@otp_secret_key.setter @otp_secret_key.setter
def otp_secret_key(self, item): def otp_secret_key(self, item):
self._otp_secret_key = signer.sign(item).decode('utf-8') self._otp_secret_key = signer.sign(item)
def get_absolute_url(self): def get_absolute_url(self):
return reverse('users:user-detail', args=(self.id,)) return reverse('users:user-detail', args=(self.id,))
...@@ -186,6 +188,18 @@ class User(AbstractUser): ...@@ -186,6 +188,18 @@ class User(AbstractUser):
else: else:
self.role = 'User' self.role = 'User'
@property
def admin_orgs(self):
from orgs.models import Organization
return Organization.get_user_admin_orgs(self)
@property
def is_org_admin(self):
if self.is_superuser or self.admin_orgs.exists():
return True
else:
return False
@property @property
def is_app(self): def is_app(self):
return self.role == 'App' return self.role == 'App'
...@@ -207,8 +221,9 @@ class User(AbstractUser): ...@@ -207,8 +221,9 @@ class User(AbstractUser):
if self.username == 'admin': if self.username == 'admin':
self.role = 'Admin' self.role = 'Admin'
self.is_active = True self.is_active = True
super().save(*args, **kwargs) super().save(*args, **kwargs)
if current_org and current_org.is_real():
self.orgs.add(current_org.id)
@property @property
def private_token(self): def private_token(self):
......
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from rest_framework import permissions
class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission):
"""Allows access to valid user, is active and not expired"""
def has_permission(self, request, view):
return super(IsValidUser, self).has_permission(request, view) \
and request.user.is_valid
class IsAppUser(IsValidUser):
"""Allows access only to app user """
def has_permission(self, request, view):
return super(IsAppUser, self).has_permission(request, view) \
and request.user.is_app
class IsSuperUser(IsValidUser):
"""Allows access only to superuser"""
def has_permission(self, request, view):
return super(IsSuperUser, self).has_permission(request, view) \
and request.user.is_superuser
class IsSuperUserOrAppUser(IsValidUser):
"""Allows access between superuser and app user"""
def has_permission(self, request, view):
return super(IsSuperUserOrAppUser, self).has_permission(request, view) \
and (request.user.is_superuser or request.user.is_app)
class IsSuperUserOrAppUserOrUserReadonly(IsSuperUserOrAppUser):
def has_permission(self, request, view):
if IsValidUser.has_permission(self, request, view) \
and request.method in permissions.SAFE_METHODS:
return True
else:
return IsSuperUserOrAppUser.has_permission(self, request, view)
class IsCurrentUserOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj == request.user
...@@ -14,7 +14,7 @@ signer = get_signer() ...@@ -14,7 +14,7 @@ signer = get_signer()
class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
groups_display = serializers.SerializerMethodField() groups_display = serializers.SerializerMethodField()
groups = serializers.PrimaryKeyRelatedField(many=True, queryset=UserGroup.objects.all(), required=False) groups = serializers.PrimaryKeyRelatedField(many=True, queryset = UserGroup.objects.all(), required=False)
class Meta: class Meta:
model = User model = User
...@@ -71,7 +71,7 @@ class UserGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer): ...@@ -71,7 +71,7 @@ class UserGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer):
class UserGroupUpdateMemeberSerializer(serializers.ModelSerializer): class UserGroupUpdateMemeberSerializer(serializers.ModelSerializer):
users = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects.all()) users = serializers.PrimaryKeyRelatedField(many=True, queryset = User.objects.all())
class Meta: class Meta:
model = UserGroup model = UserGroup
......
...@@ -80,12 +80,7 @@ ...@@ -80,12 +80,7 @@
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script> <script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
<script> <script>
$(document).ready(function() { $(document).ready(function() {
$('table').DataTable({ jumpserver.initStaticTable('table');
"searching": false,
"bInfo" : false,
"paging": false,
"order": []
});
$('#date .input-daterange').datepicker({ $('#date .input-daterange').datepicker({
format: "yyyy-mm-dd", format: "yyyy-mm-dd",
todayBtn: "linked", todayBtn: "linked",
......
...@@ -100,10 +100,18 @@ ...@@ -100,10 +100,18 @@
progress = $('#id_progress'), progress = $('#id_progress'),
password_check_rules = {{ password_check_rules|safe }}, password_check_rules = {{ password_check_rules|safe }},
minLength = {{ min_length }}, minLength = {{ min_length }},
top = 146, left = 170; top = 146, left = 170,
i18n_fallback = {
"veryWeak": "{% trans 'Very weak' %}",
"weak": "{% trans 'Weak' %}",
"normal": "{% trans 'Normal' %}",
"medium": "{% trans 'Medium' %}",
"strong": "{% trans 'Strong' %}",
"veryStrong": "{% trans 'Very strong' %}"
};
// 初始化popover // 初始化popover
initPopover(container, progress, idPassword, el, password_check_rules); initPopover(container, progress, idPassword, el, password_check_rules, i18n_fallback);
// 监听事件 // 监听事件
idPassword.on('focus', function () { idPassword.on('focus', function () {
......
...@@ -72,6 +72,7 @@ function initTable() { ...@@ -72,6 +72,7 @@ function initTable() {
} else { } else {
inited = true; inited = true;
} }
url = "{% url 'api-perms:user-assets' pk=object.id %}";
var options = { var options = {
ele: $('#user_assets_table'), ele: $('#user_assets_table'),
columnDefs: [ columnDefs: [
...@@ -102,36 +103,16 @@ function initTable() { ...@@ -102,36 +103,16 @@ function initTable() {
{data: "system_users_granted", orderable: false} {data: "system_users_granted", orderable: false}
] ]
}; };
return jumpserver.initDataTable(options); asset_table = jumpserver.initDataTable(options);
} }
function onSelected(event, treeNode) { function onSelected(event, treeNode) {
url = '{% url "api-perms:user-node-assets" pk=object.id node_id=DEFAULT_PK %}'; url = '{% url "api-perms:user-node-assets" pk=object.id node_id=DEFAULT_PK %}';
url = url.replace("{{ DEFAULT_PK }}", treeNode.id); url = url.replace("{{ DEFAULT_PK }}", treeNode.node_id);
setCookie('node_selected', treeNode.id);
asset_table = initTable();
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,24 +130,28 @@ function initTree() { ...@@ -149,24 +130,28 @@ function initTree() {
}; };
var zNodes = []; var zNodes = [];
$.get("{% url 'api-perms:user-nodes' pk=object.id %}", function(data, status){ $.get("{% url 'api-perms:user-nodes' pk=object.id %}", 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'];
value["iconSkin"] = value["is_node"] ? null : 'file';
}); });
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();
}); });
</script> </script>
{% endblock %} {% endblock %}
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<div class="verify"> <div class="verify">
<p style="margin: 20px auto;"><strong style="color: #000000">账号保护已开启,请根据提示完成以下操作</strong></p> <p style="margin: 20px auto;"><strong style="color: #000000">账号保护已开启,请根据提示完成以下操作</strong></p>
<img src="{% static 'img/otp_auth.png' %}" alt="" width="72px" height="117"> <img src="{% static 'img/otp_auth.png' %}" alt="" width="72px" height="117">
<p style="margin: 20px auto;">请在手机中打开Google Authenticator应用,输入6动态码</p> <p style="margin: 20px auto;">请在手机中打开Google Authenticator应用,输入6动态码</p>
</div> </div>
<form class="" role="form" method="post" action=""> <form class="" role="form" method="post" action="">
......
...@@ -93,10 +93,18 @@ ...@@ -93,10 +93,18 @@
password_check_rules = {{ password_check_rules|safe }}, password_check_rules = {{ password_check_rules|safe }},
minLength = {{ min_length }}, minLength = {{ min_length }},
top = idPassword.offset().top - $('.navbar').outerHeight(true) - $('.page-heading').outerHeight(true) - 10 + 34, top = idPassword.offset().top - $('.navbar').outerHeight(true) - $('.page-heading').outerHeight(true) - 10 + 34,
left = 377; left = 377,
i18n_fallback = {
"veryWeak": "{% trans 'Very weak' %}",
"weak": "{% trans 'Weak' %}",
"normal": "{% trans 'Normal' %}",
"medium": "{% trans 'Medium' %}",
"strong": "{% trans 'Strong' %}",
"veryStrong": "{% trans 'Very strong' %}"
};
// 初始化popover // 初始化popover
initPopover(container, progress, idPassword, el, password_check_rules); initPopover(container, progress, idPassword, el, password_check_rules, i18n_fallback);
// 监听事件 // 监听事件
idPassword.on('focus', function () { idPassword.on('focus', function () {
......
...@@ -29,10 +29,18 @@ ...@@ -29,10 +29,18 @@
password_check_rules = {{ password_check_rules|safe }}, password_check_rules = {{ password_check_rules|safe }},
minLength = {{ min_length }}, minLength = {{ min_length }},
top = idPassword.offset().top - $('.navbar').outerHeight(true) - $('.page-heading').outerHeight(true) - 10 + 34, top = idPassword.offset().top - $('.navbar').outerHeight(true) - $('.page-heading').outerHeight(true) - 10 + 34,
left = 377; left = 377,
i18n_fallback = {
"veryWeak": "{% trans 'Very weak' %}",
"weak": "{% trans 'Weak' %}",
"normal": "{% trans 'Normal' %}",
"medium": "{% trans 'Medium' %}",
"strong": "{% trans 'Strong' %}",
"veryStrong": "{% trans 'Very strong' %}"
};
// 初始化popover // 初始化popover
initPopover(container, progress, idPassword, el, password_check_rules); initPopover(container, progress, idPassword, el, password_check_rules, i18n_fallback);
// 监听事件 // 监听事件
idPassword.on('focus', function () { idPassword.on('focus', function () {
......
...@@ -3,38 +3,31 @@ ...@@ -3,38 +3,31 @@
# #
from __future__ import absolute_import from __future__ import absolute_import
from django.conf.urls import url from django.urls import path
from rest_framework_bulk.routes import BulkRouter from rest_framework_bulk.routes import BulkRouter
from .. import api from .. import api
app_name = 'users' app_name = 'users'
router = BulkRouter() router = BulkRouter()
router.register(r'v1/users', api.UserViewSet, 'user') router.register(r'users', api.UserViewSet, 'user')
router.register(r'v1/groups', api.UserGroupViewSet, 'user-group') router.register(r'groups', api.UserGroupViewSet, 'user-group')
urlpatterns = [ urlpatterns = [
# url(r'', api.UserListView.as_view()), # path(r'', api.UserListView.as_view()),
url(r'^v1/token/$', api.UserToken.as_view(), name='user-token'), path('token/', api.UserToken.as_view(), name='user-token'),
url(r'^v1/connection-token/$', api.UserConnectionTokenApi.as_view(), name='connection-token'), path('connection-token/', api.UserConnectionTokenApi.as_view(), name='connection-token'),
url(r'^v1/profile/$', api.UserProfile.as_view(), name='user-profile'), path('profile/', api.UserProfile.as_view(), name='user-profile'),
url(r'^v1/auth/$', api.UserAuthApi.as_view(), name='user-auth'), path('auth/', api.UserAuthApi.as_view(), name='user-auth'),
url(r'^v1/otp/auth/$', api.UserOtpAuthApi.as_view(), name='user-otp-auth'), path('otp/auth/', api.UserOtpAuthApi.as_view(), name='user-otp-auth'),
url(r'^v1/users/(?P<pk>[0-9a-zA-Z\-]{36})/password/$', path('users/<uuid:pk>/password/', api.ChangeUserPasswordApi.as_view(), name='change-user-password'),
api.ChangeUserPasswordApi.as_view(), name='change-user-password'), path('users/<uuid:pk>/password/reset/', api.UserResetPasswordApi.as_view(), name='user-reset-password'),
url(r'^v1/users/(?P<pk>[0-9a-zA-Z\-]{36})/password/reset/$', path('users/<uuid:pk>/pubkey/reset/', api.UserResetPKApi.as_view(), name='user-public-key-reset'),
api.UserResetPasswordApi.as_view(), name='user-reset-password'), path('users/<uuid:pk>/pubkey/update/', api.UserUpdatePKApi.as_view(), name='user-public-key-update'),
url(r'^v1/users/(?P<pk>[0-9a-zA-Z\-]{36})/pubkey/reset/$', path('users/<uuid:pk>/unblock/', api.UserUnblockPKApi.as_view(), name='user-unblock'),
api.UserResetPKApi.as_view(), name='user-public-key-reset'), path('users/<uuid:pk>/groups/', api.UserUpdateGroupApi.as_view(), name='user-update-group'),
url(r'^v1/users/(?P<pk>[0-9a-zA-Z\-]{36})/pubkey/update/$', path('groups/<uuid:pk>/users/', api.UserGroupUpdateUserApi.as_view(), name='user-group-update-user'),
api.UserUpdatePKApi.as_view(), name='user-public-key-update'),
url(r'^v1/users/(?P<pk>[0-9a-zA-Z\-]{36})/unblock/$',
api.UserUnblockPKApi.as_view(), name='user-unblock'),
url(r'^v1/users/(?P<pk>[0-9a-zA-Z\-]{36})/groups/$',
api.UserUpdateGroupApi.as_view(), name='user-update-group'),
url(r'^v1/groups/(?P<pk>[0-9a-zA-Z\-]{36})/users/$',
api.UserGroupUpdateUserApi.as_view(), name='user-group-update-user'),
] ]
urlpatterns += router.urls urlpatterns += router.urls
from __future__ import absolute_import from __future__ import absolute_import
from django.conf.urls import url from django.urls import path
from .. import views from .. import views
...@@ -8,45 +8,45 @@ app_name = 'users' ...@@ -8,45 +8,45 @@ app_name = 'users'
urlpatterns = [ urlpatterns = [
# Login view # Login view
url(r'^login/$', views.UserLoginView.as_view(), name='login'), path('login/', views.UserLoginView.as_view(), name='login'),
url(r'^logout/$', views.UserLogoutView.as_view(), name='logout'), path('logout/', views.UserLogoutView.as_view(), name='logout'),
url(r'^login/otp/$', views.UserLoginOtpView.as_view(), name='login-otp'), path('login/otp/', views.UserLoginOtpView.as_view(), name='login-otp'),
url(r'^password/forgot/$', views.UserForgotPasswordView.as_view(), name='forgot-password'), path('password/forgot/', views.UserForgotPasswordView.as_view(), name='forgot-password'),
url(r'^password/forgot/sendmail-success/$', views.UserForgotPasswordSendmailSuccessView.as_view(), name='forgot-password-sendmail-success'), path('password/forgot/sendmail-success/', views.UserForgotPasswordSendmailSuccessView.as_view(), name='forgot-password-sendmail-success'),
url(r'^password/reset/$', views.UserResetPasswordView.as_view(), name='reset-password'), path('password/reset/', views.UserResetPasswordView.as_view(), name='reset-password'),
url(r'^password/reset/success/$', views.UserResetPasswordSuccessView.as_view(), name='reset-password-success'), path('password/reset/success/', views.UserResetPasswordSuccessView.as_view(), name='reset-password-success'),
# Profile # Profile
url(r'^profile/$', views.UserProfileView.as_view(), name='user-profile'), path('profile/', views.UserProfileView.as_view(), name='user-profile'),
url(r'^profile/update/$', views.UserProfileUpdateView.as_view(), name='user-profile-update'), path('profile/update/', views.UserProfileUpdateView.as_view(), name='user-profile-update'),
url(r'^profile/password/update/$', views.UserPasswordUpdateView.as_view(), name='user-password-update'), path('profile/password/update/', views.UserPasswordUpdateView.as_view(), name='user-password-update'),
url(r'^profile/pubkey/update/$', views.UserPublicKeyUpdateView.as_view(), name='user-pubkey-update'), path('profile/pubkey/update/', views.UserPublicKeyUpdateView.as_view(), name='user-pubkey-update'),
url(r'^profile/pubkey/generate/$', views.UserPublicKeyGenerateView.as_view(), name='user-pubkey-generate'), path('profile/pubkey/generate/', views.UserPublicKeyGenerateView.as_view(), name='user-pubkey-generate'),
url(r'^profile/otp/enable/authentication/$', views.UserOtpEnableAuthenticationView.as_view(), name='user-otp-enable-authentication'), path('profile/otp/enable/authentication/', views.UserOtpEnableAuthenticationView.as_view(), name='user-otp-enable-authentication'),
url(r'^profile/otp/enable/install-app/$', views.UserOtpEnableInstallAppView.as_view(), name='user-otp-enable-install-app'), path('profile/otp/enable/install-app/', views.UserOtpEnableInstallAppView.as_view(), name='user-otp-enable-install-app'),
url(r'^profile/otp/enable/bind/$', views.UserOtpEnableBindView.as_view(), name='user-otp-enable-bind'), path('profile/otp/enable/bind/', views.UserOtpEnableBindView.as_view(), name='user-otp-enable-bind'),
url(r'^profile/otp/disable/authentication/$', views.UserOtpDisableAuthenticationView.as_view(), name='user-otp-disable-authentication'), path('profile/otp/disable/authentication/', views.UserOtpDisableAuthenticationView.as_view(), name='user-otp-disable-authentication'),
url(r'^profile/otp/settings-success/$', views.UserOtpSettingsSuccessView.as_view(), name='user-otp-settings-success'), path('profile/otp/settings-success/', views.UserOtpSettingsSuccessView.as_view(), name='user-otp-settings-success'),
# User view # User view
url(r'^user/$', views.UserListView.as_view(), name='user-list'), path('user/', views.UserListView.as_view(), name='user-list'),
url(r'^user/export/$', views.UserExportView.as_view(), name='user-export'), path('user/export/', views.UserExportView.as_view(), name='user-export'),
url(r'^first-login/$', views.UserFirstLoginView.as_view(), name='user-first-login'), path('first-login/', views.UserFirstLoginView.as_view(), name='user-first-login'),
url(r'^user/import/$', views.UserBulkImportView.as_view(), name='user-import'), path('user/import/', views.UserBulkImportView.as_view(), name='user-import'),
url(r'^user/create/$', views.UserCreateView.as_view(), name='user-create'), path('user/create/', views.UserCreateView.as_view(), name='user-create'),
url(r'^user/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.UserUpdateView.as_view(), name='user-update'), path('user/<uuid:pk>/update/', views.UserUpdateView.as_view(), name='user-update'),
url(r'^user/update/$', views.UserBulkUpdateView.as_view(), name='user-bulk-update'), path('user/update/', views.UserBulkUpdateView.as_view(), name='user-bulk-update'),
url(r'^user/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.UserDetailView.as_view(), name='user-detail'), path('user/<uuid:pk>/', views.UserDetailView.as_view(), name='user-detail'),
url(r'^user/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$', views.UserGrantedAssetView.as_view(), name='user-granted-asset'), path('user/<uuid:pk>/assets/', views.UserGrantedAssetView.as_view(), name='user-granted-asset'),
url(r'^user/(?P<pk>[0-9a-zA-Z\-]{36})/login-history/$', views.UserDetailView.as_view(), name='user-login-history'), path('user/<uuid:pk>/login-history/', views.UserDetailView.as_view(), name='user-login-history'),
# User group view # User group view
url(r'^user-group/$', views.UserGroupListView.as_view(), name='user-group-list'), path('user-group/', views.UserGroupListView.as_view(), name='user-group-list'),
url(r'^user-group/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.UserGroupDetailView.as_view(), name='user-group-detail'), path('user-group/<uuid:pk>/', views.UserGroupDetailView.as_view(), name='user-group-detail'),
url(r'^user-group/create/$', views.UserGroupCreateView.as_view(), name='user-group-create'), path('user-group/create/', views.UserGroupCreateView.as_view(), name='user-group-create'),
url(r'^user-group/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.UserGroupUpdateView.as_view(), name='user-group-update'), path('user-group/<uuid:pk>/update/', views.UserGroupUpdateView.as_view(), name='user-group-update'),
url(r'^user-group/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$', views.UserGroupGrantedAssetView.as_view(), name='user-group-granted-asset'), path('user-group/<uuid:pk>/assets/', views.UserGroupGrantedAssetView.as_view(), name='user-group-granted-asset'),
# Login log # Login log
url(r'^login-log/$', views.LoginLogListView.as_view(), name='login-log-list'), path('login-log/', views.LoginLogListView.as_view(), name='login-log-list'),
] ]
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals from __future__ import unicode_literals
from django import forms
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.views.generic.base import TemplateView from django.views.generic.base import TemplateView
...@@ -11,8 +10,8 @@ from django.contrib.messages.views import SuccessMessageMixin ...@@ -11,8 +10,8 @@ from django.contrib.messages.views import SuccessMessageMixin
from common.utils import get_logger from common.utils import get_logger
from common.const import create_success_msg, update_success_msg from common.const import create_success_msg, update_success_msg
from common.permissions import AdminUserRequiredMixin
from ..models import User, UserGroup from ..models import User, UserGroup
from ..utils import AdminUserRequiredMixin
from .. import forms from .. import forms
__all__ = ['UserGroupListView', 'UserGroupCreateView', 'UserGroupDetailView', __all__ = ['UserGroupListView', 'UserGroupCreateView', 'UserGroupDetailView',
......
...@@ -22,8 +22,9 @@ from formtools.wizard.views import SessionWizardView ...@@ -22,8 +22,9 @@ from formtools.wizard.views import SessionWizardView
from django.conf import settings from django.conf import settings
from common.utils import get_object_or_none from common.utils import get_object_or_none
from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin from common.mixins import DatetimeSearchMixin
from common.models import Setting from common.permissions import AdminUserRequiredMixin
from orgs.utils import current_org
from ..models import User, LoginLog from ..models import User, LoginLog
from ..utils import send_reset_password_mail, check_otp_code, get_login_ip, \ from ..utils import send_reset_password_mail, check_otp_code, get_login_ip, \
redirect_user_first_login_or_index, get_user_or_tmp_user, \ redirect_user_first_login_or_index, get_user_or_tmp_user, \
...@@ -309,7 +310,7 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView): ...@@ -309,7 +310,7 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
file_storage = default_storage file_storage = default_storage
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated() and not request.user.is_first_login: if request.user.is_authenticated and not request.user.is_first_login:
return redirect(reverse('index')) return redirect(reverse('index'))
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
...@@ -367,11 +368,17 @@ class LoginLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): ...@@ -367,11 +368,17 @@ class LoginLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
user = keyword = "" user = keyword = ""
date_to = date_from = None date_to = date_from = None
@staticmethod
def get_org_users():
users = current_org.get_org_users().values_list('username', flat=True)
return users
def get_queryset(self): def get_queryset(self):
users = self.get_org_users()
queryset = super().get_queryset().filter(username__in=users)
self.user = self.request.GET.get('user', '') self.user = self.request.GET.get('user', '')
self.keyword = self.request.GET.get("keyword", '') self.keyword = self.request.GET.get("keyword", '')
queryset = super().get_queryset()
queryset = queryset.filter( queryset = queryset.filter(
datetime__gt=self.date_from, datetime__lt=self.date_to datetime__gt=self.date_from, datetime__lt=self.date_to
) )
...@@ -393,9 +400,7 @@ class LoginLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): ...@@ -393,9 +400,7 @@ class LoginLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
'date_to': self.date_to, 'date_to': self.date_to,
'user': self.user, 'user': self.user,
'keyword': self.keyword, 'keyword': self.keyword,
'user_list': set( 'user_list': self.get_org_users(),
LoginLog.objects.all().values_list('username', flat=True)
)
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
\ No newline at end of file
...@@ -34,9 +34,10 @@ from common.const import create_success_msg, update_success_msg ...@@ -34,9 +34,10 @@ from common.const import create_success_msg, update_success_msg
from common.mixins import JSONResponseMixin from common.mixins import JSONResponseMixin
from common.utils import get_logger, get_object_or_none, is_uuid, ssh_key_gen from common.utils import get_logger, get_object_or_none, is_uuid, ssh_key_gen
from common.models import Setting from common.models import Setting
from common.permissions import AdminUserRequiredMixin
from .. import forms from .. import forms
from ..models import User, UserGroup from ..models import User, UserGroup
from ..utils import AdminUserRequiredMixin, generate_otp_uri, check_otp_code, \ from ..utils import generate_otp_uri, check_otp_code, \
get_user_or_tmp_user, get_password_check_rules, check_password_rules, \ get_user_or_tmp_user, get_password_check_rules, check_password_rules, \
is_need_unblock is_need_unblock
from ..signals import post_user_create from ..signals import post_user_create
...@@ -89,6 +90,12 @@ class UserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): ...@@ -89,6 +90,12 @@ class UserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
post_user_create.send(self.__class__, user=user) post_user_create.send(self.__class__, user=user)
return super().form_valid(form) return super().form_valid(form)
def get_form_kwargs(self):
kwargs = super(UserCreateView, self).get_form_kwargs()
data = {'request': self.request}
kwargs.update(data)
return kwargs
class UserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): class UserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
model = User model = User
...@@ -122,6 +129,12 @@ class UserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): ...@@ -122,6 +129,12 @@ class UserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
return self.form_invalid(form) return self.form_invalid(form)
return super().form_valid(form) return super().form_valid(form)
def get_form_kwargs(self):
kwargs = super(UserUpdateView, self).get_form_kwargs()
data = {'request': self.request}
kwargs.update(data)
return kwargs
class UserBulkUpdateView(AdminUserRequiredMixin, TemplateView): class UserBulkUpdateView(AdminUserRequiredMixin, TemplateView):
model = User model = User
...@@ -329,7 +342,6 @@ class UserBulkImportView(AdminUserRequiredMixin, JSONResponseMixin, FormView): ...@@ -329,7 +342,6 @@ class UserBulkImportView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
class UserGrantedAssetView(AdminUserRequiredMixin, DetailView): class UserGrantedAssetView(AdminUserRequiredMixin, DetailView):
model = User model = User
template_name = 'users/user_granted_asset.html' template_name = 'users/user_granted_asset.html'
object = None
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
......
...@@ -14,7 +14,7 @@ coreapi==2.3.3 ...@@ -14,7 +14,7 @@ coreapi==2.3.3
coreschema==0.0.4 coreschema==0.0.4
cryptography==2.1.4 cryptography==2.1.4
decorator==4.1.2 decorator==4.1.2
Django==1.11 Django==2.0.7
django-auth-ldap==1.3.0 django-auth-ldap==1.3.0
django-bootstrap3==9.1.0 django-bootstrap3==9.1.0
django-celery-beat==1.1.1 django-celery-beat==1.1.1
...@@ -24,7 +24,7 @@ django-ranged-response==0.2.0 ...@@ -24,7 +24,7 @@ django-ranged-response==0.2.0
django-redis-cache==1.7.1 django-redis-cache==1.7.1
django-rest-swagger==2.1.2 django-rest-swagger==2.1.2
django-simple-captcha==0.5.6 django-simple-captcha==0.5.6
djangorestframework==3.7.3 djangorestframework==3.8.2
djangorestframework-bulk==0.2.1 djangorestframework-bulk==0.2.1
docutils==0.14 docutils==0.14
ecdsa==0.13 ecdsa==0.13
...@@ -69,3 +69,4 @@ sshpubkeys==2.2.0 ...@@ -69,3 +69,4 @@ sshpubkeys==2.2.0
uritemplate==3.0.0 uritemplate==3.0.0
urllib3==1.22 urllib3==1.22
vine==1.1.4 vine==1.1.4
drf-yasg==1.9.1
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment