* [Update] 添加org

* [Update] 修改url

* [Update] 完成基本框架

* [Update] 修改一些逻辑

* [Update] 修改用户view

* [Update] 修改资产

* [Update] 修改asset api

* [Update] 修改协议小问题

* [Update] stash it

* [Update] 修改约束

* [Update] 修改外键为org_id

* [Update] 删掉Premiddleware

* [Update] 修改Node

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

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

* [Update] 修改permission (#1574)

* Tmp org (#1579)

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

* [Update] fix some bug

* [Update] 修改一些bug

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

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

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

* Tmp org (#1583)

* [Update] 修改一些内容

* [Update] 修改datatable 支持process

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

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

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

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

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

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

* [Update] Org功能修改

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

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

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

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

* Tmp org (#1596)

* [Update] 修改index view

* [Update] 修改nav

* [Update] 修改profile

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

* [Update] 修改get_all_assets

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

* [Bugfix] 修复merge引起的bug

* [Update] Add init

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

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

* [Update] 修改api

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

* [Bugfix] Fix perm name unique

* [Bugfix] 修复校验失败api

* [Update] Merge with org

* [Merge] 修改一下bug

* [Update] 暂时修改一些url

* [Update] 修改url 为django 2.0 path

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

* [Update] 升级url

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

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

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

* [Bugfix] 修复一些bug

* [Bugfix] 修复一些bug

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

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

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

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

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

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

* [Update] 修改token返回

* [Update] Asset返回org name

* [Update] 修改支持xpack

* [Update] 修改url

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

* [Update] 用户修改

* [Bugfix] ...

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

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

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

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

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

* [Bugfix] 修复小bug

* [Update] 优化一些api

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

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

* [Update] 修改版本号
pull/1651/head^2 1.4.0
老广 2018-08-06 23:34:35 -05:00 committed by GitHub
parent dded4e10fb
commit d5451a482a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
121 changed files with 2192 additions and 1201 deletions

1
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,14 +11,15 @@ from django.conf import settings
from common.utils import get_signer, ssh_key_string_to_obj, ssh_key_gen
from common.validators import alphanumeric
from orgs.mixins import OrgModelMixin
from .utils import private_key_validator
signer = get_signer()
class AssetUser(models.Model):
class AssetUser(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
name = models.CharField(max_length=128, verbose_name=_('Name'))
username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric])
_password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,12 +20,12 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
model = Asset
list_serializer_class = BulkListSerializer
fields = '__all__'
validators = [] # If not set to [], partial bulk update will be error
# validators = [] # If not set to [], partial bulk update will be error
def get_field_names(self, declared_fields, info):
fields = super().get_field_names(declared_fields, info)
fields.extend([
'hardware_info', 'is_connective',
'hardware_info', 'is_connective', 'org_name'
])
return fields
@ -43,7 +43,7 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
fields = (
"id", "hostname", "ip", "port", "system_users_granted",
"is_active", "system_users_join", "os", 'domain',
"platform", "comment", "protocol",
"platform", "comment", "protocol", "org_id", "org_name",
)
@staticmethod
@ -61,6 +61,6 @@ class MyAssetGrantedSerializer(AssetGrantedSerializer):
model = Asset
fields = (
"id", "hostname", "system_users_granted",
"is_active", "system_users_join",
"os", "platform", "comment",
"is_active", "system_users_join", "org_name",
"os", "platform", "comment", "org_id", "protocol"
)

View File

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

View File

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

View File

@ -0,0 +1,24 @@
{% extends '_modal.html' %}
{% load i18n %}
{% load static %}
<style>
.modal-body {
background-color: white !important;
}
</style>
{% block modal_id %}user_asset_detail_modal{% endblock %}
{% block modal_title %}{% trans "Asset detail" %}{% endblock %}
{% block modal_body %}
<div class="ibox-content" style="background-color: inherit">
<table class="table">
<tbody id="asset_detail_tbody">
</tbody>
</table>
</div>
{% endblock %}
{% block modal_button %}
<button data-dismiss="modal" class="btn btn-white" type="button">{% trans "Close" %}</button>
{% endblock %}

View File

@ -130,7 +130,7 @@
</div>
</div>
</div>
{% if user.is_superuser %}
{% if user.is_superuser or user.is_org_admin %}
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
<div class="panel panel-primary">
<div class="panel-heading">

View File

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

View File

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

View File

@ -55,11 +55,14 @@
</div>
</div>
</div>
{% include 'assets/_user_asset_detail_modal.html' %}
{% endblock %}
{% block custom_foot_js %}
<script>
var zTree, rMenu, asset_table;
var zTree, asset_table;
var inited = false;
var url;
function initTable() {
@ -68,14 +71,15 @@ function initTable() {
} else {
inited = true;
}
console.log("init table")
url = "{% url 'api-perms:my-assets' %}";
var options = {
ele: $('#user_assets_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
{% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %}
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
var detail_btn = '<a class="asset_detail" asset-id="rowData_id" data-toggle="modal" data-target="#user_asset_detail_modal" tabindex="0">'+ cellData +'</a>'
$(td).html(detail_btn.replace("rowData_id", rowData.id));
}},
{targets: 3, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
@ -103,33 +107,13 @@ function initTable() {
}
function onSelected(event, treeNode) {
console.log("select");
url = '{% url "api-perms:my-node-assets" node_id=DEFAULT_PK %}';
url = url.replace("{{ DEFAULT_PK }}", treeNode.id);
initTable();
url = url.replace("{{ DEFAULT_PK }}", treeNode.node_id);
setCookie('node_selected', treeNode.id);
asset_table.ajax.url(url);
asset_table.ajax.reload();
}
function selectQueryNode() {
var query_node_id = $.getUrlParam("node");
var cookie_node_id = getCookie('node_selected');
var node;
var node_id;
if (query_node_id !== null) {
node_id = query_node_id
} else if (cookie_node_id !== null) {
node_id = cookie_node_id;
}
node = zTree.getNodesByParam("id", node_id, null);
if (node){
zTree.selectNode(node[0]);
}
}
function initTree() {
var setting = {
view: {
@ -149,23 +133,56 @@ function initTree() {
var zNodes = [];
$.get("{% url 'api-perms:my-nodes' %}", function(data, status){
$.each(data, function (index, value) {
value["pId"] = value["parent"];
if (value["key"] === "0") {
value["open"] = true;
value["node_id"] = value["id"];
value["id"] = value["tree_id"];
if (value["tree_id"] !== value["tree_parent"]) {
value["pId"] = value["tree_parent"];
}
value["name"] = value["value"]
value["isParent"] = value["is_node"];
value['name'] = value['value'];
});
zNodes = data;
$.fn.zTree.init($("#assetTree"), setting, zNodes);
zTree = $.fn.zTree.getZTreeObj("assetTree");
rMenu = $("#rMenu");
selectQueryNode();
var root = zTree.getNodes()[0];
zTree.expandNode(root);
});
}
$(document).ready(function () {
initTree();
initTable();
})
.on('click', '.asset_detail', function() {
var data = asset_table.ajax.json();
var asset_id = this.getAttribute("asset-id");
var trs = '';
var desc = {
'hostname': "{% trans 'Hostname' %}",
'ip': "{% trans 'IP' %}",
'port': "{% trans 'Port' %}",
'protocol': "{% trans 'Protocol' %}",
'platform': "{% trans 'Platform' %}",
'os': "{% trans 'OS' %}",
'system_users_join': "{% trans 'System user' %}",
'domain': "{% trans 'Domain' %}",
'is_active': "{% trans 'Is active' %}",
'comment': "{% trans 'Comment' %}"
{#'date_joined': "{% trans 'Date joined' %}",#}
};
$.each(data, function(index, value){
if(value.id === asset_id){
for(var i in desc){
trs += "<tr class='no-borders-tr'>\n" +
"<td>"+ desc[i] + ":</td>"+
"<td><b>"+ (value[i] === null?'':value[i]) + "</b></td>\n" +
"</tr>";
}
}
});
$('#asset_detail_tbody').html(trs)
});
</script>
{% endblock %}

View File

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

View File

@ -1,52 +1,52 @@
# coding:utf-8
from django.conf.urls import url
from django.urls import path
from .. import views
app_name = 'assets'
urlpatterns = [
# Resource asset url
url(r'^$', views.AssetListView.as_view(), name='asset-index'),
url(r'^asset/$', views.AssetListView.as_view(), name='asset-list'),
url(r'^asset/create/$', views.AssetCreateView.as_view(), name='asset-create'),
url(r'^asset/export/$', views.AssetExportView.as_view(), name='asset-export'),
url(r'^asset/import/$', views.BulkImportAssetView.as_view(), name='asset-import'),
url(r'^asset/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.AssetDetailView.as_view(), name='asset-detail'),
url(r'^asset/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.AssetUpdateView.as_view(), name='asset-update'),
url(r'^asset/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.AssetDeleteView.as_view(), name='asset-delete'),
url(r'^asset/update/$', views.AssetBulkUpdateView.as_view(), name='asset-bulk-update'),
path('', views.AssetListView.as_view(), name='asset-index'),
path('asset/', views.AssetListView.as_view(), name='asset-list'),
path('asset/create/', views.AssetCreateView.as_view(), name='asset-create'),
path('asset/export/', views.AssetExportView.as_view(), name='asset-export'),
path('asset/import/', views.BulkImportAssetView.as_view(), name='asset-import'),
path('asset/<uuid:pk>/', views.AssetDetailView.as_view(), name='asset-detail'),
path('asset/<uuid:pk>/update/', views.AssetUpdateView.as_view(), name='asset-update'),
path('asset/<uuid:pk>/delete/', views.AssetDeleteView.as_view(), name='asset-delete'),
path('asset/update/', views.AssetBulkUpdateView.as_view(), name='asset-bulk-update'),
# User asset view
url(r'^user-asset/$', views.UserAssetListView.as_view(), name='user-asset-list'),
path('user-asset/', views.UserAssetListView.as_view(), name='user-asset-list'),
# Resource admin user url
url(r'^admin-user/$', views.AdminUserListView.as_view(), name='admin-user-list'),
url(r'^admin-user/create/$', views.AdminUserCreateView.as_view(), name='admin-user-create'),
url(r'^admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.AdminUserDetailView.as_view(), name='admin-user-detail'),
url(r'^admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.AdminUserUpdateView.as_view(), name='admin-user-update'),
url(r'^admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.AdminUserDeleteView.as_view(), name='admin-user-delete'),
url(r'^admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$', views.AdminUserAssetsView.as_view(), name='admin-user-assets'),
path('admin-user/', views.AdminUserListView.as_view(), name='admin-user-list'),
path('admin-user/create/', views.AdminUserCreateView.as_view(), name='admin-user-create'),
path('admin-user/<uuid:pk>/', views.AdminUserDetailView.as_view(), name='admin-user-detail'),
path('admin-user/<uuid:pk>/update/', views.AdminUserUpdateView.as_view(), name='admin-user-update'),
path('admin-user/<uuid:pk>/delete/', views.AdminUserDeleteView.as_view(), name='admin-user-delete'),
path('admin-user/<uuid:pk>/assets/', views.AdminUserAssetsView.as_view(), name='admin-user-assets'),
# Resource system user url
url(r'^system-user/$', views.SystemUserListView.as_view(), name='system-user-list'),
url(r'^system-user/create/$', views.SystemUserCreateView.as_view(), name='system-user-create'),
url(r'^system-user/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.SystemUserDetailView.as_view(), name='system-user-detail'),
url(r'^system-user/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.SystemUserUpdateView.as_view(), name='system-user-update'),
url(r'^system-user/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.SystemUserDeleteView.as_view(), name='system-user-delete'),
url(r'^system-user/(?P<pk>[0-9a-zA-Z\-]{36})/asset/$', views.SystemUserAssetView.as_view(), name='system-user-asset'),
path('system-user/', views.SystemUserListView.as_view(), name='system-user-list'),
path('system-user/create/', views.SystemUserCreateView.as_view(), name='system-user-create'),
path('system-user/<uuid:pk>/', views.SystemUserDetailView.as_view(), name='system-user-detail'),
path('system-user/<uuid:pk>/update/', views.SystemUserUpdateView.as_view(), name='system-user-update'),
path('system-user/<uuid:pk>/delete/', views.SystemUserDeleteView.as_view(), name='system-user-delete'),
path('system-user/<uuid:pk>/asset/', views.SystemUserAssetView.as_view(), name='system-user-asset'),
url(r'^label/$', views.LabelListView.as_view(), name='label-list'),
url(r'^label/create/$', views.LabelCreateView.as_view(), name='label-create'),
url(r'^label/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.LabelUpdateView.as_view(), name='label-update'),
url(r'^label/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.LabelDeleteView.as_view(), name='label-delete'),
path('label/', views.LabelListView.as_view(), name='label-list'),
path('label/create/', views.LabelCreateView.as_view(), name='label-create'),
path('label/<uuid:pk>/update/', views.LabelUpdateView.as_view(), name='label-update'),
path('label/<uuid:pk>/delete/', views.LabelDeleteView.as_view(), name='label-delete'),
url(r'^domain/$', views.DomainListView.as_view(), name='domain-list'),
url(r'^domain/create/$', views.DomainCreateView.as_view(), name='domain-create'),
url(r'^domain/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.DomainDetailView.as_view(), name='domain-detail'),
url(r'^domain/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.DomainUpdateView.as_view(), name='domain-update'),
url(r'^domain/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.DomainDeleteView.as_view(), name='domain-delete'),
url(r'^domain/(?P<pk>[0-9a-zA-Z\-]{36})/gateway/$', views.DomainGatewayListView.as_view(), name='domain-gateway-list'),
path('domain/', views.DomainListView.as_view(), name='domain-list'),
path('domain/create/', views.DomainCreateView.as_view(), name='domain-create'),
path('domain/<uuid:pk>/', views.DomainDetailView.as_view(), name='domain-detail'),
path('domain/<uuid:pk>/update/', views.DomainUpdateView.as_view(), name='domain-update'),
path('domain/<uuid:pk>/delete/', views.DomainDeleteView.as_view(), name='domain-delete'),
path('domain/<uuid:pk>/gateway/', views.DomainGatewayListView.as_view(), name='domain-gateway-list'),
url(r'^domain/(?P<pk>[0-9a-zA-Z\-]{36})/gateway/create/$', views.DomainGatewayCreateView.as_view(), name='domain-gateway-create'),
url(r'^domain/gateway/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.DomainGatewayUpdateView.as_view(), name='domain-gateway-update'),
path('domain/<uuid:pk>/gateway/create/', views.DomainGatewayCreateView.as_view(), name='domain-gateway-create'),
path('domain/gateway/<uuid:pk>/update/', views.DomainGatewayUpdateView.as_view(), name='domain-gateway-update'),
]

View File

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

View File

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

View File

@ -7,7 +7,7 @@ from django.views.generic.detail import SingleObjectMixin
from django.utils.translation import ugettext_lazy as _
from django.urls import reverse_lazy, reverse
from common.mixins import AdminUserRequiredMixin
from common.permissions import AdminUserRequiredMixin
from common.const import create_success_msg, update_success_msg
from common.utils import get_object_or_none
from ..models import Domain, Gateway

View File

@ -6,7 +6,7 @@ from django.views.generic import TemplateView, CreateView, \
from django.utils.translation import ugettext_lazy as _
from django.urls import reverse_lazy
from common.mixins import AdminUserRequiredMixin
from common.permissions import AdminUserRequiredMixin
from common.const import create_success_msg, update_success_msg
from ..models import Label
from ..forms import LabelForm

View File

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

View File

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

View File

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

View File

@ -9,10 +9,9 @@ from .. import api
app_name = "audits"
router = DefaultRouter()
router.register(r'v1/ftp-log', api.FTPLogViewSet, 'ftp-log')
router.register(r'ftp-log', api.FTPLogViewSet, 'ftp-log')
urlpatterns = [
# url(r'^v1/celery/task/(?P<pk>[0-9a-zA-Z\-]{36})/log/$', api.CeleryTaskLogApi.as_view(), name='celery-task-log'),
]
urlpatterns += router.urls

View File

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

View File

@ -2,7 +2,8 @@ from django.conf import settings
from django.views.generic import ListView
from django.utils.translation import ugettext as _
from common.mixins import AdminUserRequiredMixin, DatetimeSearchMixin
from common.mixins import DatetimeSearchMixin
from common.permissions import AdminUserRequiredMixin
from .models import FTPLog

View File

@ -8,12 +8,12 @@ from django.core.mail import get_connection, send_mail
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from .permissions import IsSuperUser
from .permissions import IsOrgAdmin
from .serializers import MailTestSerializer, LDAPTestSerializer
class MailTestingAPI(APIView):
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
serializer_class = MailTestSerializer
success_message = _("Test mail sent to {}, please check")
@ -37,7 +37,7 @@ class MailTestingAPI(APIView):
class LDAPTestingAPI(APIView):
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
serializer_class = LDAPTestSerializer
success_message = _("Test ldap success")
@ -86,7 +86,18 @@ class LDAPTestingAPI(APIView):
class DjangoSettingsAPI(APIView):
def get(self, request):
return Response('Danger, Close now')
if not settings.DEBUG:
return Response("Not in debug mode")
data = {}
for k, v in settings.__dict__.items():
if k and k.isupper():
try:
json.dumps(v)
data[k] = v
except (json.JSONDecodeError, TypeError):
data[k] = str(v)
return Response(data)

View File

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

View File

@ -4,7 +4,6 @@ from django.db import models
from django.http import JsonResponse
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.mixins import UserPassesTestMixin
class NoDeleteQuerySet(models.query.QuerySet):
@ -119,11 +118,4 @@ class DatetimeSearchMixin:
return super().get(request, *args, **kwargs)
class AdminUserRequiredMixin(UserPassesTestMixin):
def test_func(self):
if not self.request.user.is_authenticated:
return False
elif not self.request.user.is_superuser:
self.raise_exception = True
return False
return True

View File

@ -2,6 +2,11 @@
#
from rest_framework import permissions
from django.contrib.auth.mixins import UserPassesTestMixin
from django.shortcuts import redirect
from django.http.response import HttpResponseForbidden
from orgs.utils import current_org
class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission):
@ -21,28 +26,40 @@ class IsAppUser(IsValidUser):
class IsSuperUser(IsValidUser):
"""Allows access only to superuser"""
def has_permission(self, request, view):
return super(IsSuperUser, self).has_permission(request, view) \
and request.user.is_superuser
and request.user.is_superuser
class IsSuperUserOrAppUser(IsValidUser):
"""Allows access between superuser and app user"""
class IsSuperUserOrAppUser(IsSuperUser):
def has_permission(self, request, view):
return super(IsSuperUserOrAppUser, self).has_permission(request, view) \
and (request.user.is_superuser or request.user.is_app)
class IsSuperUserOrAppUserOrUserReadonly(IsSuperUserOrAppUser):
class IsOrgAdmin(IsValidUser):
"""Allows access only to superuser"""
def has_permission(self, request, view):
return super(IsOrgAdmin, self).has_permission(request, view) \
and current_org.can_admin_by(request.user)
class IsOrgAdminOrAppUser(IsValidUser):
"""Allows access between superuser and app user"""
def has_permission(self, request, view):
return super(IsOrgAdminOrAppUser, self).has_permission(request, view) \
and (current_org.can_admin_by(request.user) or request.user.is_app)
class IsOrgAdminOrAppUserOrUserReadonly(IsOrgAdminOrAppUser):
def has_permission(self, request, view):
if IsValidUser.has_permission(self, request, view) \
and request.method in permissions.SAFE_METHODS:
return True
else:
return IsSuperUserOrAppUser.has_permission(self, request, view)
return IsOrgAdminOrAppUser.has_permission(self, request, view)
class IsCurrentUserOrReadOnly(permissions.BasePermission):
@ -50,3 +67,31 @@ class IsCurrentUserOrReadOnly(permissions.BasePermission):
if request.method in permissions.SAFE_METHODS:
return True
return obj == request.user
class AdminUserRequiredMixin(UserPassesTestMixin):
def test_func(self):
if not self.request.user.is_authenticated:
return False
elif not current_org.can_admin_by(self.request.user):
self.raise_exception = True
return False
return True
def dispatch(self, request, *args, **kwargs):
print("Current org: {}".format(current_org))
if not request.user.is_authenticated:
return super().dispatch(request, *args, **kwargs)
if not current_org:
return redirect('orgs:switch-a-org')
if not current_org.can_admin_by(request.user):
print("{} cannot admin {}".format(request.user, current_org))
if request.user.is_org_admin:
print("Is org admin")
return redirect('orgs:switch-a-org')
return HttpResponseForbidden()
else:
print(current_org.can_admin_by(request.user))
return super().dispatch(request, *args, **kwargs)

View File

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

View File

@ -17,6 +17,7 @@ import threading
from io import StringIO
import uuid
from functools import wraps
import copy
import paramiko
import sshpubkeys
@ -67,10 +68,8 @@ class Signer(metaclass=Singleton):
self.secret_key = secret_key
def sign(self, value):
if isinstance(value, bytes):
value = value.decode("utf-8")
s = JSONWebSignatureSerializer(self.secret_key)
return s.dumps(value)
return s.dumps(value).decode()
def unsign(self, value):
if value is None:
@ -410,3 +409,122 @@ def with_cache(func):
cache[key] = res
return res
return wrapper
class LocalProxy(object):
"""
Copy from werkzeug.local.LocalProxy
"""
__slots__ = ('__local', '__dict__', '__name__', '__wrapped__')
def __init__(self, local, name=None):
object.__setattr__(self, '_LocalProxy__local', local)
object.__setattr__(self, '__name__', name)
if callable(local) and not hasattr(local, '__release_local__'):
# "local" is a callable that is not an instance of Local or
# LocalManager: mark it as a wrapped function.
object.__setattr__(self, '__wrapped__', local)
def _get_current_object(self):
"""Return the current object. This is useful if you want the real
object behind the proxy at a time for performance reasons or because
you want to pass the object into a different context.
"""
if not hasattr(self.__local, '__release_local__'):
return self.__local()
try:
return getattr(self.__local, self.__name__)
except AttributeError:
raise RuntimeError('no object bound to %s' % self.__name__)
@property
def __dict__(self):
try:
return self._get_current_object().__dict__
except RuntimeError:
raise AttributeError('__dict__')
def __repr__(self):
try:
obj = self._get_current_object()
except RuntimeError:
return '<%s unbound>' % self.__class__.__name__
return repr(obj)
def __bool__(self):
try:
return bool(self._get_current_object())
except RuntimeError:
return False
def __dir__(self):
try:
return dir(self._get_current_object())
except RuntimeError:
return []
def __getattr__(self, name):
if name == '__members__':
return dir(self._get_current_object())
return getattr(self._get_current_object(), name)
def __setitem__(self, key, value):
self._get_current_object()[key] = value
def __delitem__(self, key):
del self._get_current_object()[key]
__setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
__delattr__ = lambda x, n: delattr(x._get_current_object(), n)
__str__ = lambda x: str(x._get_current_object())
__lt__ = lambda x, o: x._get_current_object() < o
__le__ = lambda x, o: x._get_current_object() <= o
__eq__ = lambda x, o: x._get_current_object() == o
__ne__ = lambda x, o: x._get_current_object() != o
__gt__ = lambda x, o: x._get_current_object() > o
__ge__ = lambda x, o: x._get_current_object() >= o
__cmp__ = lambda x, o: cmp(x._get_current_object(), o) # noqa
__hash__ = lambda x: hash(x._get_current_object())
__call__ = lambda x, *a, **kw: x._get_current_object()(*a, **kw)
__len__ = lambda x: len(x._get_current_object())
__getitem__ = lambda x, i: x._get_current_object()[i]
__iter__ = lambda x: iter(x._get_current_object())
__contains__ = lambda x, i: i in x._get_current_object()
__add__ = lambda x, o: x._get_current_object() + o
__sub__ = lambda x, o: x._get_current_object() - o
__mul__ = lambda x, o: x._get_current_object() * o
__floordiv__ = lambda x, o: x._get_current_object() // o
__mod__ = lambda x, o: x._get_current_object() % o
__divmod__ = lambda x, o: x._get_current_object().__divmod__(o)
__pow__ = lambda x, o: x._get_current_object() ** o
__lshift__ = lambda x, o: x._get_current_object() << o
__rshift__ = lambda x, o: x._get_current_object() >> o
__and__ = lambda x, o: x._get_current_object() & o
__xor__ = lambda x, o: x._get_current_object() ^ o
__or__ = lambda x, o: x._get_current_object() | o
__div__ = lambda x, o: x._get_current_object().__div__(o)
__truediv__ = lambda x, o: x._get_current_object().__truediv__(o)
__neg__ = lambda x: -(x._get_current_object())
__pos__ = lambda x: +(x._get_current_object())
__abs__ = lambda x: abs(x._get_current_object())
__invert__ = lambda x: ~(x._get_current_object())
__complex__ = lambda x: complex(x._get_current_object())
__int__ = lambda x: int(x._get_current_object())
__float__ = lambda x: float(x._get_current_object())
__oct__ = lambda x: oct(x._get_current_object())
__hex__ = lambda x: hex(x._get_current_object())
__index__ = lambda x: x._get_current_object().__index__()
__coerce__ = lambda x, o: x._get_current_object().__coerce__(x, o)
__enter__ = lambda x: x._get_current_object().__enter__()
__exit__ = lambda x, *a, **kw: x._get_current_object().__exit__(*a, **kw)
__radd__ = lambda x, o: o + x._get_current_object()
__rsub__ = lambda x, o: o - x._get_current_object()
__rmul__ = lambda x, o: o * x._get_current_object()
__rdiv__ = lambda x, o: o / x._get_current_object()
__rtruediv__ = __rdiv__
__rfloordiv__ = lambda x, o: o // x._get_current_object()
__rmod__ = lambda x, o: o % x._get_current_object()
__rdivmod__ = lambda x, o: x._get_current_object().__rdivmod__(o)
__copy__ = lambda x: copy.copy(x._get_current_object())
__deepcopy__ = lambda x, memo: copy.deepcopy(x._get_current_object(), memo)

View File

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

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -56,6 +56,7 @@ ALLOWED_HOSTS = CONFIG.ALLOWED_HOSTS or []
# Application definition
INSTALLED_APPS = [
'orgs.apps.OrgsConfig',
'users.apps.UsersConfig',
'assets.apps.AssetsConfig',
'perms.apps.PermsConfig',
@ -65,6 +66,7 @@ INSTALLED_APPS = [
'audits.apps.AuditsConfig',
'rest_framework',
'rest_framework_swagger',
'drf_yasg',
'django_filters',
'bootstrap3',
'captcha',
@ -76,6 +78,12 @@ INSTALLED_APPS = [
'django.contrib.staticfiles',
]
XPACK_DIR = os.path.join(BASE_DIR, 'xpack')
XPACK_ENABLED = os.path.isdir(XPACK_DIR)
if XPACK_ENABLED:
INSTALLED_APPS.append('xpack.apps.XpackConfig')
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
@ -87,14 +95,35 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'jumpserver.middleware.TimezoneMiddleware',
'jumpserver.middleware.DemoMiddleware',
'orgs.middleware.OrgMiddleware',
]
ROOT_URLCONF = 'jumpserver.urls'
def get_xpack_context_processor():
if XPACK_ENABLED:
return ['xpack.context_processor.xpack_processor']
return []
def get_xpack_templates_dir():
if XPACK_ENABLED:
dirs = []
from xpack.utils import find_enabled_plugins
for i in find_enabled_plugins():
template_dir = os.path.join(BASE_DIR, 'xpack', 'plugins', i, 'templates')
if os.path.isdir(template_dir):
dirs.append(template_dir)
return dirs
else:
return []
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates'), ],
'DIRS': [os.path.join(BASE_DIR, 'templates'), *get_xpack_templates_dir()],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
@ -107,6 +136,8 @@ TEMPLATES = [
'django.template.context_processors.static',
'django.template.context_processors.request',
'django.template.context_processors.media',
'orgs.context_processor.org_processor',
*get_xpack_context_processor(),
],
},
},
@ -227,13 +258,13 @@ LOGGING = {
'level': LOG_LEVEL,
},
'django_auth_ldap': {
'handlers': ['console', 'ansible_logs'],
'handlers': ['console', 'file'],
'level': "INFO",
},
# 'django.db': {
# 'handlers': ['console', 'file'],
# 'level': 'DEBUG'
# }
'django.db': {
'handlers': ['console', 'file'],
'level': 'DEBUG'
}
}
}
@ -288,9 +319,10 @@ REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': (
'users.permissions.IsSuperUser',
'common.permissions.IsOrgAdmin',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'users.authentication.AccessKeyAuthentication',
'users.authentication.AccessTokenAuthentication',
'users.authentication.PrivateTokenAuthentication',
@ -373,7 +405,7 @@ CACHES = {
'password': CONFIG.REDIS_PASSWORD if CONFIG.REDIS_PASSWORD else '',
'host': CONFIG.REDIS_HOST or '127.0.0.1',
'port': CONFIG.REDIS_PORT or 6379,
'db':CONFIG.REDIS_DB_CACHE or 4,
'db': CONFIG.REDIS_DB_CACHE or 4,
}
}
}
@ -423,3 +455,12 @@ TOKEN_EXPIRATION = CONFIG.TOKEN_EXPIRATION or 3600
DISPLAY_PER_PAGE = CONFIG.DISPLAY_PER_PAGE or 25
DEFAULT_EXPIRED_YEARS = 70
USER_GUIDE_URL = ""
SWAGGER_SETTINGS = {
'SECURITY_DEFINITIONS': {
'basic': {
'type': 'basic'
}
},
}

View File

@ -1,45 +1,102 @@
# ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals
import re
import os
from django.conf.urls import url, include
from django.urls import path, include, re_path
from django.conf import settings
from django.conf.urls.static import static
from rest_framework.schemas import get_schema_view
from rest_framework_swagger.renderers import SwaggerUIRenderer, OpenAPIRenderer
from rest_framework.response import Response
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse
from django.utils.encoding import iri_to_uri
from rest_framework import permissions
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
from .views import IndexView, LunaView
schema_view = get_schema_view(title='Users API', renderer_classes=[OpenAPIRenderer, SwaggerUIRenderer])
urlpatterns = [
url(r'^$', IndexView.as_view(), name='index'),
url(r'^luna/', LunaView.as_view(), name='luna-error'),
url(r'^users/', include('users.urls.views_urls', namespace='users')),
url(r'^assets/', include('assets.urls.views_urls', namespace='assets')),
url(r'^perms/', include('perms.urls.views_urls', namespace='perms')),
url(r'^terminal/', include('terminal.urls.views_urls', namespace='terminal')),
url(r'^ops/', include('ops.urls.view_urls', namespace='ops')),
url(r'^audits/', include('audits.urls.view_urls', namespace='audits')),
url(r'^settings/', include('common.urls.view_urls', namespace='settings')),
url(r'^common/', include('common.urls.view_urls', namespace='common')),
schema_view = get_schema_view(
openapi.Info(
title="Jumpserver API Docs",
default_version='v1',
description="Jumpserver Restful api docs",
terms_of_service="https://www.jumpserver.org",
contact=openapi.Contact(email="support@fit2cloud.com"),
license=openapi.License(name="GPLv2 License"),
),
public=True,
permission_classes=(permissions.AllowAny,),
)
api_url_pattern = re.compile(r'^/api/(?P<version>\w+)/(?P<app>\w+)/(?P<extra>.*)$')
# Api url view map
url(r'^api/users/', include('users.urls.api_urls', namespace='api-users')),
url(r'^api/assets/', include('assets.urls.api_urls', namespace='api-assets')),
url(r'^api/perms/', include('perms.urls.api_urls', namespace='api-perms')),
url(r'^api/terminal/', include('terminal.urls.api_urls', namespace='api-terminal')),
url(r'^api/ops/', include('ops.urls.api_urls', namespace='api-ops')),
url(r'^api/audits/', include('audits.urls.api_urls', namespace='api-audits')),
url(r'^api/common/', include('common.urls.api_urls', namespace='api-common')),
# External apps url
url(r'^captcha/', include('captcha.urls')),
class HttpResponseTemporaryRedirect(HttpResponse):
status_code = 307
def __init__(self, redirect_to):
HttpResponse.__init__(self)
self['Location'] = iri_to_uri(redirect_to)
@csrf_exempt
def redirect_format_api(request, *args, **kwargs):
_path, query = request.path, request.GET.urlencode()
matched = api_url_pattern.match(_path)
if matched:
version, app, extra = matched.groups()
_path = '/api/{app}/{version}/{extra}?{query}'.format(**{
"app": app, "version": version, "extra": extra,
"query": query
})
return HttpResponseTemporaryRedirect(_path)
else:
return Response({"msg": "Redirect url failed: {}".format(_path)}, status=404)
v1_api_patterns = [
path('users/v1/', include('users.urls.api_urls', namespace='api-users')),
path('assets/v1/', include('assets.urls.api_urls', namespace='api-assets')),
path('perms/v1/', include('perms.urls.api_urls', namespace='api-perms')),
path('terminal/v1/', include('terminal.urls.api_urls', namespace='api-terminal')),
path('ops/v1/', include('ops.urls.api_urls', namespace='api-ops')),
path('audits/v1/', include('audits.urls.api_urls', namespace='api-audits')),
path('orgs/v1/', include('orgs.urls.api_urls', namespace='api-orgs')),
path('common/v1/', include('common.urls.api_urls', namespace='api-common')),
]
app_view_patterns = [
path('users/', include('users.urls.views_urls', namespace='users')),
path('assets/', include('assets.urls.views_urls', namespace='assets')),
path('perms/', include('perms.urls.views_urls', namespace='perms')),
path('terminal/', include('terminal.urls.views_urls', namespace='terminal')),
path('ops/', include('ops.urls.view_urls', namespace='ops')),
path('audits/', include('audits.urls.view_urls', namespace='audits')),
path('orgs/', include('orgs.urls.views_urls', namespace='orgs')),
]
if settings.XPACK_ENABLED:
app_view_patterns.append(path('xpack/', include('xpack.urls', namespace='xpack')))
urlpatterns = [
path('', IndexView.as_view(), name='index'),
path('luna/', LunaView.as_view(), name='luna-error'),
path('settings/', include('common.urls.view_urls', namespace='settings')),
path('common/', include('common.urls.view_urls', namespace='common')),
path('api/v1/', redirect_format_api),
path('api/', include(v1_api_patterns)),
# External apps url
path('captcha/', include('captcha.urls')),
]
urlpatterns += app_view_patterns
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \
+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
if settings.DEBUG:
urlpatterns += [
url(r'^docs/', schema_view, name="docs"),
re_path('swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=None), name='schema-json'),
path('docs/', schema_view.with_ui('swagger', cache_timeout=None), name="docs"),
path('redoc/', schema_view.with_ui('redoc', cache_timeout=None), name='redoc'),
]

View File

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

View File

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

View File

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

View File

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

View File

@ -263,7 +263,8 @@ class AdHoc(models.Model):
}
:return:
"""
self._become = signer.sign(json.dumps(item)).decode('utf-8')
# self._become = signer.sign(json.dumps(item)).decode('utf-8')
self._become = signer.sign(json.dumps(item))
@property
def options(self):

View File

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

View File

@ -1,8 +1,7 @@
# ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals
from django.urls import path
from django.conf.urls import url
from .. import views
__all__ = ["urlpatterns"]
@ -10,13 +9,13 @@ __all__ = ["urlpatterns"]
app_name = "ops"
urlpatterns = [
# TResource Task url
url(r'^task/$', views.TaskListView.as_view(), name='task-list'),
url(r'^task/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.TaskDetailView.as_view(), name='task-detail'),
url(r'^task/(?P<pk>[0-9a-zA-Z\-]{36})/adhoc/$', views.TaskAdhocView.as_view(), name='task-adhoc'),
url(r'^task/(?P<pk>[0-9a-zA-Z\-]{36})/history/$', views.TaskHistoryView.as_view(), name='task-history'),
url(r'^adhoc/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.AdHocDetailView.as_view(), name='adhoc-detail'),
url(r'^adhoc/(?P<pk>[0-9a-zA-Z\-]{36})/history/$', views.AdHocHistoryView.as_view(), name='adhoc-history'),
url(r'^adhoc/history/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.AdHocHistoryDetailView.as_view(), name='adhoc-history-detail'),
url(r'^celery/task/(?P<pk>[0-9a-zA-Z\-]{36})/log/$', views.CeleryTaskLogView.as_view(), name='celery-task-log'),
# Resource Task url
path('task/', views.TaskListView.as_view(), name='task-list'),
path('task/<uuid:pk>/', views.TaskDetailView.as_view(), name='task-detail'),
path('task/<uuid:pk>/adhoc/', views.TaskAdhocView.as_view(), name='task-adhoc'),
path('task/<uuid:pk>/history/', views.TaskHistoryView.as_view(), name='task-history'),
path('adhoc/<uuid:pk>/', views.AdHocDetailView.as_view(), name='adhoc-detail'),
path('adhoc/<uuid:pk>/history/', views.AdHocHistoryView.as_view(), name='adhoc-history'),
path('adhoc/history/<uuid:pk>/', views.AdHocHistoryDetailView.as_view(), name='adhoc-history-detail'),
path('celery/task/<uuid:pk>/log/', views.CeleryTaskLogView.as_view(), name='celery-task-log'),
]

View File

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

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

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

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

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

@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
#
from rest_framework import viewsets
from common.permissions import IsSuperUserOrAppUser
from .models import Organization
from .serializers import OrgSerializer
class OrgViewSet(viewsets.ModelViewSet):
queryset = Organization.objects.all()
serializer_class = OrgSerializer
permission_classes = (IsSuperUserOrAppUser,)

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

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

View File

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
#
from .utils import current_org, get_current_org
from .models import Organization
def org_processor(request):
context = {
'ADMIN_ORGS': Organization.get_user_admin_orgs(request.user),
'CURRENT_ORG': get_current_org(),
'HAS_ORG_PERM': current_org.can_admin_by(request.user),
}
return context

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

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
#
from .utils import get_org_from_request, set_current_org
class OrgMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
org = get_org_from_request(request)
request.current_org = org
set_current_org(org)
response = self.get_response(request)
return response

View File

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

@ -0,0 +1,104 @@
# -*- coding: utf-8 -*-
#
from threading import local
from django.db import models
from django.db.models import Q
from django.shortcuts import redirect
import warnings
from django.forms import ModelForm
from django.http.response import HttpResponseForbidden
from common.utils import get_logger
from .utils import current_org, set_current_org, set_to_root_org
from .models import Organization
logger = get_logger(__file__)
tl = local()
__all__ = [
'OrgManager', 'OrgViewGenericMixin', 'OrgModelMixin', 'OrgModelForm',
'RootOrgViewMixin',
]
class OrgManager(models.Manager):
def get_queryset(self):
queryset = super(OrgManager, self).get_queryset()
kwargs = {}
if not hasattr(tl, 'times'):
tl.times = 0
# logger.debug("[{}]>>>>>>>>>> Get query set".format(tl.times))
if not current_org:
kwargs['id'] = None
elif current_org.is_real():
kwargs['org_id'] = current_org.id
elif current_org.is_default():
queryset = queryset.filter(Q(org_id="") | Q(org_id__isnull=True))
queryset = queryset.filter(**kwargs)
tl.times += 1
return queryset
def all(self):
if not current_org:
msg = 'You can `objects.set_current_org(org).all()` then run it'
warnings.warn(msg)
return self
else:
return super(OrgManager, self).all()
def set_current_org(self, org):
if isinstance(org, str):
org = Organization.objects.get(name=org)
set_current_org(org)
return self
class OrgModelMixin(models.Model):
org_id = models.CharField(max_length=36, null=True, blank=True)
objects = OrgManager()
def save(self, *args, **kwargs):
if current_org and current_org.is_real():
self.org_id = current_org.id
return super().save(*args, **kwargs)
class Meta:
abstract = True
class OrgViewGenericMixin:
def dispatch(self, request, *args, **kwargs):
print("Current org: {}".format(current_org))
if not current_org:
return redirect('orgs:switch-a-org')
if not current_org.can_admin_by(request.user):
print("{} cannot admin {}".format(request.user, current_org))
if request.user.is_org_admin:
print("Is org admin")
return redirect('orgs:switch-a-org')
return HttpResponseForbidden()
else:
print(current_org.can_admin_by(request.user))
return super().dispatch(request, *args, **kwargs)
class RootOrgViewMixin:
def dispatch(self, request, *args, **kwargs):
set_to_root_org()
return super().dispatch(request, *args, **kwargs)
class OrgModelForm(ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if 'initial' not in kwargs:
return
for name, field in self.fields.items():
if not hasattr(field, 'queryset'):
continue
model = field.queryset.model
field.queryset = model.objects.all()

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

@ -0,0 +1,103 @@
import uuid
from django.db import models
from django.core.cache import cache
from django.utils.translation import ugettext_lazy as _
class Organization(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, unique=True, verbose_name=_("Name"))
users = models.ManyToManyField('users.User', related_name='orgs', blank=True)
admins = models.ManyToManyField('users.User', related_name='admin_orgs', blank=True)
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
CACHE_PREFIX = 'JMS_ORG_{}'
ROOT_ID = 'ROOT'
DEFAULT_ID = 'DEFAULT'
def __str__(self):
return self.name
def set_to_cache(self):
key = self.CACHE_PREFIX.format(self.id)
cache.set(key, self, 3600)
def expire_cache(self):
key = self.CACHE_PREFIX.format(self.id)
cache.set(key, self, 0)
@classmethod
def get_instance_from_cache(cls, oid):
key = cls.CACHE_PREFIX.format(oid)
return cache.get(key, None)
@classmethod
def get_instance(cls, oid, default=True):
cached = cls.get_instance_from_cache(oid)
if cached:
return cached
if oid == cls.DEFAULT_ID:
return cls.default()
elif oid == cls.ROOT_ID:
return cls.root()
try:
org = cls.objects.get(id=oid)
org.set_to_cache()
except cls.DoesNotExist:
org = cls.default() if default else None
return org
def get_org_users(self):
from users.models import User
if self.is_default():
users = User.objects.filter(orgs__isnull=True)
else:
users = self.users.all()
users = users.exclude(role=User.ROLE_APP)
return users
def get_org_admins(self):
if self.is_real():
return self.admins.all()
return []
def can_admin_by(self, user):
if user.is_superuser:
return True
if user in list(self.get_org_admins()):
return True
return False
def is_real(self):
return len(str(self.id)) == 36
@classmethod
def get_user_admin_orgs(cls, user):
admin_orgs = []
if user.is_anonymous:
return admin_orgs
elif user.is_superuser:
admin_orgs = list(cls.objects.all())
admin_orgs.append(cls.default())
elif user.is_org_admin:
admin_orgs = user.admin_orgs.all()
return admin_orgs
@classmethod
def default(cls):
return cls(id=cls.DEFAULT_ID, name="Default")
@classmethod
def root(cls):
return cls(id=cls.ROOT_ID, name='Root')
def is_default(self):
if self.id is self.DEFAULT_ID:
return True
else:
return False

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

@ -0,0 +1,10 @@
from rest_framework.serializers import ModelSerializer
from .models import Organization
class OrgSerializer(ModelSerializer):
class Meta:
model = Organization
fields = '__all__'
read_only_fields = ['id', 'created_by', 'date_created']

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

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

View File

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

View File

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
#
from rest_framework.routers import DefaultRouter
from .. import api
app_name = 'orgs'
router = DefaultRouter()
router.register(r'orgs', api.OrgViewSet, 'org')
urlpatterns = [
]
urlpatterns += router.urls

View File

@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
#
from django.urls import path
from .. import views
app_name = 'orgs'
urlpatterns = [
path('<str:pk>/switch/', views.SwitchOrgView.as_view(), name='org-switch'),
path('switch-a-org/', views.SwitchToAOrgView.as_view(), name='switch-a-org')
]

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

@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
#
from functools import partial
from common.utils import LocalProxy
from .models import Organization
try:
from threading import local
except ImportError:
from django.utils._threading_local import local
_thread_locals = local()
def get_org_from_request(request):
oid = request.session.get("oid")
if not oid:
oid = request.META.get("HTTP_X_JMS_ORG")
org = Organization.get_instance(oid)
return org
def set_current_org(org):
setattr(_thread_locals, 'current_org', org)
def set_to_default_org():
set_current_org(Organization.default())
def set_to_root_org():
set_current_org(Organization.root())
def _find(attr):
return getattr(_thread_locals, attr, None)
def get_current_org():
return _find('current_org')
current_org = LocalProxy(partial(_find, 'current_org'))
current_user = LocalProxy(partial(_find, 'current_user'))
current_request = LocalProxy(partial(_find, 'current_request'))

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

@ -0,0 +1,30 @@
from django.shortcuts import redirect, reverse
from django.http import HttpResponseForbidden
from django.views.generic import DetailView, View
from .models import Organization
class SwitchOrgView(DetailView):
model = Organization
object = None
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
self.object = Organization.get_instance(pk)
request.session['oid'] = self.object.id.__str__()
return redirect('index')
class SwitchToAOrgView(View):
def get(self, request, *args, **kwargs):
admin_orgs = Organization.get_user_admin_orgs(request.user)
if not admin_orgs:
return HttpResponseForbidden()
default_org = Organization.default()
if default_org in admin_orgs:
redirect_org = default_org
else:
redirect_org = admin_orgs[0]
return redirect(reverse('orgs:org-switch', kwargs={'pk': redirect_org.id}))

View File

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

View File

@ -4,11 +4,13 @@ from __future__ import absolute_import, unicode_literals
from django import forms
from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelForm
from orgs.utils import current_org
from .hands import User
from .models import AssetPermission
class AssetPermissionForm(forms.ModelForm):
class AssetPermissionForm(OrgModelForm):
users = forms.ModelMultipleChoiceField(
queryset=User.objects.exclude(role=User.ROLE_APP),
label=_("User"),
@ -21,10 +23,18 @@ class AssetPermissionForm(forms.ModelForm):
required=False,
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if 'initial' not in kwargs:
return
users_field = self.fields.get('users')
if hasattr(users_field, 'queryset'):
users_field.queryset = current_org.get_org_users()
class Meta:
model = AssetPermission
exclude = (
'id', 'date_created', 'created_by'
'id', 'date_created', 'created_by', 'org_id'
)
widgets = {
'users': forms.SelectMultiple(

View File

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

View File

@ -6,6 +6,8 @@ from django.utils import timezone
from common.utils import date_expired_default, set_or_append_attr_bulk
from orgs.mixins import OrgModelMixin, OrgManager
class AssetPermissionQuerySet(models.QuerySet):
def active(self):
@ -16,17 +18,14 @@ class AssetPermissionQuerySet(models.QuerySet):
.filter(date_expired__gt=timezone.now())
class AssetPermissionManager(models.Manager):
def get_queryset(self):
return AssetPermissionQuerySet(self.model, using=self._db)
class AssetPermissionManager(OrgManager):
def valid(self):
return self.get_queryset().valid()
class AssetPermission(models.Model):
class AssetPermission(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
name = models.CharField(max_length=128, verbose_name=_('Name'))
users = models.ManyToManyField('users.User', related_name='asset_permissions', blank=True, verbose_name=_("User"))
user_groups = models.ManyToManyField('users.UserGroup', related_name='asset_permissions', blank=True, verbose_name=_("User group"))
assets = models.ManyToManyField('assets.Asset', related_name='granted_by_permissions', blank=True, verbose_name=_("Asset"))
@ -39,7 +38,10 @@ class AssetPermission(models.Model):
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created'))
comment = models.TextField(verbose_name=_('Comment'), blank=True)
objects = AssetPermissionManager()
objects = AssetPermissionManager.from_queryset(AssetPermissionQuerySet)()
class Meta:
unique_together = [('org_id', 'name')]
def __str__(self):
return self.name
@ -71,7 +73,7 @@ class AssetPermission(models.Model):
return assets
class NodePermission(models.Model):
class NodePermission(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
node = models.ForeignKey('assets.Node', on_delete=models.CASCADE, verbose_name=_("Node"))
user_group = models.ForeignKey('users.UserGroup', on_delete=models.CASCADE, verbose_name=_("User group"))

View File

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

View File

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

View File

@ -1,16 +1,17 @@
# coding:utf-8
from django.conf.urls import url
from django.urls import path
from .. import views
app_name = 'perms'
urlpatterns = [
url(r'^asset-permission/$', views.AssetPermissionListView.as_view(), name='asset-permission-list'),
url(r'^asset-permission/create/$', views.AssetPermissionCreateView.as_view(), name='asset-permission-create'),
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.AssetPermissionUpdateView.as_view(), name='asset-permission-update'),
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.AssetPermissionDetailView.as_view(),name='asset-permission-detail'),
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.AssetPermissionDeleteView.as_view(), name='asset-permission-delete'),
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/user/$', views.AssetPermissionUserView.as_view(), name='asset-permission-user-list'),
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/asset/$', views.AssetPermissionAssetView.as_view(), name='asset-permission-asset-list'),
path('asset-permission/', views.AssetPermissionListView.as_view(), name='asset-permission-list'),
path('asset-permission/create/', views.AssetPermissionCreateView.as_view(), name='asset-permission-create'),
path('asset-permission/<uuid:pk>/update/', views.AssetPermissionUpdateView.as_view(), name='asset-permission-update'),
path('asset-permission/<uuid:pk>/', views.AssetPermissionDetailView.as_view(),name='asset-permission-detail'),
path('asset-permission/<uuid:pk>/delete/', views.AssetPermissionDeleteView.as_view(), name='asset-permission-delete'),
path('asset-permission/<uuid:pk>/user/', views.AssetPermissionUserView.as_view(), name='asset-permission-user-list'),
path('asset-permission/<uuid:pk>/asset/', views.AssetPermissionAssetView.as_view(), name='asset-permission-asset-list'),
]

View File

@ -13,7 +13,7 @@ logger = get_logger(__file__)
class Tree:
def __init__(self):
self.__all_nodes = list(Node.objects.all().prefetch_related('assets'))
self.__all_nodes = Node.objects.all().prefetch_related('assets')
self.__node_asset_map = defaultdict(set)
self.nodes = defaultdict(dict)
self.root = Node.root()
@ -21,7 +21,7 @@ class Tree:
def init_node_asset_map(self):
for node in self.__all_nodes:
assets = node.get_assets().values_list('id', flat=True)
assets = [a.id for a in node.assets.all()]
for asset in assets:
self.__node_asset_map[str(asset)].add(node)

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -179,7 +179,7 @@ function APIUpdateAttr(props) {
toastr.error(fail_message);
}
if (typeof props.error === 'function') {
return props.error(jqXHR.responseText);
return props.error(jqXHR.responseText, jqXHR.status);
}
});
// return true;
@ -224,6 +224,47 @@ function objectDelete(obj, name, url, redirectTo) {
});
}
function orgDelete(obj, name, url, redirectTo){
function doDelete() {
var body = {};
var success = function() {
if (!redirectTo) {
$(obj).parent().parent().remove();
} else {
window.location.href=redirectTo;
}
};
var fail = function(responseText, status) {
if (status === 400){
swal("错误", "[ " + name + " ] 组织中包含未删除信息,请删除后重试", "error");
}
else if (status === 405){
swal("错误", "请勿在组织 [ "+ name + " ] 下执行此操作,切换到其他组织后重试", "error");
}
};
APIUpdateAttr({
url: url,
body: JSON.stringify(body),
method: 'DELETE',
success_message: "删除成功",
success: success,
error: fail
});
}
swal({
title: "请确保组织内的以下信息已删除",
text: "用户列表、用户组、资产列表、网域列表、管理用户、系统用户、标签管理、资产授权规则",
type: "warning",
showCancelButton: true,
cancelButtonText: '取消',
confirmButtonColor: "#ed5565",
confirmButtonText: '确认',
closeOnConfirm: true
}, function () {
doDelete();
});
}
$.fn.serializeObject = function()
{
var o = {};
@ -250,6 +291,28 @@ function makeLabel(data) {
var jumpserver = {};
jumpserver.checked = false;
jumpserver.selected = {};
jumpserver.language = {
processing: "加载中",
search: "搜索",
select: {
rows: {
_: "选中 %d 项",
0: ""
}
},
lengthMenu: "每页 _MENU_",
info: "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项",
infoFiltered: "",
infoEmpty: "",
zeroRecords: "没有匹配项",
emptyTable: "没有记录",
paginate: {
first: "«",
previous: "",
next: "",
last: "»"
}
};
jumpserver.initDataTable = function (options) {
// options = {
// ele *: $('#dataTable_id'),
@ -293,21 +356,7 @@ jumpserver.initDataTable = function (options) {
},
columns: options.columns || [],
select: options.select || select,
language: {
search: "搜索",
lengthMenu: "每页 _MENU_",
info: "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项",
infoFiltered: "",
infoEmpty: "",
zeroRecords: "没有匹配项",
emptyTable: "没有记录",
paginate: {
first: "«",
previous: "",
next: "",
last: "»"
}
},
language: jumpserver.language,
lengthMenu: [[10, 15, 25, 50, -1], [10, 15, 25, 50, "All"]]
});
table.on('select', function(e, dt, type, indexes) {
@ -343,6 +392,16 @@ jumpserver.initDataTable = function (options) {
return table;
};
jumpserver.initStaticTable = function (selector) {
$(selector).DataTable({
"searching": false,
"bInfo": false,
"paging": false,
"order": [],
"language": jumpserver.language
});
};
jumpserver.initServerSideDataTable = function (options) {
// options = {
// ele *: $('#dataTable_id'),
@ -375,9 +434,8 @@ jumpserver.initServerSideDataTable = function (options) {
};
var table = ele.DataTable({
pageLength: options.pageLength || 15,
dom: options.dom || '<"#uc.pull-left">flt<"row m-t"<"col-md-8"<"#op.col-md-6"><"col-md-6 text-center"i>><"col-md-4"p>>',
dom: options.dom || '<"#uc.pull-left">fltr<"row m-t"<"col-md-8"<"#op.col-md-6"><"col-md-6 text-center"i>><"col-md-4"p>>',
order: options.order || [],
// select: options.select || 'multi',
buttons: [],
columnDefs: columnDefs,
serverSide: true,
@ -432,21 +490,7 @@ jumpserver.initServerSideDataTable = function (options) {
},
columns: options.columns || [],
select: options.select || select,
language: {
search: "搜索",
lengthMenu: "每页 _MENU_",
info: "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项",
infoFiltered: "",
infoEmpty: "",
zeroRecords: "没有匹配项",
emptyTable: "没有记录",
paginate: {
first: "«",
previous: "",
next: "",
last: "»"
}
},
language: jumpserver.language,
lengthMenu: [[10, 15, 25, 50], [10, 15, 25, 50]]
});
table.selected = [];
@ -477,8 +521,7 @@ jumpserver.initServerSideDataTable = function (options) {
}
})
}
}).
on('draw', function(){
}).on('draw', function(){
$('#op').html(options.op_html || '');
$('#uc').html(options.uc_html || '');
var table_data = [];
@ -683,7 +726,7 @@ function popoverPasswordRules(password_check_rules, $el) {
}
// 初始化弹窗popover
function initPopover($container, $progress, $idPassword, $el, password_check_rules){
function initPopover($container, $progress, $idPassword, $el, password_check_rules, i18n_fallback){
options = {};
// User Interface
options.ui = {
@ -695,6 +738,14 @@ function initPopover($container, $progress, $idPassword, $el, password_check_rul
showProgressbar: true,
showVerdictsInsideProgressBar: true
};
options.i18n = {
fallback: i18n_fallback,
t: function (key) {
var result = '';
result = options.i18n.fallback[key];
return result === key ? '' : result;
}
};
$idPassword.pwstrength(options);
popoverPasswordRules(password_check_rules, $el);
}

View File

@ -1,6 +1,6 @@
<div class="footer fixed">
<div class="pull-right">
Version <strong>1.3.3-{% include '_build.html' %}</strong> GPLv2.
Version <strong>1.4.0-{% include '_build.html' %}</strong> GPLv2.
<!--<img style="display: none" src="http://www.jumpserver.org/img/evaluate_avatar1.jpg">-->
</div>
<div>

View File

@ -35,7 +35,7 @@
</a>
<ul class="dropdown-menu animated fadeInRight m-t-xs profile-dropdown">
<li><a href="{% url 'users:user-profile' %}"><i class="fa fa-cogs"> </i><span> {% trans 'Profile' %}</span></a></li>
{% if request.user.is_superuser %}
{% if request.user.is_org_admin %}
{% if request.COOKIES.IN_ADMIN_PAGE == 'No' %}
<li><a id="switch_admin"><i class="fa fa-exchange"></i><span> {% trans 'Admin page' %}</span></a></li>
{% else %}

View File

@ -2,7 +2,7 @@
<div class="sidebar-collapse">
<ul class="nav" id="side-menu">
{% include '_user_profile.html' %}
{% if request.user.is_superuser and request.COOKIES.IN_ADMIN_PAGE != "No" %}
{% if request.user.is_org_admin and request.COOKIES.IN_ADMIN_PAGE != "No" %}
{% include '_nav.html' %}
{% else %}
{% include '_nav_user.html' %}

View File

@ -1,8 +1,8 @@
{% load i18n %}
<li id="index">
<a href="{% url 'index' %}">
<i class="fa fa-dashboard" style="width: 14px"></i> <span class="nav-label">{% trans 'Dashboard' %}</span><span
class="label label-info pull-right"></span>
<i class="fa fa-dashboard" style="width: 14px"></i> <span class="nav-label">{% trans 'Dashboard' %}</span>
<span class="label label-info pull-right"></span>
</a>
</li>
<li id="users">
@ -48,9 +48,12 @@
<span class="nav-label">{% trans 'Web terminal' %}</span>
</a>
</li>
{% if request.user.is_superuser %}
<li id="terminal"><a href="{% url 'terminal:terminal-list' %}">{% trans 'Terminal' %}</a></li>
{% endif %}
</ul>
</li>
{% if request.user.is_superuser %}
<li id="ops">
<a>
<i class="fa fa-coffee" style="width: 14px"></i> <span class="nav-label">{% trans 'Job Center' %}</span><span class="fa arrow"></span>
@ -59,6 +62,7 @@
<li id="task"><a href="{% url 'ops:task-list' %}">{% trans 'Task list' %}</a></li>
</ul>
</li>
{% endif %}
<li id="audits">
<a>
<i class="fa fa-history" style="width: 14px"></i> <span class="nav-label">{% trans 'Audits' %}</span><span class="fa arrow"></span>
@ -76,8 +80,29 @@
{# <li id="download"><a href="">{% trans 'File download' %}</a></li>#}
{# </ul>#}
{#</li>#}
{% if XPACK_ENABLED %}
<li id="xpack">
<a>
<i class="fa fa-sitemap" style="width: 14px"></i> <span class="nav-label">{% trans 'XPack' %}</span><span class="fa arrow"></span>
</a>
<ul class="nav nav-second-level">
{% for plugin in XPACK_PLUGINS %}
<li id="{{ plugin.name }}"><a href="{{ plugin.endpoint }}">{% trans plugin.verbose_name %}</a></li>
{% endfor %}
</ul>
</li>
{% endif %}
{% if request.user.is_superuser %}
<li id="settings">
<a href="{% url 'settings:basic-setting' %}">
<i class="fa fa-gears"></i> <span class="nav-label">{% trans 'Settings' %}</span><span class="label label-info pull-right"></span>
</a>
</li>
</li>
{% endif %}
<script>
$(document).ready(function () {
var current_org = '{{ CURRENT_ORG.name }}';
console.log(current_org);
})
</script>

View File

@ -1,15 +1,41 @@
{% load static %}
{% load i18n %}
<li class="nav-header">
<div class="dropdown profile-element">
<div href="http://www.jumpserver.org" target="_blank">
<img alt="logo" height="55" width="185" src="/static/img/logo-text.png" style="margin-left: 20px"/>
<div class="profile-element" style="height: 65px">
<div href="http://www.jumpserver.org" target="_blank" style="width: 100%; background-image: url({% static 'img/header-profile.png' %})">
<img alt="logo" height="55" width="185" src="/static/img/logo-text.png"/>
</div>
</div>
<div class="clearfix"></div>
<div class="logo-element">
<img alt="image" height="40" src="/static/img/logo.png"/>
</div>
{% if ADMIN_ORGS and request.COOKIES.IN_ADMIN_PAGE != 'No' %}
{% if ADMIN_ORGS|length > 1 or not CURRENT_ORG.is_default %}
<div style="height: 55px;">
<a class="dropdown-toggle" data-toggle="dropdown" aria-expanded="false" style="display: block; background-color: transparent; color: #8095a8; padding: 14px 20px 14px 25px">
<i class="fa fa-bookmark" style="width: 14px; "></i>
<span class="nav-label" style="padding-left: 7px">
{{ CURRENT_ORG.name }}
</span>
<span class="fa fa-sort-desc pull-right"></span>
</a>
<ul class="dropdown-menu" style="width: 219px;">
{% for org in ADMIN_ORGS %}
<li>
<a class="org-dropdown"
href="{% url 'orgs:org-switch' pk=org.id %}"
data-id="{{ org.id }}">
{{ org.name }}
{% if org.id == CURRENT_ORG.id %}
<span class="fa fa-circle pull-right" style="padding-top: 5px; color: #1ab394"></span>
{% endif %}
</a>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% endif %}
</li>
<script>
$(document).ready(function () {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
#
from django.conf.urls import url
from django.urls import path
from .. import views
@ -10,20 +10,20 @@ app_name = 'terminal'
urlpatterns = [
# Terminal view
url(r'^terminal/$', views.TerminalListView.as_view(), name='terminal-list'),
url(r'^terminal/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.TerminalDetailView.as_view(), name='terminal-detail'),
url(r'^terminal/(?P<pk>[0-9a-zA-Z\-]{36})/connect/$', views.TerminalConnectView.as_view(), name='terminal-connect'),
url(r'^terminal/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.TerminalUpdateView.as_view(), name='terminal-update'),
url(r'^(?P<pk>[0-9a-zA-Z\-]{36})/accept/$', views.TerminalAcceptView.as_view(), name='terminal-accept'),
url(r'^web-terminal/$', views.WebTerminalView.as_view(), name='web-terminal'),
path('terminal/', views.TerminalListView.as_view(), name='terminal-list'),
path('terminal/<uuid:pk>/', views.TerminalDetailView.as_view(), name='terminal-detail'),
path('terminal/<uuid:pk>/connect/', views.TerminalConnectView.as_view(), name='terminal-connect'),
path('terminal/<uuid:pk>/update/', views.TerminalUpdateView.as_view(), name='terminal-update'),
path('<uuid:pk>/accept/', views.TerminalAcceptView.as_view(), name='terminal-accept'),
path('web-terminal/', views.WebTerminalView.as_view(), name='web-terminal'),
# Session view
url(r'^session-online/$', views.SessionOnlineListView.as_view(), name='session-online-list'),
url(r'^session-offline/$', views.SessionOfflineListView.as_view(), name='session-offline-list'),
url(r'^session/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.SessionDetailView.as_view(), name='session-detail'),
path('session-online/', views.SessionOnlineListView.as_view(), name='session-online-list'),
path('session-offline/', views.SessionOfflineListView.as_view(), name='session-offline-list'),
path('session/<uuid:pk>/', views.SessionDetailView.as_view(), name='session-detail'),
# Command view
url(r'^command/$', views.CommandListView.as_view(), name='command-list'),
url(r'^command/export/$', views.CommandExportView.as_view(), name='command-export')
path('command/', views.CommandListView.as_view(), name='command-list'),
path('command/export/', views.CommandExportView.as_view(), name='command-export')
]

View File

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

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