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

Dev (#1646)

* [Update] 添加org

* [Update] 修改url

* [Update] 完成基本框架

* [Update] 修改一些逻辑

* [Update] 修改用户view

* [Update] 修改资产

* [Update] 修改asset api

* [Update] 修改协议小问题

* [Update] stash it

* [Update] 修改约束

* [Update] 修改外键为org_id

* [Update] 删掉Premiddleware

* [Update] 修改Node

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

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

* [Update] 修改permission (#1574)

* Tmp org (#1579)

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

* [Update] fix some bug

* [Update] 修改一些bug

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

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

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

* Tmp org (#1583)

* [Update] 修改一些内容

* [Update] 修改datatable 支持process

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

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

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

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

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

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

* [Update] Org功能修改

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

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

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

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

* Tmp org (#1596)

* [Update] 修改index view

* [Update] 修改nav

* [Update] 修改profile

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

* [Update] 修改get_all_assets

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

* [Bugfix] 修复merge引起的bug

* [Update] Add init

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

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

* [Update] 修改api

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

* [Bugfix] Fix perm name unique

* [Bugfix] 修复校验失败api

* [Update] Merge with org

* [Merge] 修改一下bug

* [Update] 暂时修改一些url

* [Update] 修改url 为django 2.0 path

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

* [Update] 升级url

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

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

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

* [Bugfix] 修复一些bug

* [Bugfix] 修复一些bug

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

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

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

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

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

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

* [Update] 修改token返回

* [Update] Asset返回org name

* [Update] 修改支持xpack

* [Update] 修改url

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

* [Update] 用户修改

* [Bugfix] ...

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

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

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

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

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

* [Bugfix] 修复小bug

* [Update] 优化一些api

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

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

* [Update] 修改版本号
parent dded4e10
......@@ -32,3 +32,4 @@ django.db
celerybeat-schedule.db
data/static
docs/_build/
xpack
......@@ -2,4 +2,4 @@
# -*- coding: utf-8 -*-
#
__version__ = "1.3.3"
__version__ = "1.4.0"
......@@ -20,7 +20,7 @@ from rest_framework_bulk import BulkModelViewSet
from common.mixins import IDInFilterMixin
from common.utils import get_logger
from ..hands import IsSuperUser
from ..hands import IsOrgAdmin
from ..models import AdminUser, Asset
from .. import serializers
from ..tasks import test_admin_user_connectability_manual
......@@ -39,19 +39,19 @@ class AdminUserViewSet(IDInFilterMixin, BulkModelViewSet):
"""
queryset = AdminUser.objects.all()
serializer_class = serializers.AdminUserSerializer
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
class AdminUserAuthApi(generics.UpdateAPIView):
queryset = AdminUser.objects.all()
serializer_class = serializers.AdminUserAuthSerializer
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
class ReplaceNodesAdminUserApi(generics.UpdateAPIView):
queryset = AdminUser.objects.all()
serializer_class = serializers.ReplaceNodeAdminUserSerializer
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
def update(self, request, *args, **kwargs):
admin_user = self.get_object()
......@@ -75,7 +75,7 @@ class AdminUserTestConnectiveApi(generics.RetrieveAPIView):
Test asset admin user connectivity
"""
queryset = AdminUser.objects.all()
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs):
admin_user = self.get_object()
......
......@@ -2,8 +2,9 @@
#
import random
import time
from rest_framework import generics
from rest_framework import generics, permissions
from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
......@@ -13,7 +14,7 @@ from django.db.models import Q
from common.mixins import IDInFilterMixin
from common.utils import get_logger
from ..hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser
from common.permissions import IsOrgAdmin, IsAppUser, IsOrgAdminOrAppUser
from ..models import Asset, SystemUser, AdminUser, Node
from .. import serializers
from ..tasks import update_asset_hardware_info_manual, \
......@@ -39,38 +40,42 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer
pagination_class = LimitOffsetPagination
permission_classes = (IsSuperUserOrAppUser,)
permission_classes = (permissions.AllowAny,)
def get_queryset(self):
queryset = super().get_queryset()\
.prefetch_related('labels', 'nodes')\
.select_related('admin_user')
admin_user_id = self.request.query_params.get('admin_user_id')
def filter_node(self):
node_id = self.request.query_params.get("node_id")
if not node_id:
return
node = get_object_or_404(Node, id=node_id)
show_current_asset = self.request.query_params.get("show_current_asset")
if node.is_root():
if show_current_asset:
self.queryset = self.queryset.filter(
Q(nodes=node_id) | Q(nodes__isnull=True)
).distinct()
return
if show_current_asset:
self.queryset = self.queryset.filter(nodes=node).distinct()
else:
self.queryset = self.queryset.filter(
nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key),
).distinct()
def filter_admin_user_id(self):
admin_user_id = self.request.query_params.get('admin_user_id')
if admin_user_id:
admin_user = get_object_or_404(AdminUser, id=admin_user_id)
queryset = queryset.filter(admin_user=admin_user)
self.queryset = self.queryset.filter(admin_user=admin_user)
if node_id and show_current_asset:
node = get_object_or_404(Node, id=node_id)
if node.is_root():
queryset = queryset.filter(
Q(nodes=node_id) | Q(nodes__isnull=True)
).distinct()
else:
queryset = queryset.filter(nodes=node).distinct()
if node_id and not show_current_asset:
node = get_object_or_404(Node, id=node_id)
if node.is_root():
queryset = Asset.objects.all()
else:
queryset = queryset.filter(
nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key),
).distinct()
return queryset
def get_queryset(self):
self.queryset = super().get_queryset()\
.prefetch_related('labels', 'nodes')\
.select_related('admin_user')
self.filter_admin_user_id()
self.filter_node()
return self.queryset
class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
......@@ -79,7 +84,7 @@ class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
"""
queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
class AssetRefreshHardwareApi(generics.RetrieveAPIView):
......@@ -88,7 +93,7 @@ class AssetRefreshHardwareApi(generics.RetrieveAPIView):
"""
queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs):
asset_id = kwargs.get('pk')
......@@ -102,7 +107,7 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView):
Test asset admin user connectivity
"""
queryset = Asset.objects.all()
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs):
asset_id = kwargs.get('pk')
......@@ -113,7 +118,7 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView):
class AssetGatewayApi(generics.RetrieveAPIView):
queryset = Asset.objects.all()
permission_classes = (IsSuperUserOrAppUser,)
permission_classes = (IsOrgAdminOrAppUser,)
def retrieve(self, request, *args, **kwargs):
asset_id = kwargs.get('pk')
......
......@@ -2,12 +2,11 @@
from rest_framework_bulk import BulkModelViewSet
from rest_framework.views import APIView, Response
from rest_framework.generics import RetrieveAPIView
from django.views.generic.detail import SingleObjectMixin
from common.utils import get_logger
from ..hands import IsSuperUser, IsSuperUserOrAppUser
from common.permissions import IsOrgAdmin, IsAppUser, IsOrgAdminOrAppUser
from ..models import Domain, Gateway
from ..utils import test_gateway_connectability
from .. import serializers
......@@ -19,7 +18,7 @@ __all__ = ['DomainViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"]
class DomainViewSet(BulkModelViewSet):
queryset = Domain.objects.all()
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.DomainSerializer
def get_serializer_class(self):
......@@ -29,7 +28,7 @@ class DomainViewSet(BulkModelViewSet):
def get_permissions(self):
if self.request.query_params.get('gateway'):
self.permission_classes = (IsSuperUserOrAppUser,)
self.permission_classes = (IsOrgAdminOrAppUser,)
return super().get_permissions()
......@@ -37,12 +36,12 @@ class GatewayViewSet(BulkModelViewSet):
filter_fields = ("domain",)
search_fields = filter_fields
queryset = Gateway.objects.all()
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.GatewaySerializer
class GatewayTestConnectionApi(SingleObjectMixin, APIView):
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
model = Gateway
object = None
......
......@@ -17,7 +17,7 @@ from rest_framework_bulk import BulkModelViewSet
from django.db.models import Count
from common.utils import get_logger
from ..hands import IsSuperUser
from ..hands import IsOrgAdmin
from ..models import Label
from .. import serializers
......@@ -27,8 +27,7 @@ __all__ = ['LabelViewSet']
class LabelViewSet(BulkModelViewSet):
queryset = Label.objects.annotate(asset_count=Count("assets"))
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.LabelSerializer
def list(self, request, *args, **kwargs):
......@@ -36,3 +35,7 @@ class LabelViewSet(BulkModelViewSet):
self.serializer_class = serializers.LabelDistinctSerializer
self.queryset = self.queryset.values("name").distinct()
return super().list(request, *args, **kwargs)
def get_queryset(self):
self.queryset = Label.objects.annotate(asset_count=Count("assets"))
return self.queryset
......@@ -13,16 +13,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from rest_framework import generics, mixins
from rest_framework import generics, mixins, viewsets
from rest_framework.serializers import ValidationError
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet
from django.utils.translation import ugettext_lazy as _
from django.shortcuts import get_object_or_404
from django.db.models import Count
from common.utils import get_logger, get_object_or_none
from ..hands import IsSuperUser
from ..hands import IsOrgAdmin
from ..models import Node
from ..tasks import update_assets_hardware_info_util, test_asset_connectability_util
from .. import serializers
......@@ -30,57 +31,31 @@ from .. import serializers
logger = get_logger(__file__)
__all__ = [
'NodeViewSet', 'NodeChildrenApi',
'NodeAssetsApi',
'NodeAddAssetsApi', 'NodeRemoveAssetsApi',
'NodeReplaceAssetsApi',
'NodeViewSet', 'NodeChildrenApi', 'NodeAssetsApi',
'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'NodeReplaceAssetsApi',
'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi',
'TestNodeConnectiveApi'
]
class NodeViewSet(BulkModelViewSet):
class NodeViewSet(viewsets.ModelViewSet):
queryset = Node.objects.all()
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeSerializer
def get_queryset(self):
queryset = super().get_queryset().annotate(Count('assets'))
return queryset
def perform_create(self, serializer):
child_key = Node.root().get_next_child_key()
serializer.validated_data["key"] = child_key
serializer.save()
# class NodeWithAssetsApi(generics.ListAPIView):
# permission_classes = (IsSuperUser,)
# serializers = serializers.NodeSerializer
#
# def get_node(self):
# pk = self.kwargs.get('pk') or self.request.query_params.get('node')
# if not pk:
# node = Node.root()
# else:
# node = get_object_or_404(Node, pk)
# return node
#
# def get_queryset(self):
# queryset = []
# node = self.get_node()
# children = node.get_children()
# assets = node.get_assets()
# queryset.extend(list(children))
#
# for asset in assets:
# node = Node()
# node.id = asset.id
# node.parent = node.id
# node.value = asset.hostname
# queryset.append(node)
# return queryset
class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
queryset = Node.objects.all()
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeSerializer
instance = None
......@@ -126,22 +101,26 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
query_all = self.request.query_params.get("all")
query_assets = self.request.query_params.get('assets')
node = self.get_object()
if node is None:
node = Node.root()
node.assets__count = node.get_all_assets().count()
queryset.append(node)
if query_all:
children = node.get_all_children()
children = node.get_all_children().annotate(Count("assets"))
else:
children = node.get_children()
children = node.get_children().annotate(Count("assets"))
queryset.extend(list(children))
if query_assets:
assets = node.get_assets()
for asset in assets:
node_fake = Node()
node_fake.assets__count = 0
node_fake.id = asset.id
node_fake.is_node = False
node_fake.parent_id = node.id
node_fake.key = node.key + ':0'
node_fake.value = asset.hostname
queryset.append(node_fake)
queryset = sorted(queryset, key=lambda x: x.is_node, reverse=True)
......@@ -152,7 +131,7 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
class NodeAssetsApi(generics.ListAPIView):
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetSerializer
def get_queryset(self):
......@@ -167,7 +146,7 @@ class NodeAssetsApi(generics.ListAPIView):
class NodeAddChildrenApi(generics.UpdateAPIView):
queryset = Node.objects.all()
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeAddChildrenSerializer
instance = None
......@@ -185,7 +164,7 @@ class NodeAddChildrenApi(generics.UpdateAPIView):
class NodeAddAssetsApi(generics.UpdateAPIView):
serializer_class = serializers.NodeAssetsSerializer
queryset = Node.objects.all()
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
instance = None
def perform_update(self, serializer):
......@@ -197,7 +176,7 @@ class NodeAddAssetsApi(generics.UpdateAPIView):
class NodeRemoveAssetsApi(generics.UpdateAPIView):
serializer_class = serializers.NodeAssetsSerializer
queryset = Node.objects.all()
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
instance = None
def perform_update(self, serializer):
......@@ -213,7 +192,7 @@ class NodeRemoveAssetsApi(generics.UpdateAPIView):
class NodeReplaceAssetsApi(generics.UpdateAPIView):
serializer_class = serializers.NodeAssetsSerializer
queryset = Node.objects.all()
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
instance = None
def perform_update(self, serializer):
......@@ -224,7 +203,7 @@ class NodeReplaceAssetsApi(generics.UpdateAPIView):
class RefreshNodeHardwareInfoApi(APIView):
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
model = Node
def get(self, request, *args, **kwargs):
......@@ -237,7 +216,7 @@ class RefreshNodeHardwareInfoApi(APIView):
class TestNodeConnectiveApi(APIView):
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
model = Node
def get(self, request, *args, **kwargs):
......
......@@ -16,8 +16,9 @@
from rest_framework import generics
from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet
from common.utils import get_logger
from ..hands import IsSuperUser, IsSuperUserOrAppUser
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from ..models import SystemUser
from .. import serializers
from ..tasks import push_system_user_to_assets_manual, \
......@@ -37,7 +38,7 @@ class SystemUserViewSet(BulkModelViewSet):
"""
queryset = SystemUser.objects.all()
serializer_class = serializers.SystemUserSerializer
permission_classes = (IsSuperUserOrAppUser,)
permission_classes = (IsOrgAdminOrAppUser,)
class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
......@@ -45,7 +46,7 @@ class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
Get system user auth info
"""
queryset = SystemUser.objects.all()
permission_classes = (IsSuperUserOrAppUser,)
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.SystemUserAuthSerializer
def destroy(self, request, *args, **kwargs):
......@@ -59,7 +60,7 @@ class SystemUserPushApi(generics.RetrieveAPIView):
Push system user to cluster assets api
"""
queryset = SystemUser.objects.all()
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs):
system_user = self.get_object()
......@@ -75,7 +76,7 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView):
Push system user to cluster assets api
"""
queryset = SystemUser.objects.all()
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs):
system_user = self.get_object()
......
......@@ -3,14 +3,17 @@
from django import forms
from django.utils.translation import gettext_lazy as _
from ..models import Asset, AdminUser
from common.utils import get_logger
from orgs.mixins import OrgModelForm
from ..models import Asset, AdminUser
logger = get_logger(__file__)
__all__ = ['AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm']
class AssetCreateForm(forms.ModelForm):
class AssetCreateForm(OrgModelForm):
class Meta:
model = Asset
fields = [
......@@ -50,7 +53,7 @@ class AssetCreateForm(forms.ModelForm):
}
class AssetUpdateForm(forms.ModelForm):
class AssetUpdateForm(OrgModelForm):
class Meta:
model = Asset
fields = [
......@@ -90,7 +93,7 @@ class AssetUpdateForm(forms.ModelForm):
}
class AssetBulkUpdateForm(forms.ModelForm):
class AssetBulkUpdateForm(OrgModelForm):
assets = forms.ModelMultipleChoiceField(
required=True, help_text='* required',
label=_('Select assets'), queryset=Asset.objects.all(),
......@@ -105,7 +108,7 @@ class AssetBulkUpdateForm(forms.ModelForm):
label=_('Port'), required=False, min_value=1, max_value=65535,
)
admin_user = forms.ModelChoiceField(
required=False, queryset=AdminUser.objects.all(),
required=False, queryset=AdminUser.objects,
label=_("Admin user"),
widget=forms.Select(
attrs={
......
......@@ -3,6 +3,7 @@
from django import forms
from django.utils.translation import gettext_lazy as _
from orgs.mixins import OrgModelForm
from ..models import Domain, Asset, Gateway
from .user import PasswordAndKeyAuthForm
......@@ -34,7 +35,7 @@ class DomainForm(forms.ModelForm):
return instance
class GatewayForm(PasswordAndKeyAuthForm):
class GatewayForm(PasswordAndKeyAuthForm, OrgModelForm):
def save(self, commit=True):
# Because we define custom field, so we need rewrite :method: `save`
......
......@@ -11,6 +11,6 @@
"""
from common.mixins import AdminUserRequiredMixin
from common.permissions import IsAppUser, IsSuperUser, IsValidUser, IsSuperUserOrAppUser
from common.permissions import AdminUserRequiredMixin
from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser
from users.models import User, UserGroup
......@@ -13,6 +13,7 @@ from django.core.cache import cache
from ..const import ASSET_ADMIN_CONN_CACHE_KEY
from .user import AdminUser, SystemUser
from orgs.mixins import OrgModelMixin,OrgManager
__all__ = ['Asset']
logger = logging.getLogger(__name__)
......@@ -44,12 +45,7 @@ class AssetQuerySet(models.QuerySet):
return self.active()
class AssetManager(models.Manager):
def get_queryset(self):
return AssetQuerySet(self.model, using=self._db)
class Asset(models.Model):
class Asset(OrgModelMixin):
# Important
PLATFORM_CHOICES = (
('Linux', 'Linux'),
......@@ -71,16 +67,11 @@ class Asset(models.Model):
)
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'),
db_index=True)
hostname = models.CharField(max_length=128, unique=True,
verbose_name=_('Hostname'))
protocol = models.CharField(max_length=128, default=SSH_PROTOCOL,
choices=PROTOCOL_CHOICES,
verbose_name=_('Protocol'))
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
hostname = models.CharField(max_length=128, verbose_name=_('Hostname'))
protocol = models.CharField(max_length=128, default=SSH_PROTOCOL, choices=PROTOCOL_CHOICES, verbose_name=_('Protocol'))
port = models.IntegerField(default=22, verbose_name=_('Port'))
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES,
default='Linux', verbose_name=_('Platform'))
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform'))
domain = models.ForeignKey("assets.Domain", null=True, blank=True,
related_name='assets', verbose_name=_("Domain"),
on_delete=models.SET_NULL)
......@@ -94,11 +85,8 @@ class Asset(models.Model):
null=True, verbose_name=_("Admin user"))
# Some information
public_ip = models.GenericIPAddressField(max_length=32, blank=True,
null=True,
verbose_name=_('Public IP'))
number = models.CharField(max_length=32, null=True, blank=True,
verbose_name=_('Asset number'))
public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP'))
number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
# Collect
vendor = models.CharField(max_length=64, null=True, blank=True,
......@@ -139,7 +127,7 @@ class Asset(models.Model):
comment = models.TextField(max_length=128, default='', blank=True,
verbose_name=_('Comment'))
objects = AssetManager()
objects = OrgManager.from_queryset(AssetQuerySet)()
def __str__(self):
return '{0.hostname}({0.ip})'.format(self)
......@@ -173,6 +161,12 @@ class Asset(models.Model):
nodes = list(reduce(lambda x, y: set(x) | set(y), nodes))
return nodes
@property
def org_name(self):
from orgs.models import Organization
org = Organization.get_instance(self.org_id)
return org.name
@property
def hardware_info(self):
if self.cpu_count:
......@@ -233,7 +227,7 @@ class Asset(models.Model):
return data
class Meta:
unique_together = ('ip', 'port')
unique_together = [('org_id', 'hostname')]
verbose_name = _("Asset")
@classmethod
......
......@@ -11,14 +11,15 @@ from django.conf import settings
from common.utils import get_signer, ssh_key_string_to_obj, ssh_key_gen
from common.validators import alphanumeric
from orgs.mixins import OrgModelMixin
from .utils import private_key_validator
signer = get_signer()
class AssetUser(models.Model):
class AssetUser(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
name = models.CharField(max_length=128, verbose_name=_('Name'))
username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric])
_password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])
......
......@@ -7,12 +7,13 @@ import random
from django.db import models
from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelMixin
from .base import AssetUser
__all__ = ['Domain', 'Gateway']
class Domain(models.Model):
class Domain(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
......@@ -43,10 +44,12 @@ class Gateway(AssetUser):
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
port = models.IntegerField(default=22, verbose_name=_('Port'))
protocol = models.CharField(choices=PROTOCOL_CHOICES, max_length=16, default=SSH_PROTOCOL, verbose_name=_("Protocol"))
domain = models.ForeignKey(Domain, verbose_name=_("Domain"), on_delete=models.CASCADE)
domain = models.ForeignKey(Domain, on_delete=models.CASCADE, verbose_name=_("Domain"))
comment = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Comment"))
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
def __str__(self):
return self.name
class Meta:
unique_together = [('name', 'org_id')]
......@@ -4,9 +4,10 @@
import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelMixin
class Label(models.Model):
class Label(OrgModelMixin):
SYSTEM_CATEGORY = "S"
USER_CATEGORY = "U"
CATEGORY_CHOICES = (
......@@ -34,4 +35,4 @@ class Label(models.Model):
class Meta:
db_table = "assets_label"
unique_together = ('name', 'value')
unique_together = [('name', 'value')]
......@@ -5,12 +5,15 @@ import uuid
from django.db import models, transaction
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from common.utils import with_cache
from orgs.mixins import OrgModelMixin
from orgs.utils import current_org, set_current_org, get_current_org
from orgs.models import Organization
__all__ = ['Node']
class Node(models.Model):
class Node(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1'
value = models.CharField(max_length=128, verbose_name=_("Value"))
......@@ -20,7 +23,8 @@ class Node(models.Model):
is_node = True
def __str__(self):
return self.full_value
return self.value
# return self.full_value
def __eq__(self, other):
return self.key == other.key
......@@ -93,12 +97,10 @@ class Node(models.Model):
def get_assets(self):
from .asset import Asset
if self.is_root():
assets = Asset.objects.filter(
Q(nodes__id=self.id) | Q(nodes__isnull=True)
)
if self.is_default_node():
assets = Asset.objects.filter(nodes__isnull=True)
else:
assets = self.assets.all()
assets = Asset.objects.filter(nodes__id=self.id)
return assets
def get_valid_assets(self):
......@@ -106,49 +108,61 @@ class Node(models.Model):
def get_all_assets(self):
from .asset import Asset
if self.is_root():
assets = Asset.objects.all()
pattern = r'^{0}$|^{0}:'.format(self.key)
args = []
kwargs = {}
if self.is_default_node():
args.append(Q(nodes__key__regex=pattern) | Q(nodes=None))
else:
pattern = r'^{0}$|^{0}:'.format(self.key)
assets = Asset.objects.filter(nodes__key__regex=pattern)
kwargs['nodes__key__regex'] = pattern
assets = Asset.objects.filter(*args, **kwargs)
return assets
def get_all_valid_assets(self):
return self.get_all_assets().valid()
def is_default_node(self):
return self.is_root() and self.key == '0'
def is_root(self):
return self.key == '0'
if self.key.isdigit():
return True
else:
return False
@property
def parent(self):
if self.key == "0" or not self.key.startswith("0"):
return self.__class__.root()
def parent_key(self):
parent_key = ":".join(self.key.split(":")[:-1])
return parent_key
@property
def parent(self):
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()
......@@ -161,11 +175,36 @@ class Node(models.Model):
).order_by('key')
return ancestor
@classmethod
def create_root_node(cls):
# 如果使用current_org 在set_current_org时会死循环
_current_org = get_current_org()
with transaction.atomic():
if _current_org.is_default():
key = '0'
else:
set_current_org(Organization.root())
org_nodes_roots = cls.objects.filter(key__regex=r'^[0-9]+$')
org_nodes_roots_keys = org_nodes_roots.values_list('key', flat=True)
key = max([int(k) for k in org_nodes_roots_keys]) + 1
set_current_org(_current_org)
root = cls.objects.create(key=key, value=_current_org.name)
return root
@classmethod
def root(cls):
obj, created = cls.objects.get_or_create(
key='0', defaults={"key": '0', 'value': "ROOT"}
)
print(obj)
return obj
root = cls.objects.filter(key__regex=r'^[0-9]+$')
if root:
return root[0]
else:
return cls.create_root_node()
@classmethod
def generate_fake(cls, count=100):
import random
for i in range(count):
node = random.choice(cls.objects.all())
node.create_child('Node {}'.format(i))
......@@ -69,6 +69,7 @@ class AdminUser(AssetUser):
class Meta:
ordering = ['name']
unique_together = [('name', 'org_id')]
verbose_name = _("Admin user")
@classmethod
......@@ -176,6 +177,7 @@ class SystemUser(AssetUser):
class Meta:
ordering = ['name']
unique_together = [('name', 'org_id')]
verbose_name = _("System user")
@classmethod
......
......@@ -58,7 +58,7 @@ class ReplaceNodeAdminUserSerializer(serializers.ModelSerializer):
管理用户更新关联到的集群
"""
nodes = serializers.PrimaryKeyRelatedField(
many=True, queryset=Node.objects.all()
many=True, queryset = Node.objects.all()
)
class Meta:
......
......@@ -20,12 +20,12 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
model = Asset
list_serializer_class = BulkListSerializer
fields = '__all__'
validators = [] # If not set to [], partial bulk update will be error
# validators = [] # If not set to [], partial bulk update will be error
def get_field_names(self, declared_fields, info):
fields = super().get_field_names(declared_fields, info)
fields.extend([
'hardware_info', 'is_connective',
'hardware_info', 'is_connective', 'org_name'
])
return fields
......@@ -43,7 +43,7 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
fields = (
"id", "hostname", "ip", "port", "system_users_granted",
"is_active", "system_users_join", "os", 'domain',
"platform", "comment", "protocol",
"platform", "comment", "protocol", "org_id", "org_name",
)
@staticmethod
......@@ -61,6 +61,6 @@ class MyAssetGrantedSerializer(AssetGrantedSerializer):
model = Asset
fields = (
"id", "hostname", "system_users_granted",
"is_active", "system_users_join",
"os", "platform", "comment",
"is_active", "system_users_join", "org_name",
"os", "platform", "comment", "org_id", "protocol"
)
......@@ -26,7 +26,7 @@ class NodeGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer):
model = Node
fields = [
'id', 'key', 'name', 'value', 'parent',
'assets_granted', 'assets_amount',
'assets_granted', 'assets_amount', 'org_id',
]
@staticmethod
......@@ -43,12 +43,16 @@ class NodeGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer):
class NodeSerializer(serializers.ModelSerializer):
parent = serializers.SerializerMethodField()
assets_amount = serializers.SerializerMethodField()
tree_id = serializers.SerializerMethodField()
tree_parent = serializers.SerializerMethodField()
class Meta:
model = Node
fields = ['id', 'key', 'value', 'parent', 'assets_amount', 'is_node']
fields = [
'id', 'key', 'value', 'assets_amount',
'is_node', 'org_id', 'tree_id', 'tree_parent',
]
list_serializer_class = BulkListSerializer
def validate(self, data):
......@@ -63,12 +67,16 @@ class NodeSerializer(serializers.ModelSerializer):
return data
@staticmethod
def get_parent(obj):
return obj.parent.id if obj.is_node else obj.parent_id
def get_assets_amount(obj):
return obj.assets__count if hasattr(obj, 'assets__count') else 0
@staticmethod
def get_assets_amount(obj):
return obj.get_all_assets().count() if obj.is_node else 0
def get_tree_id(obj):
return obj.key
@staticmethod
def get_tree_parent(obj):
return obj.parent_key
def get_fields(self):
fields = super().get_fields()
......@@ -78,7 +86,7 @@ class NodeSerializer(serializers.ModelSerializer):
class NodeAssetsSerializer(serializers.ModelSerializer):
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
assets = serializers.PrimaryKeyRelatedField(many=True, queryset = Asset.objects.all())
class Meta:
model = Node
......
......@@ -71,7 +71,7 @@ function initTable2() {
function onSelected2(event, treeNode) {
var url = asset_table2.ajax.url();
url = setUrlParam(url, "node_id", treeNode.id);
url = setUrlParam(url, "node_id", treeNode.node_id);
setCookie('node_selected', treeNode.id);
asset_table2.ajax.url(url);
asset_table2.ajax.reload();
......@@ -97,17 +97,20 @@ function initTree2() {
var zNodes = [];
$.get("{% url 'api-assets:node-list' %}", function(data, status){
$.each(data, function (index, value) {
value["pId"] = value["parent"];
value["node_id"] = value["id"];
value["id"] = value["tree_id"];
value["pId"] = value["tree_parent"];
{#value["open"] = true;#}
if (value["key"] === "0") {
value["open"] = true;
}
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
value['value'] = value['value'];
});
zNodes = data;
$.fn.zTree.init($("#assetTree2"), setting, zNodes);
zTree2 = $.fn.zTree.getZTreeObj("assetTree2");
var root = zTree2.getNodes()[0];
zTree2.expandNode(root);
});
}
......
{% extends '_modal.html' %}
{% load i18n %}
{% load static %}
<style>
.modal-body {
background-color: white !important;
}
</style>
{% block modal_id %}user_asset_detail_modal{% endblock %}
{% block modal_title %}{% trans "Asset detail" %}{% endblock %}
{% block modal_body %}
<div class="ibox-content" style="background-color: inherit">
<table class="table">
<tbody id="asset_detail_tbody">
</tbody>
</table>
</div>
{% endblock %}
{% block modal_button %}
<button data-dismiss="modal" class="btn btn-white" type="button">{% trans "Close" %}</button>
{% endblock %}
......@@ -130,7 +130,7 @@
</div>
</div>
</div>
{% if user.is_superuser %}
{% if user.is_superuser or user.is_org_admin %}
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
<div class="panel panel-primary">
<div class="panel-heading">
......
......@@ -10,6 +10,7 @@
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">
{# <link href="https://cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css" rel="stylesheet">#}
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
<script src="{% static 'js/jquery.form.min.js' %}"></script>
<style type="text/css">
......@@ -27,6 +28,10 @@
list-style: none;
background-clip: padding-box;
}
.dataTables_wrapper .dataTables_processing {
opacity: .9;
border: none;
}
div#rMenu li{
margin: 1px 0;
cursor: pointer;
......@@ -161,16 +166,6 @@ function initTable() {
}
}},
{#{targets: 5, createdCell: function (td, cellData) {#}
{# if (cellData === 'Unknown'){#}
{# $(td).html('<i class="fa fa-circle text-warning"></i>')#}
{# } else if (!cellData) {#}
{# $(td).html('<i class="fa fa-circle text-danger"></i>')#}
{# } else {#}
{# $(td).html('<i class="fa fa-circle text-navy"></i>')#}
{# }#}
{# }},#}
{targets: 5, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:asset-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
......@@ -178,13 +173,6 @@ function initTable() {
}}
],
ajax_url: '{% url "api-assets:asset-list" %}',
{#columns: [#}
{# {data: "id"}, {data: "hostname" }, {data: "ip" },#}
{# {data: "cpu_cores"}, {data: "is_active", orderable: false },#}
{# {data: "is_connective", orderable: false}, {data: "id", orderable: false }#}
{#],#}
columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" },
{data: "cpu_cores"}, {data: "is_active", orderable: false },
......@@ -202,17 +190,17 @@ function addTreeNode() {
if (!parentNode){
return
}
var url = "{% url 'api-assets:node-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", parentNode.id );
var url = "{% url 'api-assets:node-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", parentNode.node_id );
$.post(url, {}, function (data, status){
if (status === "success") {
var newNode = {
name: data["value"],
id: data["id"],
pId: parentNode.id
pId: parentNode.node_id
};
newNode.checked = zTree.getSelectedNodes()[0].checked;
zTree.addNodes(parentNode, 0, newNode);
var node = zTree.getNodeByParam('id', newNode.id, parentNode)
var node = zTree.getNodeByParam('id', newNode.node_id, parentNode);
zTree.editName(node);
} else {
alert("{% trans 'Create node failed' %}")
......@@ -231,7 +219,7 @@ function removeTreeNode() {
} else if (current_node.assets_amount !== 0) {
toastr.error("{% trans 'Have assets, cancel' %}");
} else {
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.id );
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.node_id );
$.ajax({
url: url,
method: "DELETE",
......@@ -291,7 +279,7 @@ function onBodyMouseDown(event){
function onRename(event, treeId, treeNode, isCancel){
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", treeNode.id);
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", treeNode.node_id);
var data = {"value": treeNode.name};
if (isCancel){
return
......@@ -305,9 +293,9 @@ function onRename(event, treeId, treeNode, isCancel){
function onSelected(event, treeNode) {
var url = asset_table.ajax.url();
url = setUrlParam(url, "node_id", treeNode.id);
url = setUrlParam(url, "node_id", treeNode.node_id);
url = setUrlParam(url, "show_current_asset", getCookie('show_current_asset'));
setCookie('node_selected', treeNode.id);
setCookie('node_selected', treeNode.node_id);
asset_table.ajax.url(url);
asset_table.ajax.reload();
}
......@@ -324,7 +312,7 @@ function selectQueryNode() {
node_id = cookie_node_id;
}
node = zTree.getNodesByParam("id", node_id, null);
node = zTree.getNodesByParam("node_id", node_id, null);
if (node){
zTree.selectNode(node[0]);
}
......@@ -341,11 +329,7 @@ function beforeDrop(treeId, treeNodes, targetNode, moveType) {
});
var msg = "你想移动节点: `" + treeNodesNames.join(",") + "` 到 `" + targetNode.value + "` 下吗?";
if (confirm(msg)){
return true
} else {
return false
}
return confirm(msg);
}
function onDrag(event, treeId, treeNodes) {
......@@ -354,10 +338,10 @@ function onDrag(event, treeId, treeNodes) {
function onDrop(event, treeId, treeNodes, targetNode, moveType) {
var treeNodesIds = [];
$.each(treeNodes, function (index, value) {
treeNodesIds.push(value.id);
treeNodesIds.push(value.node_id);
});
var the_url = "{% url 'api-assets:node-add-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", targetNode.id);
var the_url = "{% url 'api-assets:node-add-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", targetNode.node_id);
var body = {nodes: treeNodesIds};
APIUpdateAttr({
url: the_url,
......@@ -401,16 +385,21 @@ function initTree() {
var zNodes = [];
$.get("{% url 'api-assets:node-list' %}", function(data, status){
$.each(data, function (index, value) {
value["pId"] = value["parent"];
if (value["key"] === "0") {
value["open"] = true;
}
value["node_id"] = value["id"];
value["id"] = value["tree_id"];
if (value["tree_id"] !== value["tree_parent"]){
value["pId"] = value["tree_parent"];
} else {
value["isParent"] = true;
}
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
value['value'] = value['value'];
});
zNodes = data;
$.fn.zTree.init($("#assetTree"), setting, zNodes);
zTree = $.fn.zTree.getZTreeObj("assetTree");
var root = zTree.getNodes()[0];
zTree.expandNode(root);
rMenu = $("#rMenu");
selectQueryNode();
});
......@@ -462,7 +451,7 @@ $(document).ready(function(){
$.ajax({
url: "{% url "assets:asset-export" %}",
method: 'POST',
data: JSON.stringify({assets_id: assets, node_id: current_node.id}),
data: JSON.stringify({assets_id: assets, node_id: current_node.node_id}),
dataType: "json",
success: function (data, textStatus) {
window.open(data.redirect)
......@@ -479,8 +468,8 @@ $(document).ready(function(){
var current_node;
if (nodes && nodes.length ===1 ){
current_node = nodes[0];
action = setUrlParam(action, 'node_id', current_node.id);
{#action += "?node_id=" + current_node.id;#}
action = setUrlParam(action, 'node_id', current_node.node_id);
{#action += "?node_id=" + current_node.node_id;#}
$form.attr("action", action)
}
$form.find('.help-block').remove();
......@@ -506,7 +495,7 @@ $(document).ready(function(){
var current_node;
if (nodes && nodes.length ===1 ){
current_node = nodes[0];
url += "?node_id=" + current_node.id;
url += "?node_id=" + current_node.node_id;
}
window.open(url, '_self');
})
......@@ -520,7 +509,7 @@ $(document).ready(function(){
return null;
}
var the_url = url.replace("{{ DEFAULT_PK }}", current_node.id);
var the_url = url.replace("{{ DEFAULT_PK }}", current_node.node_id);
function success(data) {
rMenu.css({"visibility" : "hidden"});
var task_id = data.task;
......@@ -545,7 +534,7 @@ $(document).ready(function(){
return null;
}
var the_url = url.replace("{{ DEFAULT_PK }}", current_node.id);
var the_url = url.replace("{{ DEFAULT_PK }}", current_node.node_id);
function success(data) {
rMenu.css({"visibility" : "hidden"});
var task_id = data.task;
......@@ -682,7 +671,7 @@ $(document).ready(function(){
};
APIUpdateAttr({
'url': '/api/assets/v1/nodes/' + current_node.id + '/assets/remove/',
'url': '/api/assets/v1/nodes/' + current_node.node_id + '/assets/remove/',
'method': 'PUT',
'body': JSON.stringify(data),
'success': success
......@@ -719,9 +708,7 @@ $(document).ready(function(){
return
}
var data = {
'assets': assets_selected
};
var data = {'assets': assets_selected};
var success = function () {
asset_table2.selected = [];
asset_table2.ajax.reload()
......@@ -729,9 +716,9 @@ $(document).ready(function(){
var url = '';
if (update_node_action === "move") {
url = "{% url 'api-assets:node-replace-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.id);
url = "{% url 'api-assets:node-replace-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.node_id);
} else {
url = "{% url 'api-assets:node-add-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.id);
url = "{% url 'api-assets:node-add-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.node_id);
}
APIUpdateAttr({
......
......@@ -4,8 +4,8 @@
{% block help_message %}
<div class="alert alert-info help-message">
网域功能是为了解决部分环境(如:混合云)无法直接连接而新增的功能,原理是通过网关服务器进行跳转登
录。
网域功能是为了解决部分环境(如:混合云)无法直接连接而新增的功能,原理是通过网关服务器进行跳转登录。<br>
JMS => 网域网关 => 目标资产
</div>
{% endblock %}
......
......@@ -55,11 +55,14 @@
</div>
</div>
</div>
{% include 'assets/_user_asset_detail_modal.html' %}
{% endblock %}
{% block custom_foot_js %}
<script>
var zTree, rMenu, asset_table;
var zTree, asset_table;
var inited = false;
var url;
function initTable() {
......@@ -68,14 +71,15 @@ function initTable() {
} else {
inited = true;
}
console.log("init table")
url = "{% url 'api-perms:my-assets' %}";
var options = {
ele: $('#user_assets_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
{% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %}
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
var detail_btn = '<a class="asset_detail" asset-id="rowData_id" data-toggle="modal" data-target="#user_asset_detail_modal" tabindex="0">'+ cellData +'</a>'
$(td).html(detail_btn.replace("rowData_id", rowData.id));
}},
{targets: 3, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
......@@ -103,33 +107,13 @@ function initTable() {
}
function onSelected(event, treeNode) {
console.log("select");
url = '{% url "api-perms:my-node-assets" node_id=DEFAULT_PK %}';
url = url.replace("{{ DEFAULT_PK }}", treeNode.id);
initTable();
url = url.replace("{{ DEFAULT_PK }}", treeNode.node_id);
setCookie('node_selected', treeNode.id);
asset_table.ajax.url(url);
asset_table.ajax.reload();
}
function selectQueryNode() {
var query_node_id = $.getUrlParam("node");
var cookie_node_id = getCookie('node_selected');
var node;
var node_id;
if (query_node_id !== null) {
node_id = query_node_id
} else if (cookie_node_id !== null) {
node_id = cookie_node_id;
}
node = zTree.getNodesByParam("id", node_id, null);
if (node){
zTree.selectNode(node[0]);
}
}
function initTree() {
var setting = {
view: {
......@@ -149,23 +133,56 @@ function initTree() {
var zNodes = [];
$.get("{% url 'api-perms:my-nodes' %}", function(data, status){
$.each(data, function (index, value) {
value["pId"] = value["parent"];
if (value["key"] === "0") {
value["open"] = true;
value["node_id"] = value["id"];
value["id"] = value["tree_id"];
if (value["tree_id"] !== value["tree_parent"]) {
value["pId"] = value["tree_parent"];
}
value["name"] = value["value"]
value["isParent"] = value["is_node"];
value['name'] = value['value'];
});
zNodes = data;
$.fn.zTree.init($("#assetTree"), setting, zNodes);
zTree = $.fn.zTree.getZTreeObj("assetTree");
rMenu = $("#rMenu");
selectQueryNode();
var root = zTree.getNodes()[0];
zTree.expandNode(root);
});
}
$(document).ready(function () {
initTree();
initTable();
})
.on('click', '.asset_detail', function() {
var data = asset_table.ajax.json();
var asset_id = this.getAttribute("asset-id");
var trs = '';
var desc = {
'hostname': "{% trans 'Hostname' %}",
'ip': "{% trans 'IP' %}",
'port': "{% trans 'Port' %}",
'protocol': "{% trans 'Protocol' %}",
'platform': "{% trans 'Platform' %}",
'os': "{% trans 'OS' %}",
'system_users_join': "{% trans 'System user' %}",
'domain': "{% trans 'Domain' %}",
'is_active': "{% trans 'Is active' %}",
'comment': "{% trans 'Comment' %}"
{#'date_joined': "{% trans 'Date joined' %}",#}
};
$.each(data, function(index, value){
if(value.id === asset_id){
for(var i in desc){
trs += "<tr class='no-borders-tr'>\n" +
"<td>"+ desc[i] + ":</td>"+
"<td><b>"+ (value[i] === null?'':value[i]) + "</b></td>\n" +
"</tr>";
}
}
});
$('#asset_detail_tbody').html(trs)
});
</script>
{% endblock %}
\ No newline at end of file
# 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
......
# 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'),
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'),
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'),
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('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'),
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'),
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'),
path('domain/<uuid:pk>/gateway/create/', views.DomainGatewayCreateView.as_view(), name='domain-gateway-create'),
path('domain/gateway/<uuid:pk>/update/', views.DomainGatewayUpdateView.as_view(), name='domain-gateway-update'),
]
......@@ -11,7 +11,7 @@ from django.views.generic.detail import DetailView, SingleObjectMixin
from common.const import create_success_msg, update_success_msg
from .. import forms
from ..models import AdminUser, Node
from ..hands import AdminUserRequiredMixin
from common.permissions import AdminUserRequiredMixin
__all__ = [
'AdminUserCreateView', 'AdminUserDetailView',
......
......@@ -29,7 +29,7 @@ from common.utils import get_object_or_none, get_logger, is_uuid
from common.const import create_success_msg, update_success_msg
from .. import forms
from ..models import Asset, AdminUser, SystemUser, Label, Node, Domain
from ..hands import AdminUserRequiredMixin
from common.permissions import AdminUserRequiredMixin
__all__ = [
......@@ -186,7 +186,7 @@ class AssetDeleteView(AdminUserRequiredMixin, DeleteView):
success_url = reverse_lazy('assets:asset-list')
class AssetDetailView(DetailView):
class AssetDetailView(LoginRequiredMixin, DetailView):
model = Asset
context_object_name = 'asset'
template_name = 'assets/asset_detail.html'
......@@ -203,7 +203,7 @@ class AssetDetailView(DetailView):
@method_decorator(csrf_exempt, name='dispatch')
class AssetExportView(View):
class AssetExportView(LoginRequiredMixin, View):
def get(self, request):
spm = request.GET.get('spm', '')
assets_id_default = [Asset.objects.first().id] if Asset.objects.first() else []
......@@ -211,7 +211,7 @@ class AssetExportView(View):
fields = [
field for field in Asset._meta.fields
if field.name not in [
'date_created'
'date_created', 'org_id'
]
]
filename = 'assets-{}.csv'.format(
......
......@@ -7,7 +7,7 @@ from django.views.generic.detail import SingleObjectMixin
from django.utils.translation import ugettext_lazy as _
from django.urls import reverse_lazy, reverse
from common.mixins import AdminUserRequiredMixin
from common.permissions import AdminUserRequiredMixin
from common.const import create_success_msg, update_success_msg
from common.utils import get_object_or_none
from ..models import Domain, Gateway
......
......@@ -6,7 +6,7 @@ from django.views.generic import TemplateView, CreateView, \
from django.utils.translation import ugettext_lazy as _
from django.urls import reverse_lazy
from common.mixins import AdminUserRequiredMixin
from common.permissions import AdminUserRequiredMixin
from common.const import create_success_msg, update_success_msg
from ..models import Label
from ..forms import LabelForm
......
......@@ -10,7 +10,7 @@ from django.views.generic.detail import DetailView
from common.const import create_success_msg, update_success_msg
from ..forms import SystemUserForm
from ..models import SystemUser, Node
from ..hands import AdminUserRequiredMixin
from common.permissions import AdminUserRequiredMixin
__all__ = [
......
......@@ -3,7 +3,7 @@
from rest_framework import viewsets
from common.permissions import IsSuperUserOrAppUser
from common.permissions import IsOrgAdminOrAppUser
from .models import FTPLog
from .serializers import FTPLogSerializer
......@@ -11,4 +11,4 @@ from .serializers import FTPLogSerializer
class FTPLogViewSet(viewsets.ModelViewSet):
queryset = FTPLog.objects.all()
serializer_class = FTPLogSerializer
permission_classes = (IsSuperUserOrAppUser,)
permission_classes = (IsOrgAdminOrAppUser,)
......@@ -3,8 +3,10 @@ import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelMixin
class FTPLog(models.Model):
class FTPLog(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
user = models.CharField(max_length=128, verbose_name=_('User'))
remote_addr = models.CharField(max_length=15, verbose_name=_("Remote addr"), blank=True, null=True)
......
......@@ -9,10 +9,9 @@ from .. import api
app_name = "audits"
router = DefaultRouter()
router.register(r'v1/ftp-log', api.FTPLogViewSet, 'ftp-log')
router.register(r'ftp-log', api.FTPLogViewSet, 'ftp-log')
urlpatterns = [
# url(r'^v1/celery/task/(?P<pk>[0-9a-zA-Z\-]{36})/log/$', api.CeleryTaskLogApi.as_view(), name='celery-task-log'),
]
urlpatterns += router.urls
# ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals
from django.conf.urls import url
from django.urls import path
from .. import views
__all__ = ["urlpatterns"]
......@@ -10,5 +9,5 @@ __all__ = ["urlpatterns"]
app_name = "audits"
urlpatterns = [
url(r'^ftp-log/$', views.FTPLogListView.as_view(), name='ftp-log-list'),
path('ftp-log/', views.FTPLogListView.as_view(), name='ftp-log-list'),
]
......@@ -2,7 +2,8 @@ from django.conf import settings
from django.views.generic import ListView
from django.utils.translation import ugettext as _
from common.mixins import AdminUserRequiredMixin, DatetimeSearchMixin
from common.mixins import DatetimeSearchMixin
from common.permissions import AdminUserRequiredMixin
from .models import FTPLog
......
......@@ -8,12 +8,12 @@ from django.core.mail import get_connection, send_mail
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from .permissions import IsSuperUser
from .permissions import IsOrgAdmin
from .serializers import MailTestSerializer, LDAPTestSerializer
class MailTestingAPI(APIView):
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
serializer_class = MailTestSerializer
success_message = _("Test mail sent to {}, please check")
......@@ -37,7 +37,7 @@ class MailTestingAPI(APIView):
class LDAPTestingAPI(APIView):
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
serializer_class = LDAPTestSerializer
success_message = _("Test ldap success")
......@@ -86,7 +86,18 @@ class LDAPTestingAPI(APIView):
class DjangoSettingsAPI(APIView):
def get(self, request):
return Response('Danger, Close now')
if not settings.DEBUG:
return Response("Not in debug mode")
data = {}
for k, v in settings.__dict__.items():
if k and k.isupper():
try:
json.dumps(v)
data[k] = v
except (json.JSONDecodeError, TypeError):
data[k] = str(v)
return Response(data)
......@@ -62,7 +62,7 @@ class EncryptMixin:
def get_prep_value(self, value):
if value is None:
return value
return signer.sign(value).decode('utf-8')
return signer.sign(value)
class EncryptTextField(EncryptMixin, models.TextField):
......
......@@ -4,7 +4,6 @@ from django.db import models
from django.http import JsonResponse
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.mixins import UserPassesTestMixin
class NoDeleteQuerySet(models.query.QuerySet):
......@@ -119,11 +118,4 @@ class DatetimeSearchMixin:
return super().get(request, *args, **kwargs)
class AdminUserRequiredMixin(UserPassesTestMixin):
def test_func(self):
if not self.request.user.is_authenticated:
return False
elif not self.request.user.is_superuser:
self.raise_exception = True
return False
return True
......@@ -2,6 +2,11 @@
#
from rest_framework import permissions
from django.contrib.auth.mixins import UserPassesTestMixin
from django.shortcuts import redirect
from django.http.response import HttpResponseForbidden
from orgs.utils import current_org
class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission):
......@@ -21,28 +26,40 @@ class IsAppUser(IsValidUser):
class IsSuperUser(IsValidUser):
def has_permission(self, request, view):
return super(IsSuperUser, self).has_permission(request, view) \
and request.user.is_superuser
class IsSuperUserOrAppUser(IsSuperUser):
def has_permission(self, request, view):
return super(IsSuperUserOrAppUser, self).has_permission(request, view) \
and (request.user.is_superuser or request.user.is_app)
class IsOrgAdmin(IsValidUser):
"""Allows access only to superuser"""
def has_permission(self, request, view):
return super(IsSuperUser, self).has_permission(request, view) \
and request.user.is_superuser
return super(IsOrgAdmin, self).has_permission(request, view) \
and current_org.can_admin_by(request.user)
class IsSuperUserOrAppUser(IsValidUser):
class IsOrgAdminOrAppUser(IsValidUser):
"""Allows access between superuser and app user"""
def has_permission(self, request, view):
return super(IsSuperUserOrAppUser, self).has_permission(request, view) \
and (request.user.is_superuser or request.user.is_app)
return super(IsOrgAdminOrAppUser, self).has_permission(request, view) \
and (current_org.can_admin_by(request.user) or request.user.is_app)
class IsSuperUserOrAppUserOrUserReadonly(IsSuperUserOrAppUser):
class IsOrgAdminOrAppUserOrUserReadonly(IsOrgAdminOrAppUser):
def has_permission(self, request, view):
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)
from __future__ import absolute_import
from django.conf.urls import url
from django.urls import path
from .. import api
app_name = 'common'
urlpatterns = [
url(r'^v1/mail/testing/$', api.MailTestingAPI.as_view(), name='mail-testing'),
url(r'^v1/ldap/testing/$', api.LDAPTestingAPI.as_view(), name='ldap-testing'),
url(r'^v1/django-settings/$', api.DjangoSettingsAPI.as_view(), name='django-settings'),
path('mail/testing/', api.MailTestingAPI.as_view(), name='mail-testing'),
path('ldap/testing/', api.LDAPTestingAPI.as_view(), name='ldap-testing'),
# path('django-settings/', api.DjangoSettingsAPI.as_view(), name='django-settings'),
]
......@@ -17,6 +17,7 @@ import threading
from io import StringIO
import uuid
from functools import wraps
import copy
import paramiko
import sshpubkeys
......@@ -67,10 +68,8 @@ class Signer(metaclass=Singleton):
self.secret_key = secret_key
def sign(self, value):
if isinstance(value, bytes):
value = value.decode("utf-8")
s = JSONWebSignatureSerializer(self.secret_key)
return s.dumps(value)
return s.dumps(value).decode()
def unsign(self, value):
if value is None:
......@@ -410,3 +409,122 @@ def with_cache(func):
cache[key] = res
return res
return wrapper
class LocalProxy(object):
"""
Copy from werkzeug.local.LocalProxy
"""
__slots__ = ('__local', '__dict__', '__name__', '__wrapped__')
def __init__(self, local, name=None):
object.__setattr__(self, '_LocalProxy__local', local)
object.__setattr__(self, '__name__', name)
if callable(local) and not hasattr(local, '__release_local__'):
# "local" is a callable that is not an instance of Local or
# LocalManager: mark it as a wrapped function.
object.__setattr__(self, '__wrapped__', local)
def _get_current_object(self):
"""Return the current object. This is useful if you want the real
object behind the proxy at a time for performance reasons or because
you want to pass the object into a different context.
"""
if not hasattr(self.__local, '__release_local__'):
return self.__local()
try:
return getattr(self.__local, self.__name__)
except AttributeError:
raise RuntimeError('no object bound to %s' % self.__name__)
@property
def __dict__(self):
try:
return self._get_current_object().__dict__
except RuntimeError:
raise AttributeError('__dict__')
def __repr__(self):
try:
obj = self._get_current_object()
except RuntimeError:
return '<%s unbound>' % self.__class__.__name__
return repr(obj)
def __bool__(self):
try:
return bool(self._get_current_object())
except RuntimeError:
return False
def __dir__(self):
try:
return dir(self._get_current_object())
except RuntimeError:
return []
def __getattr__(self, name):
if name == '__members__':
return dir(self._get_current_object())
return getattr(self._get_current_object(), name)
def __setitem__(self, key, value):
self._get_current_object()[key] = value
def __delitem__(self, key):
del self._get_current_object()[key]
__setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
__delattr__ = lambda x, n: delattr(x._get_current_object(), n)
__str__ = lambda x: str(x._get_current_object())
__lt__ = lambda x, o: x._get_current_object() < o
__le__ = lambda x, o: x._get_current_object() <= o
__eq__ = lambda x, o: x._get_current_object() == o
__ne__ = lambda x, o: x._get_current_object() != o
__gt__ = lambda x, o: x._get_current_object() > o
__ge__ = lambda x, o: x._get_current_object() >= o
__cmp__ = lambda x, o: cmp(x._get_current_object(), o) # noqa
__hash__ = lambda x: hash(x._get_current_object())
__call__ = lambda x, *a, **kw: x._get_current_object()(*a, **kw)
__len__ = lambda x: len(x._get_current_object())
__getitem__ = lambda x, i: x._get_current_object()[i]
__iter__ = lambda x: iter(x._get_current_object())
__contains__ = lambda x, i: i in x._get_current_object()
__add__ = lambda x, o: x._get_current_object() + o
__sub__ = lambda x, o: x._get_current_object() - o
__mul__ = lambda x, o: x._get_current_object() * o
__floordiv__ = lambda x, o: x._get_current_object() // o
__mod__ = lambda x, o: x._get_current_object() % o
__divmod__ = lambda x, o: x._get_current_object().__divmod__(o)
__pow__ = lambda x, o: x._get_current_object() ** o
__lshift__ = lambda x, o: x._get_current_object() << o
__rshift__ = lambda x, o: x._get_current_object() >> o
__and__ = lambda x, o: x._get_current_object() & o
__xor__ = lambda x, o: x._get_current_object() ^ o
__or__ = lambda x, o: x._get_current_object() | o
__div__ = lambda x, o: x._get_current_object().__div__(o)
__truediv__ = lambda x, o: x._get_current_object().__truediv__(o)
__neg__ = lambda x: -(x._get_current_object())
__pos__ = lambda x: +(x._get_current_object())
__abs__ = lambda x: abs(x._get_current_object())
__invert__ = lambda x: ~(x._get_current_object())
__complex__ = lambda x: complex(x._get_current_object())
__int__ = lambda x: int(x._get_current_object())
__float__ = lambda x: float(x._get_current_object())
__oct__ = lambda x: oct(x._get_current_object())
__hex__ = lambda x: hex(x._get_current_object())
__index__ = lambda x: x._get_current_object().__index__()
__coerce__ = lambda x, o: x._get_current_object().__coerce__(x, o)
__enter__ = lambda x: x._get_current_object().__enter__()
__exit__ = lambda x, *a, **kw: x._get_current_object().__exit__(*a, **kw)
__radd__ = lambda x, o: o + x._get_current_object()
__rsub__ = lambda x, o: o - x._get_current_object()
__rmul__ = lambda x, o: o * x._get_current_object()
__rdiv__ = lambda x, o: o / x._get_current_object()
__rtruediv__ = __rdiv__
__rfloordiv__ = lambda x, o: o // x._get_current_object()
__rmod__ = lambda x, o: o % x._get_current_object()
__rdivmod__ = lambda x, o: x._get_current_object().__rdivmod__(o)
__copy__ = lambda x: copy.copy(x._get_current_object())
__deepcopy__ = lambda x, memo: copy.deepcopy(x._get_current_object(), memo)
\ No newline at end of file
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
......
......@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-07-19 18:29+0800\n"
"POT-Creation-Date: 2018-08-03 15:14+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: Jumpserver team<ibuler@qq.com>\n"
......@@ -17,57 +17,57 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: assets/api/node.py:99
#: assets/api/node.py:97
msgid "New node {}"
msgstr "新节点 {}"
#: assets/api/node.py:234
#: assets/api/node.py:232
msgid "更新节点资产硬件信息: {}"
msgstr ""
#: assets/api/node.py:247
#: assets/api/node.py:245
msgid "测试节点下资产是否可连接: {}"
msgstr ""
#: assets/forms/asset.py:24 assets/models/asset.py:89 assets/models/user.py:112
#: assets/forms/asset.py:27 assets/models/asset.py:80 assets/models/user.py:113
#: assets/templates/assets/asset_detail.html:183
#: assets/templates/assets/asset_detail.html:191
#: assets/templates/assets/system_user_detail.html:178 perms/models.py:33
#: assets/templates/assets/system_user_detail.html:178 perms/models.py:32
msgid "Nodes"
msgstr "节点管理"
#: assets/forms/asset.py:27 assets/forms/asset.py:66 assets/forms/asset.py:109
#: assets/forms/asset.py:113 assets/models/asset.py:94
#: assets/models/cluster.py:19 assets/models/user.py:72
#: assets/forms/asset.py:30 assets/forms/asset.py:69 assets/forms/asset.py:112
#: assets/forms/asset.py:116 assets/models/asset.py:85
#: assets/models/cluster.py:19 assets/models/user.py:73
#: assets/templates/assets/asset_detail.html:73 templates/_nav.html:25
msgid "Admin user"
msgstr "管理用户"
#: assets/forms/asset.py:30 assets/forms/asset.py:69 assets/forms/asset.py:125
#: assets/forms/asset.py:33 assets/forms/asset.py:72 assets/forms/asset.py:128
#: assets/templates/assets/asset_create.html:36
#: assets/templates/assets/asset_create.html:38
#: assets/templates/assets/asset_list.html:75
#: assets/templates/assets/asset_list.html:80
#: assets/templates/assets/asset_update.html:41
#: assets/templates/assets/asset_update.html:43
#: assets/templates/assets/user_asset_list.html:34
msgid "Label"
msgstr "标签"
#: assets/forms/asset.py:34 assets/forms/asset.py:73 assets/models/asset.py:85
#: assets/models/domain.py:46
#: assets/forms/asset.py:37 assets/forms/asset.py:76 assets/models/asset.py:76
#: assets/models/domain.py:47 assets/templates/assets/user_asset_list.html:188
msgid "Domain"
msgstr "网域"
#: assets/forms/asset.py:38 assets/forms/asset.py:63 assets/forms/asset.py:77
#: assets/forms/asset.py:128 assets/templates/assets/asset_create.html:30
#: assets/templates/assets/asset_update.html:35 perms/forms.py:40
#: perms/forms.py:47 perms/models.py:76
#: assets/forms/asset.py:41 assets/forms/asset.py:66 assets/forms/asset.py:80
#: assets/forms/asset.py:131 assets/templates/assets/asset_create.html:30
#: assets/templates/assets/asset_update.html:35 perms/forms.py:50
#: perms/forms.py:57 perms/models.py:78
#: perms/templates/perms/asset_permission_list.html:57
#: perms/templates/perms/asset_permission_list.html:142
msgid "Node"
msgstr "节点"
#: assets/forms/asset.py:45 assets/forms/asset.py:85
#: assets/forms/asset.py:48 assets/forms/asset.py:88
msgid ""
"root or other NOPASSWD sudo privilege user existed in asset,If asset is "
"windows or other set any one, more see admin user left menu"
......@@ -75,42 +75,39 @@ msgstr ""
"root或其他拥有NOPASSWD: ALL权限的用户, 如果是windows或其它硬件可以随意设置一"
"个, 更多信息查看左侧 `管理用户` 菜单"
#: assets/forms/asset.py:48 assets/forms/asset.py:88
msgid "* required Must set exact system platform, Windows, Linux ..."
msgstr "* required 必须准确设置操作系统平台,如Windows, Linux ..."
#: assets/forms/asset.py:49 assets/forms/asset.py:89
#: assets/forms/asset.py:52 assets/forms/asset.py:92
msgid ""
"If your have some network not connect with each other, you can set domain"
msgstr "如果有多个的互相隔离的网络,设置资产属于的网域,使用网域网关跳转登录"
#: assets/forms/asset.py:96 assets/forms/asset.py:100 assets/forms/domain.py:16
#: assets/forms/asset.py:99 assets/forms/asset.py:103 assets/forms/domain.py:17
#: assets/forms/label.py:15
#: perms/templates/perms/asset_permission_asset.html:88
msgid "Select assets"
msgstr "选择资产"
#: assets/forms/asset.py:105 assets/models/asset.py:81
#: assets/models/domain.py:44 assets/templates/assets/admin_user_assets.html:53
#: assets/forms/asset.py:108 assets/models/asset.py:73
#: assets/models/domain.py:45 assets/templates/assets/admin_user_assets.html:53
#: assets/templates/assets/asset_detail.html:69
#: assets/templates/assets/domain_gateway_list.html:58
#: assets/templates/assets/system_user_asset.html:51
#: assets/templates/assets/user_asset_list.html:183
msgid "Port"
msgstr "端口"
#: assets/forms/domain.py:14 assets/forms/label.py:13
#: assets/models/asset.py:237 assets/templates/assets/admin_user_list.html:25
#: assets/forms/domain.py:15 assets/forms/label.py:13
#: assets/models/asset.py:231 assets/templates/assets/admin_user_list.html:25
#: assets/templates/assets/domain_detail.html:60
#: assets/templates/assets/domain_list.html:23
#: assets/templates/assets/label_list.html:16
#: assets/templates/assets/system_user_list.html:30 audits/models.py:11
#: assets/templates/assets/system_user_list.html:30 audits/models.py:13
#: audits/templates/audits/ftp_log_list.html:41
#: audits/templates/audits/ftp_log_list.html:72 perms/forms.py:37
#: perms/models.py:32
#: audits/templates/audits/ftp_log_list.html:72 perms/forms.py:47
#: perms/models.py:31
#: perms/templates/perms/asset_permission_create_update.html:40
#: perms/templates/perms/asset_permission_list.html:56
#: perms/templates/perms/asset_permission_list.html:139
#: terminal/backends/command/models.py:11 terminal/models.py:127
#: terminal/backends/command/models.py:13 terminal/models.py:128
#: terminal/templates/terminal/command_list.html:40
#: terminal/templates/terminal/command_list.html:73
#: terminal/templates/terminal/session_list.html:41
......@@ -118,9 +115,9 @@ msgstr "端口"
msgid "Asset"
msgstr "资产"
#: assets/forms/domain.py:54 assets/forms/user.py:79 assets/forms/user.py:139
#: assets/models/base.py:21 assets/models/cluster.py:18
#: assets/models/domain.py:17 assets/models/group.py:20
#: assets/forms/domain.py:55 assets/forms/user.py:79 assets/forms/user.py:139
#: assets/models/base.py:22 assets/models/cluster.py:18
#: assets/models/domain.py:18 assets/models/group.py:20
#: assets/models/label.py:17 assets/templates/assets/admin_user_detail.html:56
#: assets/templates/assets/admin_user_list.html:23
#: assets/templates/assets/domain_detail.html:56
......@@ -132,29 +129,33 @@ msgstr "资产"
#: common/templates/common/terminal_setting.html:72
#: common/templates/common/terminal_setting.html:90 ops/models/adhoc.py:36
#: ops/templates/ops/task_detail.html:59 ops/templates/ops/task_list.html:35
#: perms/models.py:29 perms/templates/perms/asset_permission_detail.html:62
#: orgs/models.py:10 perms/models.py:28
#: perms/templates/perms/asset_permission_detail.html:62
#: perms/templates/perms/asset_permission_list.html:53
#: perms/templates/perms/asset_permission_user.html:54 terminal/models.py:16
#: terminal/models.py:154 terminal/templates/terminal/terminal_detail.html:43
#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:12
#: users/models/user.py:49 users/templates/users/_select_user_modal.html:13
#: perms/templates/perms/asset_permission_user.html:54 terminal/models.py:17
#: terminal/models.py:155 terminal/templates/terminal/terminal_detail.html:43
#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14
#: users/models/user.py:51 users/templates/users/_select_user_modal.html:13
#: users/templates/users/user_detail.html:63
#: users/templates/users/user_group_detail.html:55
#: users/templates/users/user_group_list.html:12
#: users/templates/users/user_list.html:23
#: users/templates/users/user_profile.html:51
#: users/templates/users/user_pubkey_update.html:53
#: xpack/plugins/orgs/templates/orgs/org_detail.html:52
#: xpack/plugins/orgs/templates/orgs/org_list.html:12
#: xpack/templates/orgs/org_list.html:12
msgid "Name"
msgstr "名称"
#: assets/forms/domain.py:55 assets/forms/user.py:80 assets/forms/user.py:140
#: assets/models/base.py:22 assets/templates/assets/admin_user_detail.html:60
#: assets/forms/domain.py:56 assets/forms/user.py:80 assets/forms/user.py:140
#: assets/models/base.py:23 assets/templates/assets/admin_user_detail.html:60
#: assets/templates/assets/admin_user_list.html:24
#: assets/templates/assets/domain_gateway_list.html:60
#: assets/templates/assets/system_user_detail.html:62
#: assets/templates/assets/system_user_list.html:27
#: perms/templates/perms/asset_permission_user.html:55 users/forms.py:13
#: users/forms.py:31 users/models/authentication.py:70 users/models/user.py:47
#: perms/templates/perms/asset_permission_user.html:55 users/forms.py:15
#: users/forms.py:33 users/models/authentication.py:70 users/models/user.py:49
#: users/templates/users/_select_user_modal.html:14
#: users/templates/users/login.html:60
#: users/templates/users/login_log_list.html:49
......@@ -168,8 +169,8 @@ msgstr "用户名"
msgid "Password or private key passphrase"
msgstr "密码或密钥密码"
#: assets/forms/user.py:25 assets/models/base.py:23 common/forms.py:113
#: users/forms.py:15 users/forms.py:33 users/forms.py:45
#: assets/forms/user.py:25 assets/models/base.py:24 common/forms.py:113
#: users/forms.py:17 users/forms.py:35 users/forms.py:47
#: users/templates/users/login.html:63
#: users/templates/users/reset_password.html:53
#: users/templates/users/user_create.html:10
......@@ -180,7 +181,7 @@ msgstr "密码或密钥密码"
msgid "Password"
msgstr "密码"
#: assets/forms/user.py:28 users/models/user.py:76
#: assets/forms/user.py:28 users/models/user.py:78
msgid "Private key"
msgstr "ssh私钥"
......@@ -212,14 +213,15 @@ msgid ""
"password."
msgstr "如果选择手动登录模式,用户名和密码则不需要填写"
#: assets/models/asset.py:74 assets/models/domain.py:43
#: assets/models/asset.py:70 assets/models/domain.py:44
#: assets/templates/assets/_asset_list_modal.html:46
#: assets/templates/assets/admin_user_assets.html:52
#: assets/templates/assets/asset_detail.html:61
#: assets/templates/assets/asset_list.html:87
#: assets/templates/assets/asset_list.html:92
#: assets/templates/assets/domain_gateway_list.html:57
#: assets/templates/assets/system_user_asset.html:50
#: assets/templates/assets/user_asset_list.html:46 common/forms.py:144
#: assets/templates/assets/user_asset_list.html:46
#: assets/templates/assets/user_asset_list.html:182 common/forms.py:144
#: perms/templates/perms/asset_permission_asset.html:55
#: users/templates/users/login_log_list.html:52
#: users/templates/users/user_granted_asset.html:45
......@@ -227,130 +229,136 @@ msgstr "如果选择手动登录模式,用户名和密码则不需要填写"
msgid "IP"
msgstr "IP"
#: assets/models/asset.py:77 assets/templates/assets/_asset_list_modal.html:45
#: assets/models/asset.py:71 assets/templates/assets/_asset_list_modal.html:45
#: assets/templates/assets/admin_user_assets.html:51
#: assets/templates/assets/asset_detail.html:57
#: assets/templates/assets/asset_list.html:86
#: assets/templates/assets/asset_list.html:91
#: assets/templates/assets/system_user_asset.html:49
#: assets/templates/assets/user_asset_list.html:45 common/forms.py:143
#: assets/templates/assets/user_asset_list.html:45
#: assets/templates/assets/user_asset_list.html:181 common/forms.py:143
#: perms/templates/perms/asset_permission_asset.html:54
#: users/templates/users/user_granted_asset.html:44
#: users/templates/users/user_group_granted_asset.html:44
msgid "Hostname"
msgstr "主机名"
#: assets/models/asset.py:80 assets/models/domain.py:45
#: assets/models/user.py:115
#: assets/models/asset.py:72 assets/models/domain.py:46
#: assets/models/user.py:116
#: assets/templates/assets/domain_gateway_list.html:59
#: assets/templates/assets/system_user_detail.html:70
#: assets/templates/assets/system_user_list.html:28
#: assets/templates/assets/user_asset_list.html:184
#: terminal/templates/terminal/session_list.html:75
msgid "Protocol"
msgstr "协议"
#: assets/models/asset.py:83 assets/templates/assets/asset_detail.html:97
#: assets/models/asset.py:74 assets/templates/assets/asset_detail.html:97
#: assets/templates/assets/user_asset_list.html:185
msgid "Platform"
msgstr "系统平台"
#: assets/models/asset.py:90 assets/models/domain.py:48
#: assets/models/asset.py:81 assets/models/domain.py:49
#: assets/models/label.py:20 assets/templates/assets/asset_detail.html:105
#: assets/templates/assets/user_asset_list.html:189
msgid "Is active"
msgstr "激活"
#: assets/models/asset.py:99 assets/templates/assets/asset_detail.html:65
#: assets/models/asset.py:88 assets/templates/assets/asset_detail.html:65
msgid "Public IP"
msgstr "公网IP"
#: assets/models/asset.py:101 assets/templates/assets/asset_detail.html:113
#: assets/models/asset.py:89 assets/templates/assets/asset_detail.html:113
msgid "Asset number"
msgstr "资产编号"
#: assets/models/asset.py:105 assets/templates/assets/asset_detail.html:77
#: assets/models/asset.py:93 assets/templates/assets/asset_detail.html:77
msgid "Vendor"
msgstr "制造商"
#: assets/models/asset.py:107 assets/templates/assets/asset_detail.html:81
#: assets/models/asset.py:95 assets/templates/assets/asset_detail.html:81
msgid "Model"
msgstr "型号"
#: assets/models/asset.py:109 assets/templates/assets/asset_detail.html:109
#: assets/models/asset.py:97 assets/templates/assets/asset_detail.html:109
msgid "Serial number"
msgstr "序列号"
#: assets/models/asset.py:112
#: assets/models/asset.py:100
msgid "CPU model"
msgstr "CPU型号"
#: assets/models/asset.py:113
#: assets/models/asset.py:101
msgid "CPU count"
msgstr "CPU数量"
#: assets/models/asset.py:114
#: assets/models/asset.py:102
msgid "CPU cores"
msgstr "CPU核数"
#: assets/models/asset.py:116 assets/templates/assets/asset_detail.html:89
#: assets/models/asset.py:104 assets/templates/assets/asset_detail.html:89
msgid "Memory"
msgstr "内存"
#: assets/models/asset.py:118
#: assets/models/asset.py:106
msgid "Disk total"
msgstr "硬盘大小"
#: assets/models/asset.py:120
#: assets/models/asset.py:108
msgid "Disk info"
msgstr "硬盘信息"
#: assets/models/asset.py:123 assets/templates/assets/asset_detail.html:101
#: assets/models/asset.py:111 assets/templates/assets/asset_detail.html:101
#: assets/templates/assets/user_asset_list.html:186
msgid "OS"
msgstr "操作系统"
#: assets/models/asset.py:125
#: assets/models/asset.py:113
msgid "OS version"
msgstr "系统版本"
#: assets/models/asset.py:127
#: assets/models/asset.py:115
msgid "OS arch"
msgstr "系统架构"
#: assets/models/asset.py:129
#: assets/models/asset.py:117
msgid "Hostname raw"
msgstr "主机名原始"
#: assets/models/asset.py:133 assets/templates/assets/asset_create.html:34
#: assets/models/asset.py:121 assets/templates/assets/asset_create.html:34
#: assets/templates/assets/asset_detail.html:220
#: assets/templates/assets/asset_update.html:39 templates/_nav.html:27
msgid "Labels"
msgstr "标签管理"
#: assets/models/asset.py:135 assets/models/base.py:29
#: assets/models/asset.py:123 assets/models/base.py:30
#: assets/models/cluster.py:28 assets/models/group.py:21
#: assets/templates/assets/admin_user_detail.html:68
#: assets/templates/assets/asset_detail.html:117
#: assets/templates/assets/domain_detail.html:72
#: assets/templates/assets/system_user_detail.html:100
#: ops/templates/ops/adhoc_detail.html:86 perms/models.py:38 perms/models.py:81
#: perms/templates/perms/asset_permission_detail.html:98
#: users/models/user.py:90 users/templates/users/user_detail.html:111
#: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:13 perms/models.py:37
#: perms/models.py:83 perms/templates/perms/asset_permission_detail.html:98
#: users/models/user.py:92 users/templates/users/user_detail.html:111
msgid "Created by"
msgstr "创建者"
#: assets/models/asset.py:138 assets/models/cluster.py:26
#: assets/models/domain.py:20 assets/models/group.py:22
#: assets/models/asset.py:126 assets/models/cluster.py:26
#: assets/models/domain.py:21 assets/models/group.py:22
#: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:64
#: assets/templates/assets/domain_detail.html:68
#: assets/templates/assets/system_user_detail.html:96
#: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:63
#: perms/models.py:39 perms/models.py:82
#: orgs/models.py:14 perms/models.py:38 perms/models.py:84
#: perms/templates/perms/asset_permission_detail.html:94
#: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:15
#: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:17
#: users/templates/users/user_group_detail.html:63
#: xpack/plugins/orgs/templates/orgs/org_detail.html:60
msgid "Date created"
msgstr "创建日期"
#: assets/models/asset.py:140 assets/models/base.py:26
#: assets/models/cluster.py:29 assets/models/domain.py:18
#: assets/models/domain.py:47 assets/models/group.py:23
#: assets/models/asset.py:128 assets/models/base.py:27
#: assets/models/cluster.py:29 assets/models/domain.py:19
#: assets/models/domain.py:48 assets/models/group.py:23
#: assets/models/label.py:21 assets/templates/assets/admin_user_detail.html:72
#: assets/templates/assets/admin_user_list.html:29
#: assets/templates/assets/asset_detail.html:125
......@@ -358,22 +366,27 @@ msgstr "创建日期"
#: assets/templates/assets/domain_gateway_list.html:61
#: assets/templates/assets/domain_list.html:25
#: assets/templates/assets/system_user_detail.html:104
#: assets/templates/assets/system_user_list.html:34 common/models.py:30
#: ops/models/adhoc.py:42 perms/models.py:40 perms/models.py:83
#: perms/templates/perms/asset_permission_detail.html:102 terminal/models.py:26
#: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:13
#: users/models/user.py:82 users/templates/users/user_detail.html:123
#: assets/templates/assets/system_user_list.html:34
#: assets/templates/assets/user_asset_list.html:190 common/models.py:30
#: ops/models/adhoc.py:42 orgs/models.py:15 perms/models.py:39
#: perms/models.py:85 perms/templates/perms/asset_permission_detail.html:102
#: terminal/models.py:27 terminal/templates/terminal/terminal_detail.html:63
#: users/models/group.py:15 users/models/user.py:84
#: users/templates/users/user_detail.html:123
#: users/templates/users/user_group_detail.html:67
#: users/templates/users/user_group_list.html:14
#: users/templates/users/user_profile.html:130
#: xpack/plugins/orgs/templates/orgs/org_detail.html:64
#: xpack/plugins/orgs/templates/orgs/org_list.html:15
#: xpack/templates/orgs/org_list.html:14
msgid "Comment"
msgstr "备注"
#: assets/models/base.py:24
#: assets/models/base.py:25
msgid "SSH private key"
msgstr "ssh密钥"
#: assets/models/base.py:25
#: assets/models/base.py:26
msgid "SSH public key"
msgstr "ssh公钥"
......@@ -385,7 +398,7 @@ msgstr "带宽"
msgid "Contact"
msgstr "联系人"
#: assets/models/cluster.py:22 users/models/user.py:68
#: assets/models/cluster.py:22 users/models/user.py:70
#: users/templates/users/user_detail.html:76
msgid "Phone"
msgstr "手机"
......@@ -411,7 +424,7 @@ msgid "Default"
msgstr "默认"
#: assets/models/cluster.py:36 assets/models/label.py:13
#: users/models/user.py:345
#: users/models/user.py:360
msgid "System"
msgstr "系统"
......@@ -431,25 +444,29 @@ msgstr "资产组"
msgid "Default asset group"
msgstr "默认资产组"
#: assets/models/label.py:14 audits/models.py:9
#: assets/models/label.py:14 audits/models.py:11
#: audits/templates/audits/ftp_log_list.html:33
#: audits/templates/audits/ftp_log_list.html:71 perms/forms.py:14
#: perms/forms.py:31 perms/models.py:30
#: audits/templates/audits/ftp_log_list.html:71 perms/forms.py:16
#: perms/forms.py:41 perms/models.py:29
#: perms/templates/perms/asset_permission_create_update.html:36
#: perms/templates/perms/asset_permission_list.html:54
#: perms/templates/perms/asset_permission_list.html:133
#: terminal/backends/command/models.py:10 terminal/models.py:126
#: terminal/backends/command/models.py:12 terminal/models.py:127
#: terminal/templates/terminal/command_list.html:32
#: terminal/templates/terminal/command_list.html:72
#: terminal/templates/terminal/session_list.html:33
#: terminal/templates/terminal/session_list.html:71 users/forms.py:282
#: users/models/user.py:31 users/models/user.py:333
#: terminal/templates/terminal/session_list.html:71 users/forms.py:312
#: users/models/user.py:33 users/models/user.py:348
#: users/templates/users/user_group_detail.html:78
#: users/templates/users/user_group_list.html:13 users/views/user.py:367
#: users/templates/users/user_group_list.html:13 users/views/user.py:380
#: xpack/plugins/orgs/forms.py:26
#: xpack/plugins/orgs/templates/orgs/org_detail.html:113
#: xpack/plugins/orgs/templates/orgs/org_list.html:14
#: xpack/templates/orgs/org_list.html:13
msgid "User"
msgstr "用户"
#: assets/models/label.py:18 assets/models/node.py:16
#: assets/models/label.py:18 assets/models/node.py:19
#: assets/templates/assets/label_list.html:15 common/models.py:27
msgid "Value"
msgstr "值"
......@@ -458,19 +475,19 @@ msgstr "值"
msgid "Category"
msgstr "分类"
#: assets/models/node.py:15
#: assets/models/node.py:18
msgid "Key"
msgstr ""
#: assets/models/user.py:108
#: assets/models/user.py:109
msgid "Automatic login"
msgstr "自动登录"
#: assets/models/user.py:109
#: assets/models/user.py:110
msgid "Manually login"
msgstr "手动登录"
#: assets/models/user.py:113
#: assets/models/user.py:114
#: assets/templates/assets/_asset_group_bulk_update_modal.html:11
#: assets/templates/assets/system_user_asset.html:21
#: assets/views/admin_user.py:29 assets/views/admin_user.py:47
......@@ -488,37 +505,37 @@ msgstr "手动登录"
msgid "Assets"
msgstr "资产管理"
#: assets/models/user.py:114
#: assets/models/user.py:115
msgid "Priority"
msgstr "优先级"
#: assets/models/user.py:116 assets/templates/assets/_system_user.html:59
#: assets/models/user.py:117 assets/templates/assets/_system_user.html:59
#: assets/templates/assets/system_user_detail.html:122
#: assets/templates/assets/system_user_update.html:10
msgid "Auto push"
msgstr "自动推送"
#: assets/models/user.py:117 assets/templates/assets/system_user_detail.html:74
#: assets/models/user.py:118 assets/templates/assets/system_user_detail.html:74
msgid "Sudo"
msgstr "Sudo"
#: assets/models/user.py:118 assets/templates/assets/system_user_detail.html:79
#: assets/models/user.py:119 assets/templates/assets/system_user_detail.html:79
msgid "Shell"
msgstr "Shell"
#: assets/models/user.py:119 assets/templates/assets/system_user_detail.html:66
#: assets/models/user.py:120 assets/templates/assets/system_user_detail.html:66
#: assets/templates/assets/system_user_list.html:29
msgid "Login mode"
msgstr "登录模式"
#: assets/models/user.py:159 audits/models.py:12
#: audits/templates/audits/ftp_log_list.html:49
#: audits/templates/audits/ftp_log_list.html:73 perms/forms.py:43
#: perms/models.py:34 perms/models.py:78
#: assets/models/user.py:181 assets/templates/assets/user_asset_list.html:187
#: audits/models.py:14 audits/templates/audits/ftp_log_list.html:49
#: audits/templates/audits/ftp_log_list.html:73 perms/forms.py:53
#: perms/models.py:33 perms/models.py:80
#: perms/templates/perms/asset_permission_detail.html:140
#: perms/templates/perms/asset_permission_list.html:58
#: perms/templates/perms/asset_permission_list.html:145 templates/_nav.html:26
#: terminal/backends/command/models.py:12 terminal/models.py:128
#: terminal/backends/command/models.py:14 terminal/models.py:129
#: terminal/templates/terminal/command_list.html:48
#: terminal/templates/terminal/command_list.html:74
#: terminal/templates/terminal/session_list.html:49
......@@ -674,7 +691,7 @@ msgstr "重置"
#: assets/templates/assets/admin_user_create_update.html:46
#: assets/templates/assets/asset_bulk_update.html:24
#: assets/templates/assets/asset_create.html:68
#: assets/templates/assets/asset_list.html:108
#: assets/templates/assets/asset_list.html:113
#: assets/templates/assets/asset_update.html:72
#: assets/templates/assets/domain_create_update.html:17
#: assets/templates/assets/gateway_create_update.html:59
......@@ -698,6 +715,16 @@ msgstr "重置"
msgid "Submit"
msgstr "提交"
#: assets/templates/assets/_user_asset_detail_modal.html:7
#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:198
msgid "Asset detail"
msgstr "资产详情"
#: assets/templates/assets/_user_asset_detail_modal.html:20
#: templates/_modal.html:21
msgid "Close"
msgstr "关闭"
#: assets/templates/assets/admin_user_assets.html:18
#: assets/templates/assets/admin_user_detail.html:18
#: assets/templates/assets/domain_detail.html:18
......@@ -753,7 +780,7 @@ msgstr "测试"
#: assets/templates/assets/admin_user_detail.html:24
#: assets/templates/assets/admin_user_list.html:85
#: assets/templates/assets/asset_detail.html:24
#: assets/templates/assets/asset_list.html:175
#: assets/templates/assets/asset_list.html:180
#: assets/templates/assets/domain_detail.html:24
#: assets/templates/assets/domain_detail.html:103
#: assets/templates/assets/domain_gateway_list.html:85
......@@ -771,13 +798,16 @@ msgstr "测试"
#: users/templates/users/user_list.html:77
#: users/templates/users/user_profile.html:151
#: users/templates/users/user_profile.html:180
#: xpack/plugins/orgs/templates/orgs/org_detail.html:25
#: xpack/plugins/orgs/templates/orgs/org_list.html:48
#: xpack/templates/orgs/org_list.html:43
msgid "Update"
msgstr "更新"
#: assets/templates/assets/admin_user_detail.html:28
#: assets/templates/assets/admin_user_list.html:86
#: assets/templates/assets/asset_detail.html:28
#: assets/templates/assets/asset_list.html:176
#: assets/templates/assets/asset_list.html:181
#: assets/templates/assets/domain_detail.html:28
#: assets/templates/assets/domain_detail.html:104
#: assets/templates/assets/domain_gateway_list.html:86
......@@ -794,6 +824,9 @@ msgstr "更新"
#: users/templates/users/user_group_list.html:45
#: users/templates/users/user_list.html:81
#: users/templates/users/user_list.html:85
#: xpack/plugins/orgs/templates/orgs/org_detail.html:29
#: xpack/plugins/orgs/templates/orgs/org_list.html:50
#: xpack/templates/orgs/org_list.html:45
msgid "Delete"
msgstr "删除"
......@@ -808,7 +841,7 @@ msgstr "选择节点"
#: assets/templates/assets/admin_user_detail.html:100
#: assets/templates/assets/asset_detail.html:200
#: assets/templates/assets/asset_list.html:638
#: assets/templates/assets/asset_list.html:646
#: assets/templates/assets/system_user_detail.html:195
#: assets/templates/assets/system_user_list.html:139 templates/_modal.html:22
#: terminal/templates/terminal/session_detail.html:108
......@@ -820,6 +853,8 @@ msgstr "选择节点"
#: users/templates/users/user_group_list.html:86
#: users/templates/users/user_list.html:200
#: users/templates/users/user_profile.html:222
#: xpack/plugins/orgs/templates/orgs/org_create_update.html:33
#: xpack/templates/orgs/org_list.html:86
msgid "Confirm"
msgstr "确认"
......@@ -841,7 +876,7 @@ msgid "Ratio"
msgstr "比例"
#: assets/templates/assets/admin_user_list.html:30
#: assets/templates/assets/asset_list.html:91
#: assets/templates/assets/asset_list.html:96
#: assets/templates/assets/domain_gateway_list.html:62
#: assets/templates/assets/domain_list.html:26
#: assets/templates/assets/label_list.html:17
......@@ -853,13 +888,11 @@ msgstr "比例"
#: terminal/templates/terminal/terminal_list.html:36
#: users/templates/users/user_group_list.html:15
#: users/templates/users/user_list.html:29
#: xpack/plugins/orgs/templates/orgs/org_list.html:16
#: xpack/templates/orgs/org_list.html:15
msgid "Action"
msgstr "动作"
#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:198
msgid "Asset detail"
msgstr "资产详情"
#: assets/templates/assets/asset_detail.html:85
msgid "CPU"
msgstr "CPU"
......@@ -882,9 +915,9 @@ msgid "Quick modify"
msgstr "快速修改"
#: assets/templates/assets/asset_detail.html:143
#: assets/templates/assets/asset_list.html:89
#: assets/templates/assets/user_asset_list.html:47 perms/models.py:35
#: perms/models.py:79
#: assets/templates/assets/asset_list.html:94
#: assets/templates/assets/user_asset_list.html:47 perms/models.py:34
#: perms/models.py:81
#: perms/templates/perms/asset_permission_create_update.html:47
#: perms/templates/perms/asset_permission_detail.html:120
#: perms/templates/perms/asset_permission_list.html:59
......@@ -912,120 +945,121 @@ msgstr "刷新"
msgid "Update successfully!"
msgstr "更新成功"
#: assets/templates/assets/asset_list.html:63 assets/views/asset.py:97
#: assets/templates/assets/asset_list.html:68 assets/views/asset.py:97
msgid "Create asset"
msgstr "创建资产"
#: assets/templates/assets/asset_list.html:67
#: assets/templates/assets/asset_list.html:72
#: users/templates/users/user_list.html:7
msgid "Import"
msgstr "导入"
#: assets/templates/assets/asset_list.html:70
#: assets/templates/assets/asset_list.html:75
#: users/templates/users/user_list.html:10
msgid "Export"
msgstr "导出"
#: assets/templates/assets/asset_list.html:88
#: assets/templates/assets/asset_list.html:93
msgid "Hardware"
msgstr "硬件"
#: assets/templates/assets/asset_list.html:100
#: assets/templates/assets/asset_list.html:105
#: users/templates/users/user_list.html:38
msgid "Delete selected"
msgstr "批量删除"
#: assets/templates/assets/asset_list.html:101
#: assets/templates/assets/asset_list.html:106
#: users/templates/users/user_list.html:39
msgid "Update selected"
msgstr "批量更新"
#: assets/templates/assets/asset_list.html:102
#: assets/templates/assets/asset_list.html:107
msgid "Remove from this node"
msgstr "从节点移除"
#: assets/templates/assets/asset_list.html:103
#: assets/templates/assets/asset_list.html:108
#: users/templates/users/user_list.html:40
msgid "Deactive selected"
msgstr "禁用所选"
#: assets/templates/assets/asset_list.html:104
#: assets/templates/assets/asset_list.html:109
#: users/templates/users/user_list.html:41
msgid "Active selected"
msgstr "激活所选"
#: assets/templates/assets/asset_list.html:121
#: assets/templates/assets/asset_list.html:126
msgid "Add node"
msgstr "新建节点"
#: assets/templates/assets/asset_list.html:122
#: assets/templates/assets/asset_list.html:127
msgid "Rename node"
msgstr "重命名节点"
#: assets/templates/assets/asset_list.html:123
#: assets/templates/assets/asset_list.html:128
msgid "Delete node"
msgstr "删除节点"
#: assets/templates/assets/asset_list.html:125
#: assets/templates/assets/asset_list.html:130
msgid "Add assets to node"
msgstr "添加资产到节点"
#: assets/templates/assets/asset_list.html:126
#: assets/templates/assets/asset_list.html:131
msgid "Move assets to node"
msgstr "移动资产到节点"
#: assets/templates/assets/asset_list.html:128
#: assets/templates/assets/asset_list.html:133
msgid "Refresh node hardware info"
msgstr "更新节点资产硬件信息"
#: assets/templates/assets/asset_list.html:129
#: assets/templates/assets/asset_list.html:134
msgid "Test node connective"
msgstr "测试节点资产可连接性"
#: assets/templates/assets/asset_list.html:131
#: assets/templates/assets/asset_list.html:136
msgid "Display only current node assets"
msgstr "仅显示当前节点资产"
#: assets/templates/assets/asset_list.html:132
#: assets/templates/assets/asset_list.html:137
msgid "Displays all child node assets"
msgstr "显示所有子节点资产"
#: assets/templates/assets/asset_list.html:218
#: assets/templates/assets/asset_list.html:223
msgid "Create node failed"
msgstr "创建节点失败"
#: assets/templates/assets/asset_list.html:230
#: assets/templates/assets/asset_list.html:235
msgid "Have child node, cancel"
msgstr "存在子节点,不能删除"
#: assets/templates/assets/asset_list.html:232
#: assets/templates/assets/asset_list.html:237
msgid "Have assets, cancel"
msgstr "存在资产,不能删除"
#: assets/templates/assets/asset_list.html:633
#: assets/templates/assets/asset_list.html:641
#: assets/templates/assets/system_user_list.html:134
#: users/templates/users/user_detail.html:369
#: users/templates/users/user_detail.html:394
#: users/templates/users/user_detail.html:461
#: users/templates/users/user_group_list.html:81
#: users/templates/users/user_list.html:195
#: xpack/templates/orgs/org_list.html:81
msgid "Are you sure?"
msgstr "你确认吗?"
#: assets/templates/assets/asset_list.html:634
#: assets/templates/assets/asset_list.html:642
msgid "This will delete the selected assets !!!"
msgstr "删除选择资产"
#: assets/templates/assets/asset_list.html:642
#: assets/templates/assets/asset_list.html:650
msgid "Asset Deleted."
msgstr "已被删除"
#: assets/templates/assets/asset_list.html:643
#: assets/templates/assets/asset_list.html:648
#: assets/templates/assets/asset_list.html:651
#: assets/templates/assets/asset_list.html:656
msgid "Asset Delete"
msgstr "删除"
#: assets/templates/assets/asset_list.html:647
#: assets/templates/assets/asset_list.html:655
msgid "Asset Deleting failed."
msgstr "删除失败"
......@@ -1219,22 +1253,22 @@ msgstr "资产管理"
msgid "System user asset"
msgstr "系统用户集群资产"
#: audits/models.py:10 audits/templates/audits/ftp_log_list.html:74
#: terminal/models.py:130 terminal/templates/terminal/session_list.html:74
#: audits/models.py:12 audits/templates/audits/ftp_log_list.html:74
#: terminal/models.py:131 terminal/templates/terminal/session_list.html:74
#: terminal/templates/terminal/terminal_detail.html:47
msgid "Remote addr"
msgstr "远端地址"
#: audits/models.py:13 audits/templates/audits/ftp_log_list.html:75
#: audits/models.py:15 audits/templates/audits/ftp_log_list.html:75
msgid "Operate"
msgstr "操作"
#: audits/models.py:14 audits/templates/audits/ftp_log_list.html:56
#: audits/models.py:16 audits/templates/audits/ftp_log_list.html:56
#: audits/templates/audits/ftp_log_list.html:76
msgid "Filename"
msgstr "文件名"
#: audits/models.py:15 audits/templates/audits/ftp_log_list.html:77
#: audits/models.py:17 audits/templates/audits/ftp_log_list.html:77
#: ops/templates/ops/task_list.html:39 users/models/authentication.py:66
#: users/templates/users/user_detail.html:443
msgid "Success"
......@@ -1243,17 +1277,17 @@ msgstr "成功"
#: audits/templates/audits/ftp_log_list.html:78
#: ops/templates/ops/adhoc_history.html:52
#: ops/templates/ops/adhoc_history_detail.html:61
#: ops/templates/ops/task_history.html:58 perms/models.py:36
#: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:137
#: ops/templates/ops/task_history.html:58 perms/models.py:35
#: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:138
#: terminal/templates/terminal/session_list.html:78
msgid "Date start"
msgstr "开始日期"
#: audits/views.py:50 templates/_nav.html:64
#: audits/views.py:51 templates/_nav.html:68
msgid "Audits"
msgstr "日志审计"
#: audits/views.py:51 templates/_nav.html:67
#: audits/views.py:52 templates/_nav.html:71
msgid "FTP log"
msgstr "FTP日志"
......@@ -1401,7 +1435,7 @@ msgid "Public key auth"
msgstr "密钥认证"
#: common/forms.py:159 common/templates/common/terminal_setting.html:68
#: terminal/forms.py:30 terminal/models.py:20
#: terminal/forms.py:30 terminal/models.py:21
msgid "Command storage"
msgstr "命令存储"
......@@ -1412,7 +1446,7 @@ msgid ""
msgstr "设置终端命令存储,default是默认用的存储方式"
#: common/forms.py:165 common/templates/common/terminal_setting.html:86
#: terminal/forms.py:35 terminal/models.py:21
#: terminal/forms.py:35 terminal/models.py:22
msgid "Replay storage"
msgstr "录像存储"
......@@ -1491,11 +1525,11 @@ msgid ""
"characters"
msgstr "开启后,用户密码修改、重置必须包含特殊字符"
#: common/mixins.py:29
#: common/mixins.py:28
msgid "is discard"
msgstr ""
#: common/mixins.py:30
#: common/mixins.py:29
msgid "discard time"
msgstr ""
......@@ -1509,7 +1543,7 @@ msgstr "启用"
#: common/templates/common/ldap_setting.html:15
#: common/templates/common/security_setting.html:15
#: common/templates/common/terminal_setting.html:16
#: common/templates/common/terminal_setting.html:46 common/views.py:22
#: common/templates/common/terminal_setting.html:46 common/views.py:20
msgid "Basic setting"
msgstr "基本设置"
......@@ -1517,7 +1551,7 @@ msgstr "基本设置"
#: common/templates/common/email_setting.html:18
#: common/templates/common/ldap_setting.html:18
#: common/templates/common/security_setting.html:18
#: common/templates/common/terminal_setting.html:20 common/views.py:48
#: common/templates/common/terminal_setting.html:20 common/views.py:46
msgid "Email setting"
msgstr "邮件设置"
......@@ -1525,7 +1559,7 @@ msgstr "邮件设置"
#: common/templates/common/email_setting.html:21
#: common/templates/common/ldap_setting.html:21
#: common/templates/common/security_setting.html:21
#: common/templates/common/terminal_setting.html:24 common/views.py:74
#: common/templates/common/terminal_setting.html:24 common/views.py:72
msgid "LDAP setting"
msgstr "LDAP设置"
......@@ -1533,7 +1567,7 @@ msgstr "LDAP设置"
#: common/templates/common/email_setting.html:24
#: common/templates/common/ldap_setting.html:24
#: common/templates/common/security_setting.html:24
#: common/templates/common/terminal_setting.html:28 common/views.py:104
#: common/templates/common/terminal_setting.html:28 common/views.py:102
msgid "Terminal setting"
msgstr "终端设置"
......@@ -1541,7 +1575,7 @@ msgstr "终端设置"
#: common/templates/common/email_setting.html:27
#: common/templates/common/ldap_setting.html:27
#: common/templates/common/security_setting.html:27
#: common/templates/common/terminal_setting.html:31 common/views.py:132
#: common/templates/common/terminal_setting.html:31 common/views.py:130
msgid "Security setting"
msgstr "安全设置"
......@@ -1563,17 +1597,17 @@ msgstr "类型"
msgid "Special char not allowed"
msgstr "不能包含特殊字符"
#: common/views.py:21 common/views.py:47 common/views.py:73 common/views.py:103
#: common/views.py:131 templates/_nav.html:81
#: common/views.py:19 common/views.py:45 common/views.py:71 common/views.py:101
#: common/views.py:129 templates/_nav.html:98
msgid "Settings"
msgstr "系统设置"
#: common/views.py:32 common/views.py:58 common/views.py:86 common/views.py:116
#: common/views.py:142
#: common/views.py:30 common/views.py:56 common/views.py:84 common/views.py:114
#: common/views.py:140
msgid "Update setting successfully, please restart program"
msgstr "更新设置成功, 请手动重启程序"
#: ops/api.py:79
#: ops/api.py:81
msgid "Waiting ..."
msgstr ""
......@@ -1626,39 +1660,40 @@ msgid "Become"
msgstr "Become"
#: ops/models/adhoc.py:161 users/templates/users/user_group_detail.html:59
#: xpack/plugins/orgs/templates/orgs/org_detail.html:56
msgid "Create by"
msgstr "创建者"
#: ops/models/adhoc.py:323
#: ops/models/adhoc.py:324
msgid "Start time"
msgstr "开始时间"
#: ops/models/adhoc.py:324
#: ops/models/adhoc.py:325
msgid "End time"
msgstr "完成时间"
#: ops/models/adhoc.py:325 ops/templates/ops/adhoc_history.html:57
#: ops/models/adhoc.py:326 ops/templates/ops/adhoc_history.html:57
#: ops/templates/ops/task_history.html:63 ops/templates/ops/task_list.html:41
msgid "Time"
msgstr "时间"
#: ops/models/adhoc.py:326 ops/templates/ops/adhoc_detail.html:106
#: ops/models/adhoc.py:327 ops/templates/ops/adhoc_detail.html:106
#: ops/templates/ops/adhoc_history.html:55
#: ops/templates/ops/adhoc_history_detail.html:69
#: ops/templates/ops/task_detail.html:83 ops/templates/ops/task_history.html:61
msgid "Is finished"
msgstr "是否完成"
#: ops/models/adhoc.py:327 ops/templates/ops/adhoc_history.html:56
#: ops/models/adhoc.py:328 ops/templates/ops/adhoc_history.html:56
#: ops/templates/ops/task_history.html:62
msgid "Is success"
msgstr "是否成功"
#: ops/models/adhoc.py:328
#: ops/models/adhoc.py:329
msgid "Adhoc raw result"
msgstr "结果"
#: ops/models/adhoc.py:329
#: ops/models/adhoc.py:330
msgid "Adhoc result summary"
msgstr "汇总"
......@@ -1738,7 +1773,7 @@ msgid "Run history detail"
msgstr "执行历史详情"
#: ops/templates/ops/adhoc_history_detail.html:22
#: terminal/backends/command/models.py:14
#: terminal/backends/command/models.py:16
msgid "Output"
msgstr "输出"
......@@ -1808,6 +1843,7 @@ msgstr "内容"
#: ops/templates/ops/task_list.html:21 ops/templates/ops/task_list.html:26
#: templates/_base_list.html:43 templates/_header_bar.html:8
#: terminal/templates/terminal/command_list.html:60
#: terminal/templates/terminal/session_list.html:61
#: users/templates/users/login_log_list.html:35
#: users/templates/users/login_log_list.html:40
msgid "Search"
......@@ -1835,7 +1871,7 @@ msgstr "任务开始: "
msgid "Ops"
msgstr "作业中心"
#: ops/views.py:37 templates/_nav.html:59
#: ops/views.py:37 templates/_nav.html:62
msgid "Task list"
msgstr "任务列表"
......@@ -1843,37 +1879,37 @@ msgstr "任务列表"
msgid "Task run history"
msgstr "执行历史"
#: perms/forms.py:18 users/forms.py:239 users/forms.py:244 users/forms.py:256
#: users/forms.py:286
#: perms/forms.py:20 users/forms.py:265 users/forms.py:270 users/forms.py:282
#: users/forms.py:316 xpack/plugins/orgs/forms.py:30
msgid "Select users"
msgstr "选择用户"
#: perms/forms.py:34 perms/models.py:31 perms/models.py:77
#: perms/forms.py:44 perms/models.py:30 perms/models.py:79
#: perms/templates/perms/asset_permission_list.html:55
#: perms/templates/perms/asset_permission_list.html:136 templates/_nav.html:14
#: users/models/group.py:23 users/models/user.py:55
#: users/models/group.py:26 users/models/user.py:57
#: users/templates/users/_select_user_modal.html:16
#: users/templates/users/user_detail.html:200
#: users/templates/users/user_list.html:26
msgid "User group"
msgstr "用户组"
#: perms/forms.py:56
#: perms/forms.py:66
msgid "User or group at least one required"
msgstr ""
#: perms/forms.py:65
#: perms/forms.py:75
msgid "Asset or group at least one required"
msgstr ""
#: perms/models.py:37 perms/models.py:80
#: perms/models.py:36 perms/models.py:82
#: perms/templates/perms/asset_permission_detail.html:90
#: users/models/user.py:87 users/templates/users/user_detail.html:107
#: users/models/user.py:89 users/templates/users/user_detail.html:107
#: users/templates/users/user_profile.html:112
msgid "Date expired"
msgstr "失效日期"
#: perms/models.py:90 templates/_nav.html:34
#: perms/models.py:92 templates/_nav.html:34
msgid "Asset permission"
msgstr "资产授权"
......@@ -1898,6 +1934,8 @@ msgstr "添加资产"
#: perms/templates/perms/asset_permission_user.html:97
#: perms/templates/perms/asset_permission_user.html:125
#: users/templates/users/user_group_detail.html:95
#: xpack/plugins/orgs/templates/orgs/org_detail.html:93
#: xpack/plugins/orgs/templates/orgs/org_detail.html:130
msgid "Add"
msgstr "添加"
......@@ -1963,28 +2001,28 @@ msgstr "添加用户组"
msgid "Select user groups"
msgstr "选择用户组"
#: perms/views.py:25 perms/views.py:55 perms/views.py:70 perms/views.py:85
#: perms/views.py:120 perms/views.py:151 templates/_nav.html:31
#: perms/views.py:23 perms/views.py:53 perms/views.py:68 perms/views.py:83
#: perms/views.py:118 perms/views.py:150 templates/_nav.html:31
msgid "Perms"
msgstr "权限管理"
#: perms/views.py:26
#: perms/views.py:24
msgid "Asset permission list"
msgstr "资产授权列表"
#: perms/views.py:56
#: perms/views.py:54
msgid "Create asset permission"
msgstr "创建权限规则"
#: perms/views.py:71 perms/views.py:86
#: perms/views.py:69 perms/views.py:84
msgid "Update asset permission"
msgstr "更新资产授权"
#: perms/views.py:121
#: perms/views.py:119
msgid "Asset permission user list"
msgstr "资产授权用户列表"
#: perms/views.py:152
#: perms/views.py:151
msgid "Asset permission asset list"
msgstr "资产授权资产列表"
......@@ -1996,14 +2034,14 @@ msgstr "商业支持"
msgid "Docs"
msgstr "文档"
#: templates/_header_bar.html:37 templates/_nav_user.html:9 users/forms.py:122
#: templates/_header_bar.html:37 templates/_nav_user.html:9 users/forms.py:148
#: users/templates/users/_user.html:39
#: users/templates/users/first_login.html:39
#: users/templates/users/user_password_update.html:39
#: users/templates/users/user_profile.html:17
#: users/templates/users/user_profile_update.html:37
#: users/templates/users/user_profile_update.html:57
#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:349
#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:362
msgid "Profile"
msgstr "个人信息"
......@@ -2054,19 +2092,15 @@ msgstr ""
"\"%(user_pubkey_update)s\"> 链接 </a> 更新\n"
" "
#: templates/_modal.html:21
msgid "Close"
msgstr "关闭"
#: templates/_nav.html:10 users/views/group.py:28 users/views/group.py:44
#: users/views/group.py:62 users/views/group.py:79 users/views/group.py:95
#: users/views/login.py:332 users/views/login.py:390 users/views/user.py:67
#: users/views/user.py:82 users/views/user.py:104 users/views/user.py:180
#: users/views/user.py:336 users/views/user.py:386 users/views/user.py:421
#: templates/_nav.html:10 users/views/group.py:27 users/views/group.py:43
#: users/views/group.py:61 users/views/group.py:78 users/views/group.py:94
#: users/views/login.py:333 users/views/login.py:397 users/views/user.py:68
#: users/views/user.py:83 users/views/user.py:111 users/views/user.py:193
#: users/views/user.py:349 users/views/user.py:399 users/views/user.py:434
msgid "Users"
msgstr "用户管理"
#: templates/_nav.html:13 users/views/user.py:68
#: templates/_nav.html:13 users/views/user.py:69
msgid "User list"
msgstr "用户列表"
......@@ -2094,17 +2128,21 @@ msgstr "命令记录"
msgid "Web terminal"
msgstr "Web终端"
#: templates/_nav.html:51 terminal/views/command.py:49
#: templates/_nav.html:52 terminal/views/command.py:50
#: terminal/views/session.py:75 terminal/views/session.py:93
#: terminal/views/session.py:115 terminal/views/terminal.py:31
#: terminal/views/terminal.py:46 terminal/views/terminal.py:58
msgid "Terminal"
msgstr "终端管理"
#: templates/_nav.html:56
#: templates/_nav.html:59
msgid "Job Center"
msgstr "作业中心"
#: templates/_nav.html:86
msgid "XPack"
msgstr ""
#: templates/captcha/image.html:3
msgid "Play CAPTCHA as audio file"
msgstr "语言播放验证码"
......@@ -2117,11 +2155,11 @@ msgstr "验证码"
msgid "Filters"
msgstr "过滤"
#: terminal/backends/command/models.py:13
#: terminal/backends/command/models.py:15
msgid "Input"
msgstr "输入"
#: terminal/backends/command/models.py:15
#: terminal/backends/command/models.py:17
#: terminal/templates/terminal/command_list.html:75
#: terminal/templates/terminal/terminal_list.html:33
msgid "Session"
......@@ -2140,62 +2178,62 @@ msgstr ""
"录像文件支持存储到服务器端硬盘、AWS S3、 阿里云 OSS 中,默认存储到服务器端硬"
"盘, 更多查看文档"
#: terminal/models.py:17
#: terminal/models.py:18
msgid "Remote Address"
msgstr "远端地址"
#: terminal/models.py:18
#: terminal/models.py:19
msgid "SSH Port"
msgstr "SSH端口"
#: terminal/models.py:19
#: terminal/models.py:20
msgid "HTTP Port"
msgstr "HTTP端口"
#: terminal/models.py:98
#: terminal/models.py:99
msgid "Session Online"
msgstr "在线会话"
#: terminal/models.py:99
#: terminal/models.py:100
msgid "CPU Usage"
msgstr "CPU使用"
#: terminal/models.py:100
#: terminal/models.py:101
msgid "Memory Used"
msgstr "内存使用"
#: terminal/models.py:101
#: terminal/models.py:102
msgid "Connections"
msgstr "连接数"
#: terminal/models.py:102
#: terminal/models.py:103
msgid "Threads"
msgstr "线程数"
#: terminal/models.py:103
#: terminal/models.py:104
msgid "Boot Time"
msgstr "运行时间"
#: terminal/models.py:132 terminal/templates/terminal/session_list.html:104
#: terminal/models.py:133 terminal/templates/terminal/session_list.html:104
msgid "Replay"
msgstr "回放"
#: terminal/models.py:133 terminal/templates/terminal/command_list.html:55
#: terminal/models.py:134 terminal/templates/terminal/command_list.html:55
#: terminal/templates/terminal/command_list.html:71
#: terminal/templates/terminal/session_detail.html:48
#: terminal/templates/terminal/session_list.html:77
msgid "Command"
msgstr "命令"
#: terminal/models.py:136
#: terminal/models.py:137
msgid "Date last active"
msgstr "最后活跃日期"
#: terminal/models.py:138
#: terminal/models.py:139
msgid "Date end"
msgstr "结束日期"
#: terminal/models.py:155
#: terminal/models.py:156
msgid "Args"
msgstr "参数"
......@@ -2213,7 +2251,7 @@ msgid "Session detail"
msgstr "会话详情"
#: terminal/templates/terminal/session_detail.html:28
#: terminal/views/command.py:50
#: terminal/views/command.py:51
msgid "Command list"
msgstr "命令记录列表"
......@@ -2329,7 +2367,7 @@ msgid ""
"You should use your ssh client tools connect terminal: {} <br /> <br />{}"
msgstr "你可以使用ssh客户端工具连接终端"
#: users/api.py:226 users/templates/users/login.html:50
#: users/api.py:232 users/templates/users/login.html:50
msgid "Log in frequently and try again later"
msgstr "登录频繁, 稍后重试"
......@@ -2384,11 +2422,11 @@ msgstr ""
msgid "Invalid token or cache refreshed."
msgstr ""
#: users/forms.py:39
#: users/forms.py:41
msgid "MFA code"
msgstr "MFA 验证码"
#: users/forms.py:50 users/models/user.py:59
#: users/forms.py:52 users/models/user.py:61
#: users/templates/users/_select_user_modal.html:15
#: users/templates/users/user_detail.html:87
#: users/templates/users/user_list.html:25
......@@ -2396,31 +2434,31 @@ msgstr "MFA 验证码"
msgid "Role"
msgstr "角色"
#: users/forms.py:53 users/forms.py:202
#: users/forms.py:55 users/forms.py:228
msgid "ssh public key"
msgstr "ssh公钥"
#: users/forms.py:54 users/forms.py:203
#: users/forms.py:56 users/forms.py:229
msgid "ssh-rsa AAAA..."
msgstr ""
#: users/forms.py:55
#: users/forms.py:57
msgid "Paste user id_rsa.pub here."
msgstr "复制用户公钥到这里"
#: users/forms.py:73 users/templates/users/user_detail.html:208
#: users/forms.py:76 users/templates/users/user_detail.html:208
msgid "Join user groups"
msgstr "添加到用户组"
#: users/forms.py:84 users/forms.py:217
#: users/forms.py:110 users/forms.py:243
msgid "Public key should not be the same as your old one."
msgstr "不能和原来的密钥相同"
#: users/forms.py:88 users/forms.py:221 users/serializers.py:48
#: users/forms.py:114 users/forms.py:247 users/serializers.py:48
msgid "Not a valid ssh public key"
msgstr "ssh密钥不合法"
#: users/forms.py:128
#: users/forms.py:154
msgid ""
"Tip: when enabled, you will enter the MFA binding process the next time you "
"log in. you can also directly bind in \"personal information -> quick "
......@@ -2429,17 +2467,17 @@ msgstr ""
"提示:启用之后您将会在下次登录时进入MFA绑定流程;您也可以在(个人信息->快速修"
"改->更改MFA设置)中直接绑定!"
#: users/forms.py:138
#: users/forms.py:164
msgid "* Enable MFA authentication to make the account more secure."
msgstr "* 启用MFA认证,使账号更加安全."
#: users/forms.py:143 users/models/authentication.py:75 users/models/user.py:71
#: users/forms.py:169 users/models/authentication.py:75 users/models/user.py:73
#: users/templates/users/first_login.html:45
#: users/templates/users/login_log_list.html:54
msgid "MFA"
msgstr "MFA"
#: users/forms.py:148
#: users/forms.py:174
msgid ""
"In order to protect you and your company, please keep your account, password "
"and key sensitive information properly. (for example: setting complex "
......@@ -2448,41 +2486,41 @@ msgstr ""
"为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:"
"设置复杂密码,启用MFA认证)"
#: users/forms.py:155 users/templates/users/first_login.html:48
#: users/forms.py:181 users/templates/users/first_login.html:48
#: users/templates/users/first_login.html:107
#: users/templates/users/first_login.html:130
msgid "Finish"
msgstr "完成"
#: users/forms.py:161
#: users/forms.py:187
msgid "Old password"
msgstr "原来密码"
#: users/forms.py:166
#: users/forms.py:192
msgid "New password"
msgstr "新密码"
#: users/forms.py:171
#: users/forms.py:197
msgid "Confirm password"
msgstr "确认密码"
#: users/forms.py:181
#: users/forms.py:207
msgid "Old password error"
msgstr "原来密码错误"
#: users/forms.py:189
#: users/forms.py:215
msgid "Password does not match"
msgstr "密码不一致"
#: users/forms.py:200
#: users/forms.py:226
msgid "Automatically configure and download the SSH key"
msgstr "自动配置并下载SSH密钥"
#: users/forms.py:204
#: users/forms.py:230
msgid "Paste your id_rsa.pub here."
msgstr "复制你的公钥到这里"
#: users/forms.py:232 users/models/user.py:79
#: users/forms.py:258 users/models/user.py:81
#: users/templates/users/first_login.html:42
#: users/templates/users/user_password_update.html:45
#: users/templates/users/user_profile.html:68
......@@ -2545,49 +2583,49 @@ msgstr "状态"
msgid "Date login"
msgstr "登录日期"
#: users/models/user.py:30 users/models/user.py:341
#: users/models/user.py:32 users/models/user.py:356
msgid "Administrator"
msgstr "管理员"
#: users/models/user.py:32
#: users/models/user.py:34
msgid "Application"
msgstr "应用程序"
#: users/models/user.py:35 users/templates/users/user_profile.html:92
#: users/models/user.py:37 users/templates/users/user_profile.html:92
#: users/templates/users/user_profile.html:163
#: users/templates/users/user_profile.html:166
msgid "Disable"
msgstr "禁用"
#: users/models/user.py:36 users/templates/users/user_profile.html:90
#: users/models/user.py:38 users/templates/users/user_profile.html:90
#: users/templates/users/user_profile.html:170
msgid "Enable"
msgstr "启用"
#: users/models/user.py:37 users/templates/users/user_profile.html:88
#: users/models/user.py:39 users/templates/users/user_profile.html:88
msgid "Force enable"
msgstr "强制启用"
#: users/models/user.py:51 users/templates/users/user_detail.html:71
#: users/models/user.py:53 users/templates/users/user_detail.html:71
#: users/templates/users/user_profile.html:59
msgid "Email"
msgstr "邮件"
#: users/models/user.py:62
#: users/models/user.py:64
msgid "Avatar"
msgstr "头像"
#: users/models/user.py:65 users/templates/users/user_detail.html:82
#: users/models/user.py:67 users/templates/users/user_detail.html:82
msgid "Wechat"
msgstr "微信"
#: users/models/user.py:94 users/templates/users/user_detail.html:103
#: users/models/user.py:96 users/templates/users/user_detail.html:103
#: users/templates/users/user_list.html:27
#: users/templates/users/user_profile.html:100
msgid "Source"
msgstr "用户来源"
#: users/models/user.py:344
#: users/models/user.py:359
msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员"
......@@ -2729,8 +2767,44 @@ msgstr "再次输入密码"
msgid "Setting"
msgstr "设置"
#: users/templates/users/reset_password.html:105
#: users/templates/users/user_password_update.html:98
#: users/templates/users/user_update.html:34
msgid "Very weak"
msgstr "很弱"
#: users/templates/users/reset_password.html:106
#: users/templates/users/user_password_update.html:99
#: users/templates/users/user_update.html:35
msgid "Weak"
msgstr "弱"
#: users/templates/users/reset_password.html:107
#: users/templates/users/user_password_update.html:100
#: users/templates/users/user_update.html:36
msgid "Normal"
msgstr "正常"
#: users/templates/users/reset_password.html:108
#: users/templates/users/user_password_update.html:101
#: users/templates/users/user_update.html:37
msgid "Medium"
msgstr "一般"
#: users/templates/users/reset_password.html:109
#: users/templates/users/user_password_update.html:102
#: users/templates/users/user_update.html:38
msgid "Strong"
msgstr "强"
#: users/templates/users/reset_password.html:110
#: users/templates/users/user_password_update.html:103
#: users/templates/users/user_update.html:39
msgid "Very strong"
msgstr "很强"
#: users/templates/users/user_create.html:4
#: users/templates/users/user_list.html:16 users/views/user.py:82
#: users/templates/users/user_list.html:16 users/views/user.py:83
msgid "Create user"
msgstr "创建用户"
......@@ -2739,7 +2813,7 @@ msgid "Reset link will be generated and sent to the user. "
msgstr "生成重置密码连接,通过邮件发送给用户"
#: users/templates/users/user_detail.html:19
#: users/templates/users/user_granted_asset.html:18 users/views/user.py:181
#: users/templates/users/user_granted_asset.html:18 users/views/user.py:194
msgid "User detail"
msgstr "用户详情"
......@@ -2828,37 +2902,44 @@ msgid "After unlocking the user, the user can log in normally."
msgstr "解除用户登录限制后,此用户即可正常登录"
#: users/templates/users/user_group_create_update.html:31
#: xpack/plugins/orgs/templates/orgs/org_create_update.html:32
msgid "Cancel"
msgstr "取消"
#: users/templates/users/user_group_detail.html:22
#: users/templates/users/user_group_granted_asset.html:18
#: users/views/group.py:80
#: users/views/group.py:79
msgid "User group detail"
msgstr "用户组详情"
#: users/templates/users/user_group_detail.html:86
#: xpack/plugins/orgs/templates/orgs/org_detail.html:121
msgid "Add user"
msgstr "添加用户"
#: users/templates/users/user_group_list.html:5 users/views/group.py:45
#: users/templates/users/user_group_list.html:5 users/views/group.py:44
#: xpack/templates/orgs/org_list.html:5
msgid "Create user group"
msgstr "创建用户组"
#: users/templates/users/user_group_list.html:82
#: xpack/templates/orgs/org_list.html:82
msgid "This will delete the selected groups !!!"
msgstr "删除选择组"
#: users/templates/users/user_group_list.html:90
#: xpack/templates/orgs/org_list.html:90
msgid "UserGroups Deleted."
msgstr "用户组删除"
#: users/templates/users/user_group_list.html:91
#: users/templates/users/user_group_list.html:96
#: xpack/templates/orgs/org_list.html:91 xpack/templates/orgs/org_list.html:96
msgid "UserGroups Delete"
msgstr "用户组删除"
#: users/templates/users/user_group_list.html:95
#: xpack/templates/orgs/org_list.html:95
msgid "UserGroup Deleting failed."
msgstr "用户组删除失败"
......@@ -2883,8 +2964,8 @@ msgstr "用户删除失败"
msgid "Administrator Settings force MFA login"
msgstr "管理员设置强制使用MFA登录"
#: users/templates/users/user_profile.html:116 users/views/user.py:211
#: users/views/user.py:265
#: users/templates/users/user_profile.html:116 users/views/user.py:224
#: users/views/user.py:278
msgid "User groups"
msgstr "用户组"
......@@ -2930,7 +3011,7 @@ msgid ""
"corresponding private key."
msgstr "新的公钥已设置成功,请下载对应的私钥"
#: users/templates/users/user_update.html:4 users/views/user.py:105
#: users/templates/users/user_update.html:4 users/views/user.py:112
msgid "Update user"
msgstr "更新用户"
......@@ -3072,119 +3153,170 @@ msgstr "密码或密钥不合法"
msgid "Bit"
msgstr " 位"
#: users/views/group.py:29
#: users/views/group.py:28
msgid "User group list"
msgstr "用户组列表"
#: users/views/group.py:63
#: users/views/group.py:62
msgid "Update user group"
msgstr "更新用户组"
#: users/views/group.py:96
#: users/views/group.py:95
msgid "User group granted asset"
msgstr "用户组授权资产"
#: users/views/login.py:76
#: users/views/login.py:77
msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie"
#: users/views/login.py:180 users/views/user.py:506 users/views/user.py:531
#: users/views/login.py:181 users/views/user.py:519 users/views/user.py:544
msgid "MFA code invalid"
msgstr "MFA码认证失败"
#: users/views/login.py:209
#: users/views/login.py:210
msgid "Logout success"
msgstr "退出登录成功"
#: users/views/login.py:210
#: users/views/login.py:211
msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面"
#: users/views/login.py:226
#: users/views/login.py:227
msgid "Email address invalid, please input again"
msgstr "邮箱地址错误,重新输入"
#: users/views/login.py:239
#: users/views/login.py:240
msgid "Send reset password message"
msgstr "发送重置密码邮件"
#: users/views/login.py:240
#: users/views/login.py:241
msgid "Send reset password mail success, login your mail box and follow it "
msgstr ""
"发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)"
#: users/views/login.py:253
#: users/views/login.py:254
msgid "Reset password success"
msgstr "重置密码成功"
#: users/views/login.py:254
#: users/views/login.py:255
msgid "Reset password success, return to login page"
msgstr "重置密码成功,返回到登录页面"
#: users/views/login.py:275 users/views/login.py:288
#: users/views/login.py:276 users/views/login.py:289
msgid "Token invalid or expired"
msgstr "Token错误或失效"
#: users/views/login.py:284
#: users/views/login.py:285
msgid "Password not same"
msgstr "密码不一致"
#: users/views/login.py:294 users/views/user.py:120 users/views/user.py:404
#: users/views/login.py:295 users/views/user.py:127 users/views/user.py:417
msgid "* Your password does not meet the requirements"
msgstr "* 您的密码不符合要求"
#: users/views/login.py:332
#: users/views/login.py:333
msgid "First login"
msgstr "首次登陆"
#: users/views/login.py:391
#: users/views/login.py:398
msgid "Login log list"
msgstr "登录日志"
#: users/views/user.py:131
#: users/views/user.py:144
msgid "Bulk update user success"
msgstr "批量更新用户成功"
#: users/views/user.py:240
#: users/views/user.py:253
msgid "Invalid file."
msgstr "文件不合法"
#: users/views/user.py:337
#: users/views/user.py:350
msgid "User granted assets"
msgstr "用户授权资产"
#: users/views/user.py:368
#: users/views/user.py:381
msgid "Profile setting"
msgstr "个人信息设置"
#: users/views/user.py:387
#: users/views/user.py:400
msgid "Password update"
msgstr "密码更新"
#: users/views/user.py:422
#: users/views/user.py:435
msgid "Public key update"
msgstr "密钥更新"
#: users/views/user.py:463
#: users/views/user.py:476
msgid "Password invalid"
msgstr "用户名或密码无效"
#: users/views/user.py:557
#: users/views/user.py:570
msgid "MFA enable success"
msgstr "MFA 绑定成功"
#: users/views/user.py:558
#: users/views/user.py:571
msgid "MFA enable success, return login page"
msgstr "MFA 绑定成功,返回到登录页面"
#: users/views/user.py:560
#: users/views/user.py:573
msgid "MFA disable success"
msgstr "MFA 解绑成功"
#: users/views/user.py:561
#: users/views/user.py:574
msgid "MFA disable success, return login page"
msgstr "MFA 解绑成功,返回登录页面"
#: xpack/plugins/orgs/forms.py:14
#: xpack/plugins/orgs/templates/orgs/org_detail.html:76
#: xpack/plugins/orgs/templates/orgs/org_list.html:13
msgid "Admin"
msgstr "管理员"
#: xpack/plugins/orgs/forms.py:18
msgid "Select admins"
msgstr "选择管理员"
#: xpack/plugins/orgs/meta.py:7
msgid "Organization"
msgstr "组织管理"
#: xpack/plugins/orgs/templates/orgs/org_detail.html:22
#: xpack/plugins/orgs/views.py:73
msgid "Org detail"
msgstr "组织详情"
#: xpack/plugins/orgs/templates/orgs/org_detail.html:84
msgid "Add admin"
msgstr "添加管理员"
#: xpack/plugins/orgs/templates/orgs/org_list.html:5
msgid "Create organization "
msgstr "创建组织"
#: xpack/plugins/orgs/views.py:24
msgid "Org"
msgstr "组织"
#: xpack/plugins/orgs/views.py:25
msgid "Org list"
msgstr "组织列表"
#: xpack/plugins/orgs/views.py:40 xpack/plugins/orgs/views.py:56
#: xpack/plugins/orgs/views.py:72
msgid "Orgs"
msgstr "组织"
#: xpack/plugins/orgs/views.py:41
msgid "Create org"
msgstr "创建组织"
#: xpack/plugins/orgs/views.py:57
msgid "Update org"
msgstr "更新组织"
#~ msgid "* required Must set exact system platform, Windows, Linux ..."
#~ msgstr "* required 必须准确设置操作系统平台,如Windows, Linux ..."
#~ msgid "Unblock user successfully. "
#~ msgstr "解除登录限制成功"
......
......@@ -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'
}
},
}
\ No newline at end of file
# ~*~ 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])
schema_view = get_schema_view(
openapi.Info(
title="Jumpserver API Docs",
default_version='v1',
description="Jumpserver Restful api docs",
terms_of_service="https://www.jumpserver.org",
contact=openapi.Contact(email="support@fit2cloud.com"),
license=openapi.License(name="GPLv2 License"),
),
public=True,
permission_classes=(permissions.AllowAny,),
)
api_url_pattern = re.compile(r'^/api/(?P<version>\w+)/(?P<app>\w+)/(?P<extra>.*)$')
class HttpResponseTemporaryRedirect(HttpResponse):
status_code = 307
def __init__(self, redirect_to):
HttpResponse.__init__(self)
self['Location'] = iri_to_uri(redirect_to)
@csrf_exempt
def redirect_format_api(request, *args, **kwargs):
_path, query = request.path, request.GET.urlencode()
matched = api_url_pattern.match(_path)
if matched:
version, app, extra = matched.groups()
_path = '/api/{app}/{version}/{extra}?{query}'.format(**{
"app": app, "version": version, "extra": extra,
"query": query
})
return HttpResponseTemporaryRedirect(_path)
else:
return Response({"msg": "Redirect url failed: {}".format(_path)}, status=404)
v1_api_patterns = [
path('users/v1/', include('users.urls.api_urls', namespace='api-users')),
path('assets/v1/', include('assets.urls.api_urls', namespace='api-assets')),
path('perms/v1/', include('perms.urls.api_urls', namespace='api-perms')),
path('terminal/v1/', include('terminal.urls.api_urls', namespace='api-terminal')),
path('ops/v1/', include('ops.urls.api_urls', namespace='api-ops')),
path('audits/v1/', include('audits.urls.api_urls', namespace='api-audits')),
path('orgs/v1/', include('orgs.urls.api_urls', namespace='api-orgs')),
path('common/v1/', include('common.urls.api_urls', namespace='api-common')),
]
app_view_patterns = [
path('users/', include('users.urls.views_urls', namespace='users')),
path('assets/', include('assets.urls.views_urls', namespace='assets')),
path('perms/', include('perms.urls.views_urls', namespace='perms')),
path('terminal/', include('terminal.urls.views_urls', namespace='terminal')),
path('ops/', include('ops.urls.view_urls', namespace='ops')),
path('audits/', include('audits.urls.view_urls', namespace='audits')),
path('orgs/', include('orgs.urls.views_urls', namespace='orgs')),
]
if settings.XPACK_ENABLED:
app_view_patterns.append(path('xpack/', include('xpack.urls', namespace='xpack')))
urlpatterns = [
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')),
# 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')),
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
url(r'^captcha/', include('captcha.urls')),
path('captcha/', include('captcha.urls')),
]
urlpatterns += app_view_patterns
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \
+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
if settings.DEBUG:
urlpatterns += [
url(r'^docs/', schema_view, name="docs"),
re_path('swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=None), name='schema-json'),
path('docs/', schema_view.with_ui('swagger', cache_timeout=None), name="docs"),
path('redoc/', schema_view.with_ui('redoc', cache_timeout=None), name='redoc'),
]
......@@ -4,12 +4,13 @@ from django.http import HttpResponse
from django.views.generic import TemplateView, View
from django.utils import timezone
from django.db.models import Count
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import redirect
from django.contrib.auth.mixins import LoginRequiredMixin
from users.models import User
from assets.models import Asset
from terminal.models import Session
from orgs.utils import current_org
class IndexView(LoginRequiredMixin, TemplateView):
......@@ -20,14 +21,16 @@ class IndexView(LoginRequiredMixin, TemplateView):
session_month_dates = []
session_month_dates_archive = []
def get(self, request, *args, **kwargs):
if not request.user.is_superuser:
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return self.handle_no_permission()
if not request.user.is_org_admin:
return redirect('assets:user-asset-list')
return super(IndexView, self).get(request, *args, **kwargs)
return super(IndexView, self).dispatch(request, *args, **kwargs)
@staticmethod
def get_user_count():
return User.objects.filter(role__in=('Admin', 'User')).count()
return current_org.get_org_users().count()
@staticmethod
def get_asset_count():
......@@ -49,7 +52,6 @@ class IndexView(LoginRequiredMixin, TemplateView):
def get_week_login_asset_count(self):
return self.session_week.count()
# return self.session_week.values('asset').distinct().count()
def get_month_day_metrics(self):
month_str = [d.strftime('%m-%d') for d in self.session_month_dates] or ['0']
......@@ -175,4 +177,7 @@ class LunaView(View):
Luna是单独部署的一个程序,你需要部署luna,coco,配置nginx做url分发,
如果你看到了这个页面,证明你访问的不是nginx监听的端口,祝你好运
"""
return HttpResponse(msg)
\ No newline at end of file
return HttpResponse(msg)
......@@ -8,7 +8,7 @@ from django.utils.translation import ugettext as _
from rest_framework import viewsets, generics
from rest_framework.views import Response
from .hands import IsSuperUser
from common.permissions import IsOrgAdmin
from .models import Task, AdHoc, AdHocRunHistory, CeleryTask
from .serializers import TaskSerializer, AdHocSerializer, \
AdHocRunHistorySerializer
......@@ -18,13 +18,15 @@ from .tasks import run_ansible_task
class TaskViewSet(viewsets.ModelViewSet):
queryset = Task.objects.all()
serializer_class = TaskSerializer
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
label = None
help_text = ''
class TaskRun(generics.RetrieveAPIView):
queryset = Task.objects.all()
serializer_class = TaskViewSet
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs):
task = self.get_object()
......@@ -35,7 +37,7 @@ class TaskRun(generics.RetrieveAPIView):
class AdHocViewSet(viewsets.ModelViewSet):
queryset = AdHoc.objects.all()
serializer_class = AdHocSerializer
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
def get_queryset(self):
task_id = self.request.query_params.get('task')
......@@ -48,7 +50,7 @@ class AdHocViewSet(viewsets.ModelViewSet):
class AdHocRunHistorySet(viewsets.ModelViewSet):
queryset = AdHocRunHistory.objects.all()
serializer_class = AdHocRunHistorySerializer
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
def get_queryset(self):
task_id = self.request.query_params.get('task')
......@@ -65,7 +67,7 @@ class AdHocRunHistorySet(viewsets.ModelViewSet):
class CeleryTaskLogApi(generics.RetrieveAPIView):
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
buff_size = 1024 * 10
end = False
queryset = CeleryTask.objects.all()
......
......@@ -7,5 +7,9 @@ class OpsConfig(AppConfig):
name = 'ops'
def ready(self):
from orgs.models import Organization
from orgs.utils import set_current_org
set_current_org(Organization.root())
super().ready()
from .celery import signal_handler
# ~*~ coding: utf-8 ~*~
from users.permissions import IsSuperUser
from users.utils import AdminUserRequiredMixin
\ No newline at end of file
......@@ -263,7 +263,8 @@ class AdHoc(models.Model):
}
: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):
......
# ~*~ 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
# ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals
from django.urls import path
from django.conf.urls import url
from .. import views
__all__ = ["urlpatterns"]
......@@ -10,13 +9,13 @@ __all__ = ["urlpatterns"]
app_name = "ops"
urlpatterns = [
# TResource Task url
url(r'^task/$', views.TaskListView.as_view(), name='task-list'),
url(r'^task/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.TaskDetailView.as_view(), name='task-detail'),
url(r'^task/(?P<pk>[0-9a-zA-Z\-]{36})/adhoc/$', views.TaskAdhocView.as_view(), name='task-adhoc'),
url(r'^task/(?P<pk>[0-9a-zA-Z\-]{36})/history/$', views.TaskHistoryView.as_view(), name='task-history'),
url(r'^adhoc/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.AdHocDetailView.as_view(), name='adhoc-detail'),
url(r'^adhoc/(?P<pk>[0-9a-zA-Z\-]{36})/history/$', views.AdHocHistoryView.as_view(), name='adhoc-history'),
url(r'^adhoc/history/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.AdHocHistoryDetailView.as_view(), name='adhoc-history-detail'),
url(r'^celery/task/(?P<pk>[0-9a-zA-Z\-]{36})/log/$', views.CeleryTaskLogView.as_view(), name='celery-task-log'),
# Resource Task url
path('task/', views.TaskListView.as_view(), name='task-list'),
path('task/<uuid:pk>/', views.TaskDetailView.as_view(), name='task-detail'),
path('task/<uuid:pk>/adhoc/', views.TaskAdhocView.as_view(), name='task-adhoc'),
path('task/<uuid:pk>/history/', views.TaskHistoryView.as_view(), name='task-history'),
path('adhoc/<uuid:pk>/', views.AdHocDetailView.as_view(), name='adhoc-detail'),
path('adhoc/<uuid:pk>/history/', views.AdHocHistoryView.as_view(), name='adhoc-history'),
path('adhoc/history/<uuid:pk>/', views.AdHocHistoryDetailView.as_view(), name='adhoc-history-detail'),
path('celery/task/<uuid:pk>/log/', views.CeleryTaskLogView.as_view(), name='celery-task-log'),
]
......@@ -6,7 +6,7 @@ from django.views.generic import ListView, DetailView, TemplateView
from common.mixins import DatetimeSearchMixin
from .models import Task, AdHoc, AdHocRunHistory, CeleryTask
from .hands import AdminUserRequiredMixin
from common.permissions import AdminUserRequiredMixin
class TaskListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
......
from django.contrib import admin
# Register your models here.
# -*- coding: utf-8 -*-
#
from rest_framework import viewsets
from common.permissions import IsSuperUserOrAppUser
from .models import Organization
from .serializers import OrgSerializer
class OrgViewSet(viewsets.ModelViewSet):
queryset = Organization.objects.all()
serializer_class = OrgSerializer
permission_classes = (IsSuperUserOrAppUser,)
from django.apps import AppConfig
class OrgsConfig(AppConfig):
name = 'orgs'
# -*- coding: utf-8 -*-
#
from .utils import current_org, get_current_org
from .models import Organization
def org_processor(request):
context = {
'ADMIN_ORGS': Organization.get_user_admin_orgs(request.user),
'CURRENT_ORG': get_current_org(),
'HAS_ORG_PERM': current_org.can_admin_by(request.user),
}
return context
# -*- coding: utf-8 -*-
#
from .utils import get_org_from_request, set_current_org
class OrgMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
org = get_org_from_request(request)
request.current_org = org
set_current_org(org)
response = self.get_response(request)
return response
# -*- coding: utf-8 -*-
#
from threading import local
from django.db import models
from django.db.models import Q
from django.shortcuts import redirect
import warnings
from django.forms import ModelForm
from django.http.response import HttpResponseForbidden
from common.utils import get_logger
from .utils import current_org, set_current_org, set_to_root_org
from .models import Organization
logger = get_logger(__file__)
tl = local()
__all__ = [
'OrgManager', 'OrgViewGenericMixin', 'OrgModelMixin', 'OrgModelForm',
'RootOrgViewMixin',
]
class OrgManager(models.Manager):
def get_queryset(self):
queryset = super(OrgManager, self).get_queryset()
kwargs = {}
if not hasattr(tl, 'times'):
tl.times = 0
# logger.debug("[{}]>>>>>>>>>> Get query set".format(tl.times))
if not current_org:
kwargs['id'] = None
elif current_org.is_real():
kwargs['org_id'] = current_org.id
elif current_org.is_default():
queryset = queryset.filter(Q(org_id="") | Q(org_id__isnull=True))
queryset = queryset.filter(**kwargs)
tl.times += 1
return queryset
def all(self):
if not current_org:
msg = 'You can `objects.set_current_org(org).all()` then run it'
warnings.warn(msg)
return self
else:
return super(OrgManager, self).all()
def set_current_org(self, org):
if isinstance(org, str):
org = Organization.objects.get(name=org)
set_current_org(org)
return self
class OrgModelMixin(models.Model):
org_id = models.CharField(max_length=36, null=True, blank=True)
objects = OrgManager()
def save(self, *args, **kwargs):
if current_org and current_org.is_real():
self.org_id = current_org.id
return super().save(*args, **kwargs)
class Meta:
abstract = True
class OrgViewGenericMixin:
def dispatch(self, request, *args, **kwargs):
print("Current org: {}".format(current_org))
if not current_org:
return redirect('orgs:switch-a-org')
if not current_org.can_admin_by(request.user):
print("{} cannot admin {}".format(request.user, current_org))
if request.user.is_org_admin:
print("Is org admin")
return redirect('orgs:switch-a-org')
return HttpResponseForbidden()
else:
print(current_org.can_admin_by(request.user))
return super().dispatch(request, *args, **kwargs)
class RootOrgViewMixin:
def dispatch(self, request, *args, **kwargs):
set_to_root_org()
return super().dispatch(request, *args, **kwargs)
class OrgModelForm(ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if 'initial' not in kwargs:
return
for name, field in self.fields.items():
if not hasattr(field, 'queryset'):
continue
model = field.queryset.model
field.queryset = model.objects.all()
import uuid
from django.db import models
from django.core.cache import cache
from django.utils.translation import ugettext_lazy as _
class Organization(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, unique=True, verbose_name=_("Name"))
users = models.ManyToManyField('users.User', related_name='orgs', blank=True)
admins = models.ManyToManyField('users.User', related_name='admin_orgs', blank=True)
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
CACHE_PREFIX = 'JMS_ORG_{}'
ROOT_ID = 'ROOT'
DEFAULT_ID = 'DEFAULT'
def __str__(self):
return self.name
def set_to_cache(self):
key = self.CACHE_PREFIX.format(self.id)
cache.set(key, self, 3600)
def expire_cache(self):
key = self.CACHE_PREFIX.format(self.id)
cache.set(key, self, 0)
@classmethod
def get_instance_from_cache(cls, oid):
key = cls.CACHE_PREFIX.format(oid)
return cache.get(key, None)
@classmethod
def get_instance(cls, oid, default=True):
cached = cls.get_instance_from_cache(oid)
if cached:
return cached
if oid == cls.DEFAULT_ID:
return cls.default()
elif oid == cls.ROOT_ID:
return cls.root()
try:
org = cls.objects.get(id=oid)
org.set_to_cache()
except cls.DoesNotExist:
org = cls.default() if default else None
return org
def get_org_users(self):
from users.models import User
if self.is_default():
users = User.objects.filter(orgs__isnull=True)
else:
users = self.users.all()
users = users.exclude(role=User.ROLE_APP)
return users
def get_org_admins(self):
if self.is_real():
return self.admins.all()
return []
def can_admin_by(self, user):
if user.is_superuser:
return True
if user in list(self.get_org_admins()):
return True
return False
def is_real(self):
return len(str(self.id)) == 36
@classmethod
def get_user_admin_orgs(cls, user):
admin_orgs = []
if user.is_anonymous:
return admin_orgs
elif user.is_superuser:
admin_orgs = list(cls.objects.all())
admin_orgs.append(cls.default())
elif user.is_org_admin:
admin_orgs = user.admin_orgs.all()
return admin_orgs
@classmethod
def default(cls):
return cls(id=cls.DEFAULT_ID, name="Default")
@classmethod
def root(cls):
return cls(id=cls.ROOT_ID, name='Root')
def is_default(self):
if self.id is self.DEFAULT_ID:
return True
else:
return False
from rest_framework.serializers import ModelSerializer
from .models import Organization
class OrgSerializer(ModelSerializer):
class Meta:
model = Organization
fields = '__all__'
read_only_fields = ['id', 'created_by', 'date_created']
from django.test import TestCase
# Create your tests here.
# -*- coding: utf-8 -*-
#
# -*- coding: utf-8 -*-
#
from rest_framework.routers import DefaultRouter
from .. import api
app_name = 'orgs'
router = DefaultRouter()
router.register(r'orgs', api.OrgViewSet, 'org')
urlpatterns = [
]
urlpatterns += router.urls
# -*- coding: utf-8 -*-
#
from django.urls import path
from .. import views
app_name = 'orgs'
urlpatterns = [
path('<str:pk>/switch/', views.SwitchOrgView.as_view(), name='org-switch'),
path('switch-a-org/', views.SwitchToAOrgView.as_view(), name='switch-a-org')
]
# -*- coding: utf-8 -*-
#
from functools import partial
from common.utils import LocalProxy
from .models import Organization
try:
from threading import local
except ImportError:
from django.utils._threading_local import local
_thread_locals = local()
def get_org_from_request(request):
oid = request.session.get("oid")
if not oid:
oid = request.META.get("HTTP_X_JMS_ORG")
org = Organization.get_instance(oid)
return org
def set_current_org(org):
setattr(_thread_locals, 'current_org', org)
def set_to_default_org():
set_current_org(Organization.default())
def set_to_root_org():
set_current_org(Organization.root())
def _find(attr):
return getattr(_thread_locals, attr, None)
def get_current_org():
return _find('current_org')
current_org = LocalProxy(partial(_find, 'current_org'))
current_user = LocalProxy(partial(_find, 'current_user'))
current_request = LocalProxy(partial(_find, 'current_request'))
from django.shortcuts import redirect, reverse
from django.http import HttpResponseForbidden
from django.views.generic import DetailView, View
from .models import Organization
class SwitchOrgView(DetailView):
model = Organization
object = None
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
self.object = Organization.get_instance(pk)
request.session['oid'] = self.object.id.__str__()
return redirect('index')
class SwitchToAOrgView(View):
def get(self, request, *args, **kwargs):
admin_orgs = Organization.get_user_admin_orgs(request.user)
if not admin_orgs:
return HttpResponseForbidden()
default_org = Organization.default()
if default_org in admin_orgs:
redirect_org = default_org
else:
redirect_org = admin_orgs[0]
return redirect(reverse('orgs:org-switch', kwargs={'pk': redirect_org.id}))
......@@ -7,7 +7,8 @@ from rest_framework.generics import ListAPIView, get_object_or_404, RetrieveUpda
from rest_framework import viewsets
from common.utils import set_or_append_attr_bulk, get_object_or_none
from users.permissions import IsValidUser, IsSuperUser, IsSuperUserOrAppUser
from common.permissions import IsValidUser, IsOrgAdmin, IsOrgAdminOrAppUser
from orgs.mixins import RootOrgViewMixin
from .utils import AssetPermissionUtil
from .models import AssetPermission
from .hands import AssetGrantedSerializer, User, UserGroup, Asset, Node, \
......@@ -21,7 +22,7 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
"""
queryset = AssetPermission.objects.all()
serializer_class = serializers.AssetPermissionCreateUpdateSerializer
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
def get_serializer_class(self):
if self.action in ("list", 'retrieve'):
......@@ -54,11 +55,11 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
return permissions
class UserGrantedAssetsApi(ListAPIView):
class UserGrantedAssetsApi(RootOrgViewMixin, ListAPIView):
"""
用户授权的所有资产
"""
permission_classes = (IsSuperUserOrAppUser,)
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = AssetGrantedSerializer
def get_queryset(self):
......@@ -83,8 +84,8 @@ class UserGrantedAssetsApi(ListAPIView):
return super().get_permissions()
class UserGrantedNodesApi(ListAPIView):
permission_classes = (IsSuperUser,)
class UserGrantedNodesApi(RootOrgViewMixin, ListAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = NodeSerializer
def get_queryset(self):
......@@ -103,8 +104,8 @@ class UserGrantedNodesApi(ListAPIView):
return super().get_permissions()
class UserGrantedNodesWithAssetsApi(ListAPIView):
permission_classes = (IsSuperUserOrAppUser,)
class UserGrantedNodesWithAssetsApi(RootOrgViewMixin, ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = NodeGrantedSerializer
def get_queryset(self):
......@@ -132,8 +133,8 @@ class UserGrantedNodesWithAssetsApi(ListAPIView):
return super().get_permissions()
class UserGrantedNodeAssetsApi(ListAPIView):
permission_classes = (IsSuperUserOrAppUser,)
class UserGrantedNodeAssetsApi(RootOrgViewMixin, ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = AssetGrantedSerializer
def get_queryset(self):
......@@ -159,7 +160,7 @@ class UserGrantedNodeAssetsApi(ListAPIView):
class UserGroupGrantedAssetsApi(ListAPIView):
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
serializer_class = AssetGrantedSerializer
def get_queryset(self):
......@@ -179,7 +180,7 @@ class UserGroupGrantedAssetsApi(ListAPIView):
class UserGroupGrantedNodesApi(ListAPIView):
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
serializer_class = NodeSerializer
def get_queryset(self):
......@@ -195,7 +196,7 @@ class UserGroupGrantedNodesApi(ListAPIView):
class UserGroupGrantedNodesWithAssetsApi(ListAPIView):
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
serializer_class = NodeGrantedSerializer
def get_queryset(self):
......@@ -218,7 +219,7 @@ class UserGroupGrantedNodesWithAssetsApi(ListAPIView):
class UserGroupGrantedNodeAssetsApi(ListAPIView):
permission_classes = (IsSuperUserOrAppUser,)
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = AssetGrantedSerializer
def get_queryset(self):
......@@ -235,8 +236,8 @@ class UserGroupGrantedNodeAssetsApi(ListAPIView):
return assets
class ValidateUserAssetPermissionView(APIView):
permission_classes = (IsSuperUserOrAppUser,)
class ValidateUserAssetPermissionView(RootOrgViewMixin, APIView):
permission_classes = (IsOrgAdminOrAppUser,)
@staticmethod
def get(request):
......@@ -260,7 +261,7 @@ class AssetPermissionRemoveUserApi(RetrieveUpdateAPIView):
"""
将用户从授权中移除,Detail页面会调用
"""
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetPermissionUpdateUserSerializer
queryset = AssetPermission.objects.all()
......@@ -277,7 +278,7 @@ class AssetPermissionRemoveUserApi(RetrieveUpdateAPIView):
class AssetPermissionAddUserApi(RetrieveUpdateAPIView):
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetPermissionUpdateUserSerializer
queryset = AssetPermission.objects.all()
......@@ -297,7 +298,7 @@ class AssetPermissionRemoveAssetApi(RetrieveUpdateAPIView):
"""
将用户从授权中移除,Detail页面会调用
"""
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetPermissionUpdateAssetSerializer
queryset = AssetPermission.objects.all()
......@@ -314,7 +315,7 @@ class AssetPermissionRemoveAssetApi(RetrieveUpdateAPIView):
class AssetPermissionAddAssetApi(RetrieveUpdateAPIView):
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetPermissionUpdateAssetSerializer
queryset = AssetPermission.objects.all()
......
......@@ -4,11 +4,13 @@ from __future__ import absolute_import, unicode_literals
from django import forms
from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelForm
from orgs.utils import current_org
from .hands import User
from .models import AssetPermission
class AssetPermissionForm(forms.ModelForm):
class AssetPermissionForm(OrgModelForm):
users = forms.ModelMultipleChoiceField(
queryset=User.objects.exclude(role=User.ROLE_APP),
label=_("User"),
......@@ -21,10 +23,18 @@ class AssetPermissionForm(forms.ModelForm):
required=False,
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if 'initial' not in kwargs:
return
users_field = self.fields.get('users')
if hasattr(users_field, 'queryset'):
users_field.queryset = current_org.get_org_users()
class Meta:
model = AssetPermission
exclude = (
'id', 'date_created', 'created_by'
'id', 'date_created', 'created_by', 'org_id'
)
widgets = {
'users': forms.SelectMultiple(
......
# ~*~ coding: utf-8 ~*~
#
from users.utils import AdminUserRequiredMixin
from common.permissions import AdminUserRequiredMixin
from users.models import User, UserGroup
from assets.models import Asset, SystemUser, Node
from assets.serializers import AssetGrantedSerializer, NodeGrantedSerializer, NodeSerializer
......
......@@ -6,6 +6,8 @@ from django.utils import timezone
from common.utils import date_expired_default, set_or_append_attr_bulk
from orgs.mixins import OrgModelMixin, OrgManager
class AssetPermissionQuerySet(models.QuerySet):
def active(self):
......@@ -16,17 +18,14 @@ class AssetPermissionQuerySet(models.QuerySet):
.filter(date_expired__gt=timezone.now())
class AssetPermissionManager(models.Manager):
def get_queryset(self):
return AssetPermissionQuerySet(self.model, using=self._db)
class AssetPermissionManager(OrgManager):
def valid(self):
return self.get_queryset().valid()
class AssetPermission(models.Model):
class AssetPermission(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
name = models.CharField(max_length=128, verbose_name=_('Name'))
users = models.ManyToManyField('users.User', related_name='asset_permissions', blank=True, verbose_name=_("User"))
user_groups = models.ManyToManyField('users.UserGroup', related_name='asset_permissions', blank=True, verbose_name=_("User group"))
assets = models.ManyToManyField('assets.Asset', related_name='granted_by_permissions', blank=True, verbose_name=_("Asset"))
......@@ -39,7 +38,10 @@ class AssetPermission(models.Model):
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created'))
comment = models.TextField(verbose_name=_('Comment'), blank=True)
objects = AssetPermissionManager()
objects = AssetPermissionManager.from_queryset(AssetPermissionQuerySet)()
class Meta:
unique_together = [('org_id', 'name')]
def __str__(self):
return self.name
......@@ -71,7 +73,7 @@ class AssetPermission(models.Model):
return assets
class NodePermission(models.Model):
class NodePermission(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
node = models.ForeignKey('assets.Node', on_delete=models.CASCADE, verbose_name=_("Node"))
user_group = models.ForeignKey('users.UserGroup', on_delete=models.CASCADE, verbose_name=_("User group"))
......
......@@ -80,12 +80,12 @@ function onSelected(event, treeNode) {
var url = table.ajax.url();
if (treeNode.is_node) {
url = setUrlParam(url, 'asset', "");
url = setUrlParam(url, 'node', treeNode.id)
url = setUrlParam(url, 'node', treeNode.node_id)
} else {
url = setUrlParam(url, 'node', "");
url = setUrlParam(url, 'asset', treeNode.id)
url = setUrlParam(url, 'asset', treeNode.node_id)
}
setCookie('node_selected', treeNode.id);
setCookie('node_selected', treeNode.node_id);
table.ajax.url(url);
table.ajax.reload();
}
......@@ -111,10 +111,19 @@ function selectQueryNode() {
function filter(treeId, parentNode, childNodes) {
$.each(childNodes, function (index, value) {
value["pId"] = value["parent"];
value["name"] = value["value"];
value["isParent"] = value["is_node"];
value["node_id"] = value["id"];
value["id"] = value["tree_id"];
if (value["tree_id"] !== value["tree_parent"]) {
value["pId"] = value["tree_parent"];
} else {
value["isParent"] = true;
}
value['name'] = value['value'];
value["iconSkin"] = value["is_node"] ? null : 'file';
{#value["pId"] = value["parent"];#}
{#value["name"] = value["value"];#}
value["isParent"] = value["is_node"];
});
return childNodes;
}
......@@ -227,7 +236,7 @@ function initTree() {
async: {
enable: true,
url: "{% url 'api-assets:node-children-2' %}?assets=1&all=",
autoParam:["id", "name=n", "level=lv"],
autoParam:["node_id=id", "name=n", "level=lv"],
dataFilter: filter,
type: 'get'
},
......@@ -238,19 +247,22 @@ function initTree() {
};
var zNodes = [];
$.get("{% url 'api-assets:node-children-2' %}?assets=1&all=", function(data, status){
$.get("{% url 'api-assets:node-children-2' %}?assets=1", function(data, status){
$.each(data, function (index, value) {
value["pId"] = value["parent"];
value["name"] = value["value"];
value["open"] = value["key"] === "0";
value["node_id"] = value["id"];
value["id"] = value["tree_id"];
if (value["tree_id"] !== value["tree_parent"]) {
value["pId"] = value["tree_parent"];
}
value["isParent"] = value["is_node"];
value['name'] = value['value'];
value["iconSkin"] = value["is_node"] ? null : 'file';
});
zNodes = data;
{#$.fn.zTree.init($("#assetTree"), setting);#}
$.fn.zTree.init($("#assetTree"), setting, zNodes);
zTree = $.fn.zTree.getZTreeObj("assetTree");
{#selectQueryNode();#}
var root = zTree.getNodes()[0];
zTree.expandNode(root);
});
}
......@@ -288,9 +300,9 @@ $(document).ready(function(){
var _assets = [];
$.each(nodes, function (id, node) {
if (node.is_node) {
_nodes.push(node.id)
_nodes.push(node.node_id)
} else {
_assets.push(node.id)
_assets.push(node.node_id)
}
});
url += "?assets=" + _assets.join(",") + "&nodes=" + _nodes.join(",");
......
# 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
......
# coding:utf-8
from django.conf.urls import url
from django.urls import path
from .. import views
app_name = 'perms'
urlpatterns = [
url(r'^asset-permission/$', views.AssetPermissionListView.as_view(), name='asset-permission-list'),
url(r'^asset-permission/create/$', views.AssetPermissionCreateView.as_view(), name='asset-permission-create'),
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.AssetPermissionUpdateView.as_view(), name='asset-permission-update'),
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.AssetPermissionDetailView.as_view(),name='asset-permission-detail'),
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.AssetPermissionDeleteView.as_view(), name='asset-permission-delete'),
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/user/$', views.AssetPermissionUserView.as_view(), name='asset-permission-user-list'),
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/asset/$', views.AssetPermissionAssetView.as_view(), name='asset-permission-asset-list'),
path('asset-permission/', views.AssetPermissionListView.as_view(), name='asset-permission-list'),
path('asset-permission/create/', views.AssetPermissionCreateView.as_view(), name='asset-permission-create'),
path('asset-permission/<uuid:pk>/update/', views.AssetPermissionUpdateView.as_view(), name='asset-permission-update'),
path('asset-permission/<uuid:pk>/', views.AssetPermissionDetailView.as_view(),name='asset-permission-detail'),
path('asset-permission/<uuid:pk>/delete/', views.AssetPermissionDeleteView.as_view(), name='asset-permission-delete'),
path('asset-permission/<uuid:pk>/user/', views.AssetPermissionUserView.as_view(), name='asset-permission-user-list'),
path('asset-permission/<uuid:pk>/asset/', views.AssetPermissionAssetView.as_view(), name='asset-permission-asset-list'),
]
......@@ -13,7 +13,7 @@ logger = get_logger(__file__)
class Tree:
def __init__(self):
self.__all_nodes = list(Node.objects.all().prefetch_related('assets'))
self.__all_nodes = Node.objects.all().prefetch_related('assets')
self.__node_asset_map = defaultdict(set)
self.nodes = defaultdict(dict)
self.root = Node.root()
......@@ -21,7 +21,7 @@ class Tree:
def init_node_asset_map(self):
for node in self.__all_nodes:
assets = node.get_assets().values_list('id', flat=True)
assets = [a.id for a in node.assets.all()]
for asset in assets:
self.__node_asset_map[str(asset)].add(node)
......
......@@ -3,22 +3,20 @@
from __future__ import unicode_literals, absolute_import
from django.utils.translation import ugettext as _
from django.views.generic import ListView, CreateView, UpdateView, DetailView
from django.views.generic import ListView, CreateView, UpdateView, DetailView, TemplateView
from django.views.generic.edit import DeleteView, SingleObjectMixin
from django.urls import reverse_lazy
from django.conf import settings
from common.mixins import AdminUserRequiredMixin
from common.permissions import AdminUserRequiredMixin
from orgs.utils import current_org
from .hands import Node, Asset, SystemUser, User, UserGroup
from .models import AssetPermission
from .forms import AssetPermissionForm
class AssetPermissionListView(AdminUserRequiredMixin, ListView):
model = AssetPermission
class AssetPermissionListView(AdminUserRequiredMixin, TemplateView):
template_name = 'perms/asset_permission_list.html'
paginate_by = settings.DISPLAY_PER_PAGE
user = user_group = asset = node = system_user = q = ""
def get_context_data(self, **kwargs):
context = {
......@@ -87,7 +85,6 @@ class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView):
'system_users_remain': SystemUser.objects.exclude(
granted_by_permissions=self.object
),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
......@@ -116,11 +113,13 @@ class AssetPermissionUserView(AdminUserRequiredMixin,
return queryset
def get_context_data(self, **kwargs):
context = {
'app': _('Perms'),
'action': _('Asset permission user list'),
'users_remain': User.objects.exclude(asset_permissions=self.object)
.exclude(role=User.ROLE_APP),
'users_remain': current_org.get_org_users().exclude(
asset_permissions=self.object
),
'user_groups_remain': UserGroup.objects.exclude(
asset_permissions=self.object
)
......@@ -138,7 +137,7 @@ class AssetPermissionAssetView(AdminUserRequiredMixin,
object = None
def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=AssetPermission.objects.all())
self.object = self.get_object(queryset = AssetPermission.objects.all())
return super().get(request, *args, **kwargs)
def get_queryset(self):
......
......@@ -335,13 +335,13 @@ div.dataTables_wrapper div.dataTables_filter {
.nav-header, body.mini-navbar .nav-header {
padding: 0;
background: #202c37;
/*background: #202c37;*/
}
.profile-element div:first-child {
line-height: 60px;
/*width: 70px;*/
float: left;
/*float: left;*/
text-align: center;
}
......
......@@ -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);
}
<div class="footer fixed">
<div class="pull-right">
Version <strong>1.3.3-{% include '_build.html' %}</strong> GPLv2.
Version <strong>1.4.0-{% include '_build.html' %}</strong> GPLv2.
<!--<img style="display: none" src="http://www.jumpserver.org/img/evaluate_avatar1.jpg">-->
</div>
<div>
......
......@@ -35,7 +35,7 @@
</a>
<ul class="dropdown-menu animated fadeInRight m-t-xs profile-dropdown">
<li><a href="{% url 'users:user-profile' %}"><i class="fa fa-cogs"> </i><span> {% trans 'Profile' %}</span></a></li>
{% if request.user.is_superuser %}
{% if request.user.is_org_admin %}
{% if request.COOKIES.IN_ADMIN_PAGE == 'No' %}
<li><a id="switch_admin"><i class="fa fa-exchange"></i><span> {% trans 'Admin page' %}</span></a></li>
{% else %}
......
......@@ -2,7 +2,7 @@
<div class="sidebar-collapse">
<ul class="nav" id="side-menu">
{% include '_user_profile.html' %}
{% if request.user.is_superuser and request.COOKIES.IN_ADMIN_PAGE != "No" %}
{% if request.user.is_org_admin and request.COOKIES.IN_ADMIN_PAGE != "No" %}
{% include '_nav.html' %}
{% else %}
{% include '_nav_user.html' %}
......
{% 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>
\ No newline at end of file
</li>
{% endif %}
<script>
$(document).ready(function () {
var current_org = '{{ CURRENT_ORG.name }}';
console.log(current_org);
})
</script>
\ No newline at end of file
{% load static %}
{% load i18n %}
<li class="nav-header">
<div class="dropdown profile-element">
<div href="http://www.jumpserver.org" target="_blank">
<img alt="logo" height="55" width="185" src="/static/img/logo-text.png" style="margin-left: 20px"/>
<div class="profile-element" style="height: 65px">
<div href="http://www.jumpserver.org" target="_blank" style="width: 100%; background-image: url({% static 'img/header-profile.png' %})">
<img alt="logo" height="55" width="185" src="/static/img/logo-text.png"/>
</div>
</div>
<div class="clearfix"></div>
<div class="logo-element">
<img alt="image" height="40" src="/static/img/logo.png"/>
</div>
{% if ADMIN_ORGS and request.COOKIES.IN_ADMIN_PAGE != 'No' %}
{% if ADMIN_ORGS|length > 1 or not CURRENT_ORG.is_default %}
<div style="height: 55px;">
<a class="dropdown-toggle" data-toggle="dropdown" aria-expanded="false" style="display: block; background-color: transparent; color: #8095a8; padding: 14px 20px 14px 25px">
<i class="fa fa-bookmark" style="width: 14px; "></i>
<span class="nav-label" style="padding-left: 7px">
{{ CURRENT_ORG.name }}
</span>
<span class="fa fa-sort-desc pull-right"></span>
</a>
<ul class="dropdown-menu" style="width: 219px;">
{% for org in ADMIN_ORGS %}
<li>
<a class="org-dropdown"
href="{% url 'orgs:org-switch' pk=org.id %}"
data-id="{{ org.id }}">
{{ org.name }}
{% if org.id == CURRENT_ORG.id %}
<span class="fa fa-circle pull-right" style="padding-top: 5px; color: #1ab394"></span>
{% endif %}
</a>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% endif %}
</li>
<script>
$(document).ready(function () {
......
......@@ -25,8 +25,7 @@ from .hands import SystemUser
from .models import Terminal, Status, Session, Task
from .serializers import TerminalSerializer, StatusSerializer, \
SessionSerializer, TaskSerializer, ReplaySerializer
from .hands import IsSuperUserOrAppUser, IsAppUser, \
IsSuperUserOrAppUserOrUserReadonly
from common.permissions import IsAppUser, IsOrgAdminOrAppUser
from .backends import get_command_storage, get_multi_command_storage, \
SessionCommandSerializer
......@@ -36,7 +35,7 @@ logger = logging.getLogger(__file__)
class TerminalViewSet(viewsets.ModelViewSet):
queryset = Terminal.objects.filter(is_deleted=False)
serializer_class = TerminalSerializer
permission_classes = (IsSuperUserOrAppUserOrUserReadonly,)
permission_classes = (AllowAny,)
def create(self, request, *args, **kwargs):
name = request.data.get('name')
......@@ -105,7 +104,7 @@ class TerminalTokenApi(APIView):
class StatusViewSet(viewsets.ModelViewSet):
queryset = Status.objects.all()
serializer_class = StatusSerializer
permission_classes = (IsSuperUserOrAppUser,)
permission_classes = (IsOrgAdminOrAppUser,)
session_serializer_class = SessionSerializer
task_serializer_class = TaskSerializer
......@@ -177,7 +176,7 @@ class StatusViewSet(viewsets.ModelViewSet):
class SessionViewSet(viewsets.ModelViewSet):
queryset = Session.objects.all()
serializer_class = SessionSerializer
permission_classes = (IsSuperUserOrAppUser,)
permission_classes = (IsOrgAdminOrAppUser,)
def get_queryset(self):
terminal_id = self.kwargs.get("terminal", None)
......@@ -200,11 +199,11 @@ class SessionViewSet(viewsets.ModelViewSet):
class TaskViewSet(BulkModelViewSet):
queryset = Task.objects.all()
serializer_class = TaskSerializer
permission_classes = (IsSuperUserOrAppUser,)
permission_classes = (IsOrgAdminOrAppUser,)
class KillSessionAPI(APIView):
permission_classes = (IsSuperUserOrAppUser,)
permission_classes = (IsOrgAdminOrAppUser,)
model = Task
def post(self, request, *args, **kwargs):
......@@ -236,7 +235,7 @@ class CommandViewSet(viewsets.ViewSet):
command_store = get_command_storage()
multi_command_storage = get_multi_command_storage()
serializer_class = SessionCommandSerializer
permission_classes = (IsSuperUserOrAppUser,)
permission_classes = (IsOrgAdminOrAppUser,)
def get_queryset(self):
self.command_store.filter(**dict(self.request.query_params))
......@@ -244,13 +243,14 @@ class CommandViewSet(viewsets.ViewSet):
def create(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data, many=True)
if serializer.is_valid():
print(serializer.validated_data)
ok = self.command_store.bulk_save(serializer.validated_data)
if ok:
return Response("ok", status=201)
else:
return Response("Save error", status=500)
else:
msg = "Not valid: {}".format(serializer.errors)
msg = "Command not valid: {}".format(serializer.errors)
logger.error(msg)
return Response({"msg": msg}, status=401)
......@@ -262,7 +262,7 @@ class CommandViewSet(viewsets.ViewSet):
class SessionReplayViewSet(viewsets.ViewSet):
serializer_class = ReplaySerializer
permission_classes = (IsSuperUserOrAppUser,)
permission_classes = (IsOrgAdminOrAppUser,)
session = None
upload_to = 'replay' # 仅添加到本地存储中
......@@ -348,7 +348,7 @@ class SessionReplayViewSet(viewsets.ViewSet):
class SessionReplayV2ViewSet(SessionReplayViewSet):
serializer_class = ReplaySerializer
permission_classes = (IsSuperUserOrAppUser,)
permission_classes = (IsOrgAdminOrAppUser,)
session = None
def retrieve(self, request, *args, **kwargs):
......
......@@ -21,7 +21,7 @@ class CommandStore(CommandBase):
user=command["user"], asset=command["asset"],
system_user=command["system_user"], input=command["input"],
output=command["output"], session=command["session"],
timestamp=command["timestamp"]
org_id=command["org_id"], timestamp=command["timestamp"]
)
def bulk_save(self, commands):
......@@ -33,7 +33,7 @@ class CommandStore(CommandBase):
_commands.append(self.model(
user=c["user"], asset=c["asset"], system_user=c["system_user"],
input=c["input"], output=c["output"], session=c["session"],
timestamp=c["timestamp"]
org_id=c["org_id"], timestamp=c["timestamp"]
))
return self.model.objects.bulk_create(_commands)
......
......@@ -4,8 +4,10 @@ import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelMixin
class AbstractSessionCommand(models.Model):
class AbstractSessionCommand(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
user = models.CharField(max_length=64, db_index=True, verbose_name=_("User"))
asset = models.CharField(max_length=128, db_index=True, verbose_name=_("Asset"))
......
......@@ -12,5 +12,6 @@ class SessionCommandSerializer(serializers.Serializer):
input = serializers.CharField(max_length=128)
output = serializers.CharField(max_length=1024, allow_blank=True)
session = serializers.CharField(max_length=36)
org_id = serializers.CharField(max_length=36, required=False, default='', allow_null=True)
timestamp = serializers.IntegerField()
......@@ -2,7 +2,4 @@
#
from users.models import User
from users.permissions import IsSuperUserOrAppUser, IsAppUser, \
IsSuperUserOrAppUserOrUserReadonly
from users.utils import AdminUserRequiredMixin
from assets.models import SystemUser
\ No newline at end of file
from assets.models import SystemUser
......@@ -8,6 +8,7 @@ from django.utils import timezone
from django.conf import settings
from users.models import User
from orgs.mixins import OrgModelMixin
from .backends.command.models import AbstractSessionCommand
......@@ -112,7 +113,7 @@ class Status(models.Model):
return self.date_created.strftime("%Y-%m-%d %H:%M:%S")
class Session(models.Model):
class Session(OrgModelMixin):
LOGIN_FROM_CHOICES = (
('ST', 'SSH Terminal'),
('WT', 'Web Terminal'),
......
......@@ -150,12 +150,7 @@
});
}
$(document).ready(function() {
$('table').DataTable({
"searching": false,
"paging": false,
"bInfo" : false,
"order": []
});
jumpserver.initStaticTable('table');
$('.select2').select2({
dropdownAutoWidth: true,
width: "auto"
......
......@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
#
from django.conf.urls import url
from django.urls import path
from rest_framework import routers
from .. import api
......@@ -10,26 +10,26 @@ from .. import api
app_name = 'terminal'
router = routers.DefaultRouter()
router.register(r'v1/terminal/(?P<terminal>[a-zA-Z0-9\-]{36})?/?status', api.StatusViewSet, 'terminal-status')
router.register(r'v1/terminal/(?P<terminal>[a-zA-Z0-9\-]{36})?/?sessions', api.SessionViewSet, 'terminal-sessions')
router.register(r'v1/tasks', api.TaskViewSet, 'tasks')
router.register(r'v1/terminal', api.TerminalViewSet, 'terminal')
router.register(r'v1/command', api.CommandViewSet, 'command')
router.register(r'v1/sessions', api.SessionViewSet, 'session')
router.register(r'v1/status', api.StatusViewSet, 'session')
router.register(r'terminal/(?P<terminal>[a-zA-Z0-9\-]{36})?/?status', api.StatusViewSet, 'terminal-status')
router.register(r'terminal/(?P<terminal>[a-zA-Z0-9\-]{36})?/?sessions', api.SessionViewSet, 'terminal-sessions')
router.register(r'terminal', api.TerminalViewSet, 'terminal')
router.register(r'tasks', api.TaskViewSet, 'tasks')
router.register(r'command', api.CommandViewSet, 'command')
router.register(r'sessions', api.SessionViewSet, 'session')
router.register(r'status', api.StatusViewSet, 'session')
urlpatterns = [
url(r'^v1/sessions/(?P<pk>[0-9a-zA-Z\-]{36})/replay/$',
api.SessionReplayViewSet.as_view({'get': 'retrieve', 'post': 'create'}),
name='session-replay'),
url(r'^v1/tasks/kill-session/', api.KillSessionAPI.as_view(), name='kill-session'),
url(r'^v1/terminal/(?P<terminal>[a-zA-Z0-9\-]{36})/access-key', api.TerminalTokenApi.as_view(),
name='terminal-access-key'),
url(r'^v1/terminal/config', api.TerminalConfig.as_view(), name='terminal-config'),
path('sessions/<uuid:pk>/replay/',
api.SessionReplayV2ViewSet.as_view({'get': 'retrieve', 'post': 'create'}),
name='session-replay'),
path('tasks/kill-session/', api.KillSessionAPI.as_view(), name='kill-session'),
path('terminal/<uuid:terminal>/access-key/', api.TerminalTokenApi.as_view(),
name='terminal-access-key'),
path('terminal/config/', api.TerminalConfig.as_view(), name='terminal-config'),
# v2: get session's replay
url(r'^v2/sessions/(?P<pk>[0-9a-zA-Z\-]{36})/replay/$',
api.SessionReplayV2ViewSet.as_view({'get': 'retrieve'}),
name='session-replay-v2'),
# path('v2/sessions/<uuid:pk>/replay/',
# api.SessionReplayV2ViewSet.as_view({'get': 'retrieve'}),
# name='session-replay-v2'),
]
urlpatterns += router.urls
......@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
#
from django.conf.urls import url
from django.urls import path
from .. import views
......@@ -10,20 +10,20 @@ app_name = 'terminal'
urlpatterns = [
# Terminal view
url(r'^terminal/$', views.TerminalListView.as_view(), name='terminal-list'),
url(r'^terminal/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.TerminalDetailView.as_view(), name='terminal-detail'),
url(r'^terminal/(?P<pk>[0-9a-zA-Z\-]{36})/connect/$', views.TerminalConnectView.as_view(), name='terminal-connect'),
url(r'^terminal/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.TerminalUpdateView.as_view(), name='terminal-update'),
url(r'^(?P<pk>[0-9a-zA-Z\-]{36})/accept/$', views.TerminalAcceptView.as_view(), name='terminal-accept'),
url(r'^web-terminal/$', views.WebTerminalView.as_view(), name='web-terminal'),
path('terminal/', views.TerminalListView.as_view(), name='terminal-list'),
path('terminal/<uuid:pk>/', views.TerminalDetailView.as_view(), name='terminal-detail'),
path('terminal/<uuid:pk>/connect/', views.TerminalConnectView.as_view(), name='terminal-connect'),
path('terminal/<uuid:pk>/update/', views.TerminalUpdateView.as_view(), name='terminal-update'),
path('<uuid:pk>/accept/', views.TerminalAcceptView.as_view(), name='terminal-accept'),
path('web-terminal/', views.WebTerminalView.as_view(), name='web-terminal'),
# Session view
url(r'^session-online/$', views.SessionOnlineListView.as_view(), name='session-online-list'),
url(r'^session-offline/$', views.SessionOfflineListView.as_view(), name='session-offline-list'),
url(r'^session/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.SessionDetailView.as_view(), name='session-detail'),
path('session-online/', views.SessionOnlineListView.as_view(), name='session-online-list'),
path('session-offline/', views.SessionOfflineListView.as_view(), name='session-offline-list'),
path('session/<uuid:pk>/', views.SessionDetailView.as_view(), name='session-detail'),
# Command view
url(r'^command/$', views.CommandListView.as_view(), name='command-list'),
url(r'^command/export/$', views.CommandExportView.as_view(), name='command-export')
path('command/', views.CommandListView.as_view(), name='command-list'),
path('command/export/', views.CommandExportView.as_view(), name='command-export')
]
......@@ -8,7 +8,8 @@ from django.http import HttpResponse
from django.template import loader
import time
from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin
from common.mixins import DatetimeSearchMixin
from common.permissions import AdminUserRequiredMixin
from ..models import Command
from .. import utils
from ..backends import get_multi_command_storage
......
......@@ -7,7 +7,7 @@ from django.utils.translation import ugettext as _
from django.utils import timezone
from django.conf import settings
from users.utils import AdminUserRequiredMixin
from common.permissions import AdminUserRequiredMixin
from common.mixins import DatetimeSearchMixin
from ..models import Session, Command, Terminal
from ..backends import get_multi_command_storage
......
......@@ -10,7 +10,7 @@ from django.urls import reverse_lazy, reverse
from common.mixins import JSONResponseMixin
from ..models import Terminal
from ..forms import TerminalForm
from ..hands import AdminUserRequiredMixin
from common.permissions import AdminUserRequiredMixin
__all__ = [
......
......@@ -3,6 +3,7 @@ import uuid
from django.core.cache import cache
from django.urls import reverse
from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext as _
from rest_framework import generics
......@@ -16,10 +17,12 @@ from .serializers import UserSerializer, UserGroupSerializer, \
UserUpdateGroupSerializer, ChangeUserPasswordSerializer
from .tasks import write_login_log_async
from .models import User, UserGroup, LoginLog
from .permissions import IsSuperUser, IsValidUser, IsCurrentUserOrReadOnly, \
IsSuperUserOrAppUser
from .utils import check_user_valid, generate_token, get_login_ip, \
check_otp_code, set_user_login_failed_count_to_cache, is_block_login
from .hands import Asset, SystemUser
from orgs.utils import current_org
from common.permissions import IsOrgAdmin, IsCurrentUserOrReadOnly, IsOrgAdminOrAppUser
from .hands import Asset, SystemUser
from common.mixins import IDInFilterMixin
from common.utils import get_logger
......@@ -30,17 +33,23 @@ logger = get_logger(__name__)
class UserViewSet(IDInFilterMixin, BulkModelViewSet):
queryset = User.objects.exclude(role="App")
serializer_class = UserSerializer
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
filter_fields = ('username', 'email', 'name', 'id')
def get_queryset(self):
queryset = super().get_queryset()
org_users = current_org.get_org_users().values_list('id', flat=True)
queryset = queryset.filter(id__in=org_users)
return queryset
def get_permissions(self):
if self.action == "retrieve":
self.permission_classes = (IsSuperUserOrAppUser,)
self.permission_classes = (IsOrgAdminOrAppUser,)
return super().get_permissions()
class ChangeUserPasswordApi(generics.RetrieveUpdateAPIView):
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
queryset = User.objects.all()
serializer_class = ChangeUserPasswordSerializer
......@@ -53,7 +62,7 @@ class ChangeUserPasswordApi(generics.RetrieveUpdateAPIView):
class UserUpdateGroupApi(generics.RetrieveUpdateAPIView):
queryset = User.objects.all()
serializer_class = UserUpdateGroupSerializer
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
class UserResetPasswordApi(generics.UpdateAPIView):
......@@ -97,7 +106,7 @@ class UserUpdatePKApi(generics.UpdateAPIView):
class UserUnblockPKApi(generics.UpdateAPIView):
queryset = User.objects.all()
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
serializer_class = UserSerializer
key_prefix_limit = "_LOGIN_LIMIT_{}_{}"
key_prefix_block = "_LOGIN_BLOCK_{}"
......@@ -114,13 +123,13 @@ class UserUnblockPKApi(generics.UpdateAPIView):
class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet):
queryset = UserGroup.objects.all()
serializer_class = UserGroupSerializer
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
class UserGroupUpdateUserApi(generics.RetrieveUpdateAPIView):
queryset = UserGroup.objects.all()
serializer_class = UserGroupUpdateMemeberSerializer
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
class UserToken(APIView):
......@@ -298,17 +307,23 @@ class UserAuthApi(APIView):
class UserConnectionTokenApi(APIView):
permission_classes = (IsSuperUserOrAppUser,)
permission_classes = (IsOrgAdminOrAppUser,)
def post(self, request):
user_id = request.data.get('user', '')
asset_id = request.data.get('asset', '')
system_user_id = request.data.get('system_user', '')
token = str(uuid.uuid4())
user = get_object_or_404(User, id=user_id)
asset = get_object_or_404(Asset, id=asset_id)
system_user = get_object_or_404(SystemUser, id=system_user_id)
value = {
'user': user_id,
'username': user.username,
'asset': asset_id,
'system_user': system_user_id
'hostname': asset.hostname,
'system_user': system_user_id,
'system_user_name': system_user.name
}
cache.set(token, value, timeout=20)
return Response({"token": token}, status=201)
......
......@@ -6,6 +6,8 @@ from django.utils.translation import gettext_lazy as _
from captcha.fields import CaptchaField
from common.utils import validate_ssh_public_key
from orgs.mixins import OrgModelForm
from orgs.utils import current_org
from .models import User, UserGroup
......@@ -39,7 +41,7 @@ class UserCheckOtpCodeForm(forms.Form):
otp_code = forms.CharField(label=_('MFA code'), max_length=6)
class UserCreateUpdateForm(forms.ModelForm):
class UserCreateUpdateForm(OrgModelForm):
role_choices = ((i, n) for i, n in User.ROLE_CHOICES if i != User.ROLE_APP)
password = forms.CharField(
label=_('Password'), widget=forms.PasswordInput,
......@@ -67,15 +69,39 @@ class UserCreateUpdateForm(forms.ModelForm):
'email': '* required',
}
widgets = {
'otp_level': forms.RadioSelect(),
'groups': forms.SelectMultiple(
attrs={
'class': 'select2',
'data-placeholder': _('Join user groups')
}
),
'otp_level': forms.RadioSelect(),
)
}
def __init__(self, *args, **kwargs):
self.request = kwargs.pop("request", None)
super(UserCreateUpdateForm, self).__init__(*args, **kwargs)
roles = []
# Super admin user
if self.request.user.is_superuser:
roles.append((User.ROLE_ADMIN, dict(User.ROLE_CHOICES).get(User.ROLE_ADMIN)))
roles.append((User.ROLE_USER, dict(User.ROLE_CHOICES).get(User.ROLE_USER)))
# Org admin user
else:
user = kwargs.get('instance')
# Update
if user:
role = kwargs.get('instance').role
roles.append((role, dict(User.ROLE_CHOICES).get(role)))
# Create
else:
roles.append((User.ROLE_USER, dict(User.ROLE_CHOICES).get(User.ROLE_USER)))
field = self.fields['role']
field.choices = set(roles)
def clean_public_key(self):
public_key = self.cleaned_data['public_key']
if not public_key:
......@@ -237,7 +263,7 @@ class UserBulkUpdateForm(forms.ModelForm):
required=True,
help_text='* required',
label=_('Select users'),
queryset=User.objects.all(),
queryset = User.objects.all(),
widget=forms.SelectMultiple(
attrs={
'class': 'select2',
......@@ -276,6 +302,10 @@ class UserBulkUpdateForm(forms.ModelForm):
return users
def user_limit_to():
return {"orgs": current_org}
class UserGroupForm(forms.ModelForm):
users = forms.ModelMultipleChoiceField(
queryset=User.objects.exclude(role=User.ROLE_APP),
......@@ -287,17 +317,21 @@ class UserGroupForm(forms.ModelForm):
}
),
required=False,
limit_choices_to=user_limit_to
)
def __init__(self, **kwargs):
instance = kwargs.get('instance')
if instance:
initial = kwargs.get('initial', {})
initial.update({
'users': instance.users.all(),
})
initial.update({'users': instance.users.all()})
kwargs['initial'] = initial
super().__init__(**kwargs)
if 'initial' not in kwargs:
return
users_field = self.fields.get('users')
if hasattr(users_field, 'queryset'):
users_field.queryset = current_org.get_org_users()
def save(self, commit=True):
group = super().save(commit=commit)
......@@ -315,30 +349,11 @@ class UserGroupForm(forms.ModelForm):
}
# class UserGroupPrivateAssetPermissionForm(forms.ModelForm):
# def save(self, commit=True):
# self.instance = super(UserGroupPrivateAssetPermissionForm, self)\
# .save(commit=commit)
# self.instance.user_groups = [self.user_group]
# self.instance.save()
# return self.instance
#
# class Meta:
# model = AssetPermission
# fields = [
# 'assets', 'asset_groups', 'system_users', 'name',
# ]
# widgets = {
# 'assets': forms.SelectMultiple(
# attrs={'class': 'select2',
# 'data-placeholder': _('Select assets')}),
# 'asset_groups': forms.SelectMultiple(
# attrs={'class': 'select2',
# 'data-placeholder': _('Select asset groups')}),
# 'system_users': forms.SelectMultiple(
# attrs={'class': 'select2',
# 'data-placeholder': _('Select system users')}),
# }
class OrgUserField(forms.ModelMultipleChoiceField):
def get_limit_choices_to(self):
return {"orgs"}
class FileForm(forms.Form):
......
......@@ -15,3 +15,4 @@
# from users.models import User
# from perms.models import AssetPermission
# from perms.utils import get_user_granted_assets, get_user_granted_asset_groups
from assets.models import Asset, SystemUser
......@@ -4,12 +4,14 @@ import uuid
from django.db import models, IntegrityError
from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelMixin
__all__ = ['UserGroup']
class UserGroup(models.Model):
class UserGroup(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'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
date_created = models.DateTimeField(auto_now_add=True, null=True,
verbose_name=_('Date created'))
......@@ -20,6 +22,7 @@ class UserGroup(models.Model):
class Meta:
ordering = ['name']
unique_together = [('org_id', 'name')]
verbose_name = _("User group")
@classmethod
......
......@@ -6,7 +6,7 @@ from collections import OrderedDict
from django.conf import settings
from django.contrib.auth.hashers import make_password
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.models import AbstractUser, UserManager
from django.core import signing
from django.db import models
from django.utils.translation import ugettext_lazy as _
......@@ -15,6 +15,8 @@ from django.shortcuts import reverse
from common.utils import get_signer, date_expired_default
from common.models import Setting
from orgs.mixins import OrgManager
from orgs.utils import current_org
__all__ = ['User']
......@@ -116,7 +118,7 @@ class User(AbstractUser):
@otp_secret_key.setter
def otp_secret_key(self, item):
self._otp_secret_key = signer.sign(item).decode('utf-8')
self._otp_secret_key = signer.sign(item)
def get_absolute_url(self):
return reverse('users:user-detail', args=(self.id,))
......@@ -186,6 +188,18 @@ class User(AbstractUser):
else:
self.role = 'User'
@property
def admin_orgs(self):
from orgs.models import Organization
return Organization.get_user_admin_orgs(self)
@property
def is_org_admin(self):
if self.is_superuser or self.admin_orgs.exists():
return True
else:
return False
@property
def is_app(self):
return self.role == 'App'
......@@ -207,8 +221,9 @@ class User(AbstractUser):
if self.username == 'admin':
self.role = 'Admin'
self.is_active = True
super().save(*args, **kwargs)
if current_org and current_org.is_real():
self.orgs.add(current_org.id)
@property
def private_token(self):
......
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from rest_framework import permissions
class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission):
"""Allows access to valid user, is active and not expired"""
def has_permission(self, request, view):
return super(IsValidUser, self).has_permission(request, view) \
and request.user.is_valid
class IsAppUser(IsValidUser):
"""Allows access only to app user """
def has_permission(self, request, view):
return super(IsAppUser, self).has_permission(request, view) \
and request.user.is_app
class IsSuperUser(IsValidUser):
"""Allows access only to superuser"""
def has_permission(self, request, view):
return super(IsSuperUser, self).has_permission(request, view) \
and request.user.is_superuser
class IsSuperUserOrAppUser(IsValidUser):
"""Allows access between superuser and app user"""
def has_permission(self, request, view):
return super(IsSuperUserOrAppUser, self).has_permission(request, view) \
and (request.user.is_superuser or request.user.is_app)
class IsSuperUserOrAppUserOrUserReadonly(IsSuperUserOrAppUser):
def has_permission(self, request, view):
if IsValidUser.has_permission(self, request, view) \
and request.method in permissions.SAFE_METHODS:
return True
else:
return IsSuperUserOrAppUser.has_permission(self, request, view)
class IsCurrentUserOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj == request.user
......@@ -14,7 +14,7 @@ signer = get_signer()
class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
groups_display = serializers.SerializerMethodField()
groups = serializers.PrimaryKeyRelatedField(many=True, queryset=UserGroup.objects.all(), required=False)
groups = serializers.PrimaryKeyRelatedField(many=True, queryset = UserGroup.objects.all(), required=False)
class Meta:
model = User
......@@ -71,7 +71,7 @@ class UserGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer):
class UserGroupUpdateMemeberSerializer(serializers.ModelSerializer):
users = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects.all())
users = serializers.PrimaryKeyRelatedField(many=True, queryset = User.objects.all())
class Meta:
model = UserGroup
......
......@@ -80,12 +80,7 @@
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
<script>
$(document).ready(function() {
$('table').DataTable({
"searching": false,
"bInfo" : false,
"paging": false,
"order": []
});
jumpserver.initStaticTable('table');
$('#date .input-daterange').datepicker({
format: "yyyy-mm-dd",
todayBtn: "linked",
......
......@@ -100,10 +100,18 @@
progress = $('#id_progress'),
password_check_rules = {{ password_check_rules|safe }},
minLength = {{ min_length }},
top = 146, left = 170;
top = 146, left = 170,
i18n_fallback = {
"veryWeak": "{% trans 'Very weak' %}",
"weak": "{% trans 'Weak' %}",
"normal": "{% trans 'Normal' %}",
"medium": "{% trans 'Medium' %}",
"strong": "{% trans 'Strong' %}",
"veryStrong": "{% trans 'Very strong' %}"
};
// 初始化popover
initPopover(container, progress, idPassword, el, password_check_rules);
initPopover(container, progress, idPassword, el, password_check_rules, i18n_fallback);
// 监听事件
idPassword.on('focus', function () {
......
......@@ -72,6 +72,7 @@ function initTable() {
} else {
inited = true;
}
url = "{% url 'api-perms:user-assets' pk=object.id %}";
var options = {
ele: $('#user_assets_table'),
columnDefs: [
......@@ -102,36 +103,16 @@ function initTable() {
{data: "system_users_granted", orderable: false}
]
};
return jumpserver.initDataTable(options);
asset_table = jumpserver.initDataTable(options);
}
function onSelected(event, treeNode) {
url = '{% url "api-perms:user-node-assets" pk=object.id node_id=DEFAULT_PK %}';
url = url.replace("{{ DEFAULT_PK }}", treeNode.id);
setCookie('node_selected', treeNode.id);
asset_table = initTable();
url = url.replace("{{ DEFAULT_PK }}", treeNode.node_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,24 +130,28 @@ function initTree() {
};
var zNodes = [];
$.get("{% url 'api-perms:user-nodes' pk=object.id %}", function(data, status){
$.get("{% url 'api-perms:user-nodes' pk=object.id %}", function(data, status) {
$.each(data, function (index, value) {
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'];
value["iconSkin"] = value["is_node"] ? null : 'file';
});
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();
});
</script>
{% endblock %}
......@@ -6,7 +6,7 @@
<div class="verify">
<p style="margin: 20px auto;"><strong style="color: #000000">账号保护已开启,请根据提示完成以下操作</strong></p>
<img src="{% static 'img/otp_auth.png' %}" alt="" width="72px" height="117">
<p style="margin: 20px auto;">请在手机中打开Google Authenticator应用,输入6动态码</p>
<p style="margin: 20px auto;">请在手机中打开Google Authenticator应用,输入6动态码</p>
</div>
<form class="" role="form" method="post" action="">
......
......@@ -93,10 +93,18 @@
password_check_rules = {{ password_check_rules|safe }},
minLength = {{ min_length }},
top = idPassword.offset().top - $('.navbar').outerHeight(true) - $('.page-heading').outerHeight(true) - 10 + 34,
left = 377;
left = 377,
i18n_fallback = {
"veryWeak": "{% trans 'Very weak' %}",
"weak": "{% trans 'Weak' %}",
"normal": "{% trans 'Normal' %}",
"medium": "{% trans 'Medium' %}",
"strong": "{% trans 'Strong' %}",
"veryStrong": "{% trans 'Very strong' %}"
};
// 初始化popover
initPopover(container, progress, idPassword, el, password_check_rules);
initPopover(container, progress, idPassword, el, password_check_rules, i18n_fallback);
// 监听事件
idPassword.on('focus', function () {
......
......@@ -29,10 +29,18 @@
password_check_rules = {{ password_check_rules|safe }},
minLength = {{ min_length }},
top = idPassword.offset().top - $('.navbar').outerHeight(true) - $('.page-heading').outerHeight(true) - 10 + 34,
left = 377;
left = 377,
i18n_fallback = {
"veryWeak": "{% trans 'Very weak' %}",
"weak": "{% trans 'Weak' %}",
"normal": "{% trans 'Normal' %}",
"medium": "{% trans 'Medium' %}",
"strong": "{% trans 'Strong' %}",
"veryStrong": "{% trans 'Very strong' %}"
};
// 初始化popover
initPopover(container, progress, idPassword, el, password_check_rules);
initPopover(container, progress, idPassword, el, password_check_rules, i18n_fallback);
// 监听事件
idPassword.on('focus', function () {
......
......@@ -3,38 +3,31 @@
#
from __future__ import absolute_import
from django.conf.urls import url
from django.urls import path
from rest_framework_bulk.routes import BulkRouter
from .. import api
app_name = 'users'
router = BulkRouter()
router.register(r'v1/users', api.UserViewSet, 'user')
router.register(r'v1/groups', api.UserGroupViewSet, 'user-group')
router.register(r'users', api.UserViewSet, 'user')
router.register(r'groups', api.UserGroupViewSet, 'user-group')
urlpatterns = [
# url(r'', api.UserListView.as_view()),
url(r'^v1/token/$', api.UserToken.as_view(), name='user-token'),
url(r'^v1/connection-token/$', api.UserConnectionTokenApi.as_view(), name='connection-token'),
url(r'^v1/profile/$', api.UserProfile.as_view(), name='user-profile'),
url(r'^v1/auth/$', api.UserAuthApi.as_view(), name='user-auth'),
url(r'^v1/otp/auth/$', api.UserOtpAuthApi.as_view(), name='user-otp-auth'),
url(r'^v1/users/(?P<pk>[0-9a-zA-Z\-]{36})/password/$',
api.ChangeUserPasswordApi.as_view(), name='change-user-password'),
url(r'^v1/users/(?P<pk>[0-9a-zA-Z\-]{36})/password/reset/$',
api.UserResetPasswordApi.as_view(), name='user-reset-password'),
url(r'^v1/users/(?P<pk>[0-9a-zA-Z\-]{36})/pubkey/reset/$',
api.UserResetPKApi.as_view(), name='user-public-key-reset'),
url(r'^v1/users/(?P<pk>[0-9a-zA-Z\-]{36})/pubkey/update/$',
api.UserUpdatePKApi.as_view(), name='user-public-key-update'),
url(r'^v1/users/(?P<pk>[0-9a-zA-Z\-]{36})/unblock/$',
api.UserUnblockPKApi.as_view(), name='user-unblock'),
url(r'^v1/users/(?P<pk>[0-9a-zA-Z\-]{36})/groups/$',
api.UserUpdateGroupApi.as_view(), name='user-update-group'),
url(r'^v1/groups/(?P<pk>[0-9a-zA-Z\-]{36})/users/$',
api.UserGroupUpdateUserApi.as_view(), name='user-group-update-user'),
# path(r'', api.UserListView.as_view()),
path('token/', api.UserToken.as_view(), name='user-token'),
path('connection-token/', api.UserConnectionTokenApi.as_view(), name='connection-token'),
path('profile/', api.UserProfile.as_view(), name='user-profile'),
path('auth/', api.UserAuthApi.as_view(), name='user-auth'),
path('otp/auth/', api.UserOtpAuthApi.as_view(), name='user-otp-auth'),
path('users/<uuid:pk>/password/', api.ChangeUserPasswordApi.as_view(), name='change-user-password'),
path('users/<uuid:pk>/password/reset/', api.UserResetPasswordApi.as_view(), name='user-reset-password'),
path('users/<uuid:pk>/pubkey/reset/', api.UserResetPKApi.as_view(), name='user-public-key-reset'),
path('users/<uuid:pk>/pubkey/update/', api.UserUpdatePKApi.as_view(), name='user-public-key-update'),
path('users/<uuid:pk>/unblock/', api.UserUnblockPKApi.as_view(), name='user-unblock'),
path('users/<uuid:pk>/groups/', api.UserUpdateGroupApi.as_view(), name='user-update-group'),
path('groups/<uuid:pk>/users/', api.UserGroupUpdateUserApi.as_view(), name='user-group-update-user'),
]
urlpatterns += router.urls
from __future__ import absolute_import
from django.conf.urls import url
from django.urls import path
from .. import views
......@@ -8,45 +8,45 @@ app_name = 'users'
urlpatterns = [
# Login view
url(r'^login/$', views.UserLoginView.as_view(), name='login'),
url(r'^logout/$', views.UserLogoutView.as_view(), name='logout'),
url(r'^login/otp/$', views.UserLoginOtpView.as_view(), name='login-otp'),
url(r'^password/forgot/$', views.UserForgotPasswordView.as_view(), name='forgot-password'),
url(r'^password/forgot/sendmail-success/$', views.UserForgotPasswordSendmailSuccessView.as_view(), name='forgot-password-sendmail-success'),
url(r'^password/reset/$', views.UserResetPasswordView.as_view(), name='reset-password'),
url(r'^password/reset/success/$', views.UserResetPasswordSuccessView.as_view(), name='reset-password-success'),
path('login/', views.UserLoginView.as_view(), name='login'),
path('logout/', views.UserLogoutView.as_view(), name='logout'),
path('login/otp/', views.UserLoginOtpView.as_view(), name='login-otp'),
path('password/forgot/', views.UserForgotPasswordView.as_view(), name='forgot-password'),
path('password/forgot/sendmail-success/', views.UserForgotPasswordSendmailSuccessView.as_view(), name='forgot-password-sendmail-success'),
path('password/reset/', views.UserResetPasswordView.as_view(), name='reset-password'),
path('password/reset/success/', views.UserResetPasswordSuccessView.as_view(), name='reset-password-success'),
# Profile
url(r'^profile/$', views.UserProfileView.as_view(), name='user-profile'),
url(r'^profile/update/$', views.UserProfileUpdateView.as_view(), name='user-profile-update'),
url(r'^profile/password/update/$', views.UserPasswordUpdateView.as_view(), name='user-password-update'),
url(r'^profile/pubkey/update/$', views.UserPublicKeyUpdateView.as_view(), name='user-pubkey-update'),
url(r'^profile/pubkey/generate/$', views.UserPublicKeyGenerateView.as_view(), name='user-pubkey-generate'),
url(r'^profile/otp/enable/authentication/$', views.UserOtpEnableAuthenticationView.as_view(), name='user-otp-enable-authentication'),
url(r'^profile/otp/enable/install-app/$', views.UserOtpEnableInstallAppView.as_view(), name='user-otp-enable-install-app'),
url(r'^profile/otp/enable/bind/$', views.UserOtpEnableBindView.as_view(), name='user-otp-enable-bind'),
url(r'^profile/otp/disable/authentication/$', views.UserOtpDisableAuthenticationView.as_view(), name='user-otp-disable-authentication'),
url(r'^profile/otp/settings-success/$', views.UserOtpSettingsSuccessView.as_view(), name='user-otp-settings-success'),
path('profile/', views.UserProfileView.as_view(), name='user-profile'),
path('profile/update/', views.UserProfileUpdateView.as_view(), name='user-profile-update'),
path('profile/password/update/', views.UserPasswordUpdateView.as_view(), name='user-password-update'),
path('profile/pubkey/update/', views.UserPublicKeyUpdateView.as_view(), name='user-pubkey-update'),
path('profile/pubkey/generate/', views.UserPublicKeyGenerateView.as_view(), name='user-pubkey-generate'),
path('profile/otp/enable/authentication/', views.UserOtpEnableAuthenticationView.as_view(), name='user-otp-enable-authentication'),
path('profile/otp/enable/install-app/', views.UserOtpEnableInstallAppView.as_view(), name='user-otp-enable-install-app'),
path('profile/otp/enable/bind/', views.UserOtpEnableBindView.as_view(), name='user-otp-enable-bind'),
path('profile/otp/disable/authentication/', views.UserOtpDisableAuthenticationView.as_view(), name='user-otp-disable-authentication'),
path('profile/otp/settings-success/', views.UserOtpSettingsSuccessView.as_view(), name='user-otp-settings-success'),
# User view
url(r'^user/$', views.UserListView.as_view(), name='user-list'),
url(r'^user/export/$', views.UserExportView.as_view(), name='user-export'),
url(r'^first-login/$', views.UserFirstLoginView.as_view(), name='user-first-login'),
url(r'^user/import/$', views.UserBulkImportView.as_view(), name='user-import'),
url(r'^user/create/$', views.UserCreateView.as_view(), name='user-create'),
url(r'^user/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.UserUpdateView.as_view(), name='user-update'),
url(r'^user/update/$', views.UserBulkUpdateView.as_view(), name='user-bulk-update'),
url(r'^user/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.UserDetailView.as_view(), name='user-detail'),
url(r'^user/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$', views.UserGrantedAssetView.as_view(), name='user-granted-asset'),
url(r'^user/(?P<pk>[0-9a-zA-Z\-]{36})/login-history/$', views.UserDetailView.as_view(), name='user-login-history'),
path('user/', views.UserListView.as_view(), name='user-list'),
path('user/export/', views.UserExportView.as_view(), name='user-export'),
path('first-login/', views.UserFirstLoginView.as_view(), name='user-first-login'),
path('user/import/', views.UserBulkImportView.as_view(), name='user-import'),
path('user/create/', views.UserCreateView.as_view(), name='user-create'),
path('user/<uuid:pk>/update/', views.UserUpdateView.as_view(), name='user-update'),
path('user/update/', views.UserBulkUpdateView.as_view(), name='user-bulk-update'),
path('user/<uuid:pk>/', views.UserDetailView.as_view(), name='user-detail'),
path('user/<uuid:pk>/assets/', views.UserGrantedAssetView.as_view(), name='user-granted-asset'),
path('user/<uuid:pk>/login-history/', views.UserDetailView.as_view(), name='user-login-history'),
# User group view
url(r'^user-group/$', views.UserGroupListView.as_view(), name='user-group-list'),
url(r'^user-group/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.UserGroupDetailView.as_view(), name='user-group-detail'),
url(r'^user-group/create/$', views.UserGroupCreateView.as_view(), name='user-group-create'),
url(r'^user-group/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.UserGroupUpdateView.as_view(), name='user-group-update'),
url(r'^user-group/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$', views.UserGroupGrantedAssetView.as_view(), name='user-group-granted-asset'),
path('user-group/', views.UserGroupListView.as_view(), name='user-group-list'),
path('user-group/<uuid:pk>/', views.UserGroupDetailView.as_view(), name='user-group-detail'),
path('user-group/create/', views.UserGroupCreateView.as_view(), name='user-group-create'),
path('user-group/<uuid:pk>/update/', views.UserGroupUpdateView.as_view(), name='user-group-update'),
path('user-group/<uuid:pk>/assets/', views.UserGroupGrantedAssetView.as_view(), name='user-group-granted-asset'),
# Login log
url(r'^login-log/$', views.LoginLogListView.as_view(), name='login-log-list'),
path('login-log/', views.LoginLogListView.as_view(), name='login-log-list'),
]
# ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals
from django import forms
from django.utils.translation import ugettext as _
from django.urls import reverse_lazy
from django.views.generic.base import TemplateView
......@@ -11,8 +10,8 @@ from django.contrib.messages.views import SuccessMessageMixin
from common.utils import get_logger
from common.const import create_success_msg, update_success_msg
from common.permissions import AdminUserRequiredMixin
from ..models import User, UserGroup
from ..utils import AdminUserRequiredMixin
from .. import forms
__all__ = ['UserGroupListView', 'UserGroupCreateView', 'UserGroupDetailView',
......
......@@ -22,8 +22,9 @@ from formtools.wizard.views import SessionWizardView
from django.conf import settings
from common.utils import get_object_or_none
from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin
from common.models import Setting
from common.mixins import DatetimeSearchMixin
from common.permissions import AdminUserRequiredMixin
from orgs.utils import current_org
from ..models import User, LoginLog
from ..utils import send_reset_password_mail, check_otp_code, get_login_ip, \
redirect_user_first_login_or_index, get_user_or_tmp_user, \
......@@ -309,7 +310,7 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
file_storage = default_storage
def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated() and not request.user.is_first_login:
if request.user.is_authenticated and not request.user.is_first_login:
return redirect(reverse('index'))
return super().dispatch(request, *args, **kwargs)
......@@ -367,11 +368,17 @@ class LoginLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
user = keyword = ""
date_to = date_from = None
@staticmethod
def get_org_users():
users = current_org.get_org_users().values_list('username', flat=True)
return users
def get_queryset(self):
users = self.get_org_users()
queryset = super().get_queryset().filter(username__in=users)
self.user = self.request.GET.get('user', '')
self.keyword = self.request.GET.get("keyword", '')
queryset = super().get_queryset()
queryset = queryset.filter(
datetime__gt=self.date_from, datetime__lt=self.date_to
)
......@@ -393,9 +400,7 @@ class LoginLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
'date_to': self.date_to,
'user': self.user,
'keyword': self.keyword,
'user_list': set(
LoginLog.objects.all().values_list('username', flat=True)
)
'user_list': self.get_org_users(),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
\ No newline at end of file
......@@ -34,9 +34,10 @@ from common.const import create_success_msg, update_success_msg
from common.mixins import JSONResponseMixin
from common.utils import get_logger, get_object_or_none, is_uuid, ssh_key_gen
from common.models import Setting
from common.permissions import AdminUserRequiredMixin
from .. import forms
from ..models import User, UserGroup
from ..utils import AdminUserRequiredMixin, generate_otp_uri, check_otp_code, \
from ..utils import generate_otp_uri, check_otp_code, \
get_user_or_tmp_user, get_password_check_rules, check_password_rules, \
is_need_unblock
from ..signals import post_user_create
......@@ -89,6 +90,12 @@ class UserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
post_user_create.send(self.__class__, user=user)
return super().form_valid(form)
def get_form_kwargs(self):
kwargs = super(UserCreateView, self).get_form_kwargs()
data = {'request': self.request}
kwargs.update(data)
return kwargs
class UserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
model = User
......@@ -122,6 +129,12 @@ class UserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
return self.form_invalid(form)
return super().form_valid(form)
def get_form_kwargs(self):
kwargs = super(UserUpdateView, self).get_form_kwargs()
data = {'request': self.request}
kwargs.update(data)
return kwargs
class UserBulkUpdateView(AdminUserRequiredMixin, TemplateView):
model = User
......@@ -329,7 +342,6 @@ class UserBulkImportView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
class UserGrantedAssetView(AdminUserRequiredMixin, DetailView):
model = User
template_name = 'users/user_granted_asset.html'
object = None
def get_context_data(self, **kwargs):
context = {
......
......@@ -14,7 +14,7 @@ coreapi==2.3.3
coreschema==0.0.4
cryptography==2.1.4
decorator==4.1.2
Django==1.11
Django==2.0.7
django-auth-ldap==1.3.0
django-bootstrap3==9.1.0
django-celery-beat==1.1.1
......@@ -24,7 +24,7 @@ django-ranged-response==0.2.0
django-redis-cache==1.7.1
django-rest-swagger==2.1.2
django-simple-captcha==0.5.6
djangorestframework==3.7.3
djangorestframework==3.8.2
djangorestframework-bulk==0.2.1
docutils==0.14
ecdsa==0.13
......@@ -69,3 +69,4 @@ sshpubkeys==2.2.0
uritemplate==3.0.0
urllib3==1.22
vine==1.1.4
drf-yasg==1.9.1
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment