mirror of https://github.com/jumpserver/jumpserver
Merge remote-tracking branch 'origin/v3' into v3
commit
b12f83dbea
|
@ -120,6 +120,13 @@ class BitChoicesField(TreeChoicesMixin, serializers.MultipleChoiceField):
|
||||||
super().__init__(choices=choices, **kwargs)
|
super().__init__(choices=choices, **kwargs)
|
||||||
|
|
||||||
def to_representation(self, value):
|
def to_representation(self, value):
|
||||||
|
if isinstance(value, list) and len(value) == 1:
|
||||||
|
# Swagger 会使用 field.choices.keys() 迭代传递进来
|
||||||
|
return [
|
||||||
|
{"value": c.name, "label": c.label}
|
||||||
|
for c in self._choice_cls
|
||||||
|
if c.name == value[0]
|
||||||
|
]
|
||||||
return [
|
return [
|
||||||
{"value": c.name, "label": c.label}
|
{"value": c.name, "label": c.label}
|
||||||
for c in self._choice_cls
|
for c in self._choice_cls
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
|
||||||
def bit(x):
|
def bit(x):
|
||||||
return 2 ** (x - 1)
|
if x == 0:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return 2 ** (x - 1)
|
||||||
|
|
|
@ -1,53 +1,23 @@
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework.generics import ListAPIView, get_object_or_404
|
from rest_framework.generics import ListAPIView, get_object_or_404
|
||||||
|
|
||||||
from common.permissions import IsValidUser
|
from common.utils import get_logger
|
||||||
from common.utils import get_logger, lazyproperty
|
|
||||||
from assets.serializers import AccountSerializer
|
|
||||||
from perms.hands import User, Asset, Account
|
|
||||||
from perms import serializers
|
from perms import serializers
|
||||||
|
from perms.hands import Asset
|
||||||
from perms.utils import PermAccountUtil
|
from perms.utils import PermAccountUtil
|
||||||
from .mixin import RoleAdminMixin, RoleUserMixin
|
from .mixin import SelfOrPKUserMixin
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'UserAllGrantedAccountsApi',
|
|
||||||
'MyAllGrantedAccountsApi',
|
|
||||||
'UserGrantedAssetAccountsApi',
|
'UserGrantedAssetAccountsApi',
|
||||||
'MyGrantedAssetAccountsApi',
|
|
||||||
'UserGrantedAssetSpecialAccountsApi',
|
|
||||||
'MyGrantedAssetSpecialAccountsApi',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class UserAllGrantedAccountsApi(RoleAdminMixin, ListAPIView):
|
class UserGrantedAssetAccountsApi(SelfOrPKUserMixin, ListAPIView):
|
||||||
""" 授权给用户的所有账号列表 """
|
|
||||||
serializer_class = AccountSerializer
|
|
||||||
filterset_fields = ("name", "username", "privileged", "version")
|
|
||||||
search_fields = filterset_fields
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
util = PermAccountUtil()
|
|
||||||
accounts = util.get_perm_accounts_for_user(self.user)
|
|
||||||
return accounts
|
|
||||||
|
|
||||||
|
|
||||||
class MyAllGrantedAccountsApi(RoleUserMixin, UserAllGrantedAccountsApi):
|
|
||||||
""" 授权给我的所有账号列表 """
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class UserGrantedAssetAccountsApi(ListAPIView):
|
|
||||||
serializer_class = serializers.AccountsGrantedSerializer
|
serializer_class = serializers.AccountsGrantedSerializer
|
||||||
|
|
||||||
@lazyproperty
|
@property
|
||||||
def user(self) -> User:
|
|
||||||
user_id = self.kwargs.get('pk')
|
|
||||||
return User.objects.get(id=user_id)
|
|
||||||
|
|
||||||
@lazyproperty
|
|
||||||
def asset(self):
|
def asset(self):
|
||||||
asset_id = self.kwargs.get('asset_id')
|
asset_id = self.kwargs.get('asset_id')
|
||||||
kwargs = {'id': asset_id, 'is_active': True}
|
kwargs = {'id': asset_id, 'is_active': True}
|
||||||
|
@ -55,41 +25,6 @@ class UserGrantedAssetAccountsApi(ListAPIView):
|
||||||
return asset
|
return asset
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
accounts = PermAccountUtil().get_perm_accounts_for_user_asset(
|
util = PermAccountUtil()
|
||||||
self.user, self.asset, with_actions=True
|
accounts = util.get_permed_accounts_for_user(self.user, self.asset)
|
||||||
)
|
|
||||||
return accounts
|
return accounts
|
||||||
|
|
||||||
|
|
||||||
class MyGrantedAssetAccountsApi(UserGrantedAssetAccountsApi):
|
|
||||||
permission_classes = (IsValidUser,)
|
|
||||||
|
|
||||||
@lazyproperty
|
|
||||||
def user(self):
|
|
||||||
return self.request.user
|
|
||||||
|
|
||||||
|
|
||||||
class UserGrantedAssetSpecialAccountsApi(ListAPIView):
|
|
||||||
serializer_class = serializers.AccountsGrantedSerializer
|
|
||||||
|
|
||||||
@lazyproperty
|
|
||||||
def user(self):
|
|
||||||
return self.request.user
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
# 构造默认包含的账号,如: @INPUT @USER
|
|
||||||
accounts = [
|
|
||||||
Account.get_manual_account(),
|
|
||||||
Account.get_user_account(self.user.username)
|
|
||||||
]
|
|
||||||
for account in accounts:
|
|
||||||
account.actions = Action.ALL
|
|
||||||
return accounts
|
|
||||||
|
|
||||||
|
|
||||||
class MyGrantedAssetSpecialAccountsApi(UserGrantedAssetSpecialAccountsApi):
|
|
||||||
permission_classes = (IsValidUser,)
|
|
||||||
|
|
||||||
@lazyproperty
|
|
||||||
def user(self):
|
|
||||||
return self.request.user
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
from rest_framework.generics import ListAPIView
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from rest_framework.generics import ListAPIView
|
||||||
|
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from ..mixin import AssetRoleAdminMixin, AssetRoleUserMixin
|
|
||||||
from .mixin import (
|
from .mixin import (
|
||||||
UserAllGrantedAssetsQuerysetMixin, UserDirectGrantedAssetsQuerysetMixin, UserFavoriteGrantedAssetsMixin,
|
UserAllGrantedAssetsQuerysetMixin, UserDirectGrantedAssetsQuerysetMixin, UserFavoriteGrantedAssetsMixin,
|
||||||
UserGrantedNodeAssetsMixin, AssetsSerializerFormatMixin, AssetsTreeFormatMixin,
|
UserGrantedNodeAssetsMixin, AssetsSerializerFormatMixin, AssetsTreeFormatMixin,
|
||||||
)
|
)
|
||||||
|
from ..mixin import AssetRoleAdminMixin, AssetRoleUserMixin
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'UserDirectGrantedAssetsApi', 'MyDirectGrantedAssetsApi',
|
'UserDirectGrantedAssetsApi', 'MyDirectGrantedAssetsApi',
|
||||||
|
@ -14,7 +14,8 @@ __all__ = [
|
||||||
'MyFavoriteGrantedAssetsApi', 'UserDirectGrantedAssetsAsTreeApi',
|
'MyFavoriteGrantedAssetsApi', 'UserDirectGrantedAssetsAsTreeApi',
|
||||||
'MyUngroupAssetsAsTreeApi',
|
'MyUngroupAssetsAsTreeApi',
|
||||||
'UserAllGrantedAssetsApi', 'MyAllGrantedAssetsApi', 'MyAllAssetsAsTreeApi',
|
'UserAllGrantedAssetsApi', 'MyAllGrantedAssetsApi', 'MyAllAssetsAsTreeApi',
|
||||||
'UserGrantedNodeAssetsApi', 'MyGrantedNodeAssetsApi',
|
'UserGrantedNodeAssetsApi',
|
||||||
|
'MyGrantedNodeAssetsApi',
|
||||||
]
|
]
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
|
|
||||||
from common.http import is_true
|
from common.http import is_true
|
||||||
from common.mixins.api import RoleAdminMixin, RoleUserMixin
|
from common.mixins.api import RoleAdminMixin, RoleUserMixin
|
||||||
from perms.utils.user_permission import UserGrantedTreeRefreshController
|
from perms.utils.user_permission import UserGrantedTreeRefreshController
|
||||||
|
from rbac.permissions import RBACPermission
|
||||||
from users.models import User
|
from users.models import User
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,3 +36,43 @@ class AssetRoleUserMixin(RebuildTreeMixin, RoleUserMixin):
|
||||||
('get_tree', 'perms.view_myassets'),
|
('get_tree', 'perms.view_myassets'),
|
||||||
('GET', 'perms.view_myassets'),
|
('GET', 'perms.view_myassets'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SelfOrPKUserMixin:
|
||||||
|
kwargs: dict
|
||||||
|
request: Request
|
||||||
|
permission_classes = (RBACPermission,)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def self_rbac_perms(self):
|
||||||
|
return (
|
||||||
|
('list', 'perms.view_myassets'),
|
||||||
|
('retrieve', 'perms.view_myassets'),
|
||||||
|
('get_tree', 'perms.view_myassets'),
|
||||||
|
('GET', 'perms.view_myassets'),
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def admin_rbac_perms(self):
|
||||||
|
return (
|
||||||
|
('list', 'perms.view_userassets'),
|
||||||
|
('retrieve', 'perms.view_userassets'),
|
||||||
|
('get_tree', 'perms.view_userassets'),
|
||||||
|
('GET', 'perms.view_userassets'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_rbac_perms(self):
|
||||||
|
if self.request_user_is_self():
|
||||||
|
return self.self_rbac_perms
|
||||||
|
else:
|
||||||
|
return self.admin_rbac_perms
|
||||||
|
|
||||||
|
def request_user_is_self(self):
|
||||||
|
return self.kwargs.get('user') in ['my', 'self']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def user(self):
|
||||||
|
if self.request_user_is_self():
|
||||||
|
return self.request.user
|
||||||
|
else:
|
||||||
|
return get_object_or_404(User, pk=self.kwargs.get('user'))
|
||||||
|
|
|
@ -10,11 +10,11 @@ __all__ = ["SpecialAccount", "ActionChoices"]
|
||||||
|
|
||||||
|
|
||||||
class ActionChoices(BitChoices):
|
class ActionChoices(BitChoices):
|
||||||
connect = bit(0), _("Connect")
|
connect = bit(1), _("Connect")
|
||||||
upload = bit(1), _("Upload")
|
upload = bit(2), _("Upload")
|
||||||
download = bit(2), _("Download")
|
download = bit(3), _("Download")
|
||||||
copy = bit(3), _("Copy")
|
copy = bit(4), _("Copy")
|
||||||
paste = bit(4), _("Paste")
|
paste = bit(5), _("Paste")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_tree(cls):
|
def is_tree(cls):
|
||||||
|
|
|
@ -16,7 +16,7 @@ __all__ = ["AssetPermissionSerializer", "ActionChoicesField"]
|
||||||
|
|
||||||
class ActionChoicesField(BitChoicesField):
|
class ActionChoicesField(BitChoicesField):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(ActionChoices, **kwargs)
|
super().__init__(choice_cls=ActionChoices, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class AssetPermissionSerializer(BulkOrgResourceModelSerializer):
|
class AssetPermissionSerializer(BulkOrgResourceModelSerializer):
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
from rest_framework import serializers
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
from common.drf.fields import ObjectRelatedField, LabeledChoiceField
|
|
||||||
from assets.models import Node, Asset, Platform, Account
|
|
||||||
from assets.const import Category, AllTypes
|
from assets.const import Category, AllTypes
|
||||||
|
from assets.models import Node, Asset, Platform, Account
|
||||||
|
from common.drf.fields import ObjectRelatedField, LabeledChoiceField
|
||||||
from perms.serializers.permission import ActionChoicesField
|
from perms.serializers.permission import ActionChoicesField
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
@ -49,13 +49,9 @@ class ActionsSerializer(serializers.Serializer):
|
||||||
|
|
||||||
|
|
||||||
class AccountsGrantedSerializer(serializers.ModelSerializer):
|
class AccountsGrantedSerializer(serializers.ModelSerializer):
|
||||||
""" 授权的账号序列类 """
|
|
||||||
|
|
||||||
# Todo: 添加前端登录逻辑中需要的一些字段,比如:是否需要手动输入密码
|
|
||||||
# need_manual = serializers.BooleanField(label=_('Need manual input'))
|
|
||||||
actions = ActionChoicesField(read_only=True)
|
actions = ActionChoicesField(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Account
|
model = Account
|
||||||
fields = ['id', 'name', 'username', 'actions']
|
fields = ['id', 'name', 'username', 'secret_type', 'has_secret', 'actions']
|
||||||
read_only_fields = fields
|
read_only_fields = fields
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
# coding:utf-8
|
# coding:utf-8
|
||||||
|
|
||||||
from .asset_permission import asset_permission_urlpatterns
|
from .asset_permission import asset_permission_urlpatterns
|
||||||
|
from .user_permission import user_permission_urlpatterns
|
||||||
|
|
||||||
app_name = 'perms'
|
app_name = 'perms'
|
||||||
|
|
||||||
urlpatterns = []
|
urlpatterns = asset_permission_urlpatterns \
|
||||||
urlpatterns += asset_permission_urlpatterns
|
+ user_permission_urlpatterns
|
||||||
|
|
|
@ -7,81 +7,14 @@ from .. import api
|
||||||
|
|
||||||
router = BulkRouter()
|
router = BulkRouter()
|
||||||
router.register('asset-permissions', api.AssetPermissionViewSet, 'asset-permission')
|
router.register('asset-permissions', api.AssetPermissionViewSet, 'asset-permission')
|
||||||
router.register('asset-permissions-users-relations', api.AssetPermissionUserRelationViewSet, 'asset-permissions-users-relation')
|
router.register('asset-permissions-users-relations', api.AssetPermissionUserRelationViewSet,
|
||||||
router.register('asset-permissions-user-groups-relations', api.AssetPermissionUserGroupRelationViewSet, 'asset-permissions-user-groups-relation')
|
'asset-permissions-users-relation')
|
||||||
router.register('asset-permissions-assets-relations', api.AssetPermissionAssetRelationViewSet, 'asset-permissions-assets-relation')
|
router.register('asset-permissions-user-groups-relations', api.AssetPermissionUserGroupRelationViewSet,
|
||||||
router.register('asset-permissions-nodes-relations', api.AssetPermissionNodeRelationViewSet, 'asset-permissions-nodes-relation')
|
'asset-permissions-user-groups-relation')
|
||||||
|
router.register('asset-permissions-assets-relations', api.AssetPermissionAssetRelationViewSet,
|
||||||
user_permission_urlpatterns = [
|
'asset-permissions-assets-relation')
|
||||||
# 以 serializer 格式返回
|
router.register('asset-permissions-nodes-relations', api.AssetPermissionNodeRelationViewSet,
|
||||||
path('<uuid:pk>/assets/', api.UserAllGrantedAssetsApi.as_view(), name='user-assets'),
|
'asset-permissions-nodes-relation')
|
||||||
path('assets/', api.MyAllGrantedAssetsApi.as_view(), name='my-assets'),
|
|
||||||
# Tree Node 的数据格式返回
|
|
||||||
path('<uuid:pk>/assets/tree/', api.UserDirectGrantedAssetsAsTreeApi.as_view(), name='user-assets-as-tree'),
|
|
||||||
path('assets/tree/', api.MyAllAssetsAsTreeApi.as_view(), name='my-assets-as-tree'),
|
|
||||||
path('ungroup/assets/tree/', api.MyUngroupAssetsAsTreeApi.as_view(), name='my-ungroup-assets-as-tree'),
|
|
||||||
|
|
||||||
# 获取用户所有`直接授权的节点`与`直接授权资产`关联的节点
|
|
||||||
# 以 serializer 格式返回
|
|
||||||
path('<uuid:pk>/nodes/', api.UserGrantedNodesApi.as_view(), name='user-nodes'),
|
|
||||||
path('nodes/', api.MyGrantedNodesApi.as_view(), name='my-nodes'),
|
|
||||||
# 以 Tree Node 的数据格式返回
|
|
||||||
path('<uuid:pk>/nodes/tree/', api.MyGrantedNodesAsTreeApi.as_view(), name='user-nodes-as-tree'),
|
|
||||||
path('nodes/tree/', api.MyGrantedNodesAsTreeApi.as_view(), name='my-nodes-as-tree'),
|
|
||||||
|
|
||||||
# 一层一层的获取用户授权的节点,
|
|
||||||
# 以 Serializer 的数据格式返回
|
|
||||||
path('<uuid:pk>/nodes/children/', api.UserGrantedNodeChildrenForAdminApi.as_view(), name='user-nodes-children'),
|
|
||||||
path('nodes/children/', api.MyGrantedNodeChildrenApi.as_view(), name='my-nodes-children'),
|
|
||||||
# 以 Tree Node 的数据格式返回
|
|
||||||
path('<uuid:pk>/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeForAdminApi.as_view(), name='user-nodes-children-as-tree'),
|
|
||||||
# 部分调用位置
|
|
||||||
# - 普通用户 -> 我的资产 -> 展开节点 时调用
|
|
||||||
path('nodes/children/tree/', api.MyGrantedNodeChildrenAsTreeApi.as_view(), name='my-nodes-children-as-tree'),
|
|
||||||
|
|
||||||
# 此接口会返回整棵树
|
|
||||||
# 普通用户 -> 命令执行 -> 左侧树
|
|
||||||
path('nodes-with-assets/tree/', api.MyGrantedNodesWithAssetsAsTreeApi.as_view(), name='my-nodes-with-assets-as-tree'),
|
|
||||||
|
|
||||||
# 主要用于 luna 页面,带资产的节点树
|
|
||||||
path('<uuid:pk>/nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='user-nodes-children-with-assets-as-tree'),
|
|
||||||
path('nodes/children-with-assets/tree/', api.MyGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='my-nodes-children-with-assets-as-tree'),
|
|
||||||
|
|
||||||
# 查询授权树上某个节点的所有资产
|
|
||||||
path('<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'),
|
|
||||||
path('nodes/<uuid:node_id>/assets/', api.MyGrantedNodeAssetsApi.as_view(), name='my-node-assets'),
|
|
||||||
|
|
||||||
# 未分组的资产
|
|
||||||
path('<uuid:pk>/nodes/ungrouped/assets/', api.UserDirectGrantedAssetsApi.as_view(), name='user-ungrouped-assets'),
|
|
||||||
path('nodes/ungrouped/assets/', api.MyDirectGrantedAssetsApi.as_view(), name='my-ungrouped-assets'),
|
|
||||||
|
|
||||||
# 收藏的资产
|
|
||||||
path('<uuid:pk>/nodes/favorite/assets/', api.UserFavoriteGrantedAssetsApi.as_view(), name='user-ungrouped-assets'),
|
|
||||||
path('nodes/favorite/assets/', api.MyFavoriteGrantedAssetsApi.as_view(), name='my-ungrouped-assets'),
|
|
||||||
|
|
||||||
# 获取授权给用户的所有账号
|
|
||||||
path('<uuid:pk>/accounts/', api.UserAllGrantedAccountsApi.as_view(), name='user-accounts'),
|
|
||||||
path('accounts/', api.MyAllGrantedAccountsApi.as_view(), name='my-accounts'),
|
|
||||||
|
|
||||||
# 获取授权给用户某个资产的所有账号
|
|
||||||
path('<uuid:pk>/assets/<uuid:asset_id>/accounts/', api.UserGrantedAssetAccountsApi.as_view(), name='user-asset-accounts'),
|
|
||||||
path('assets/<uuid:asset_id>/accounts/', api.MyGrantedAssetAccountsApi.as_view(), name='my-asset-accounts'),
|
|
||||||
# 用户登录资产的特殊账号, @INPUT, @USER 等
|
|
||||||
path('<uuid:pk>/assets/special-accounts/', api.UserGrantedAssetSpecialAccountsApi.as_view(), name='user-special-accounts'),
|
|
||||||
path('assets/special-accounts/', api.MyGrantedAssetSpecialAccountsApi.as_view(), name='my-special-accounts'),
|
|
||||||
]
|
|
||||||
|
|
||||||
user_group_permission_urlpatterns = [
|
|
||||||
# 查询某个用户组授权的资产和资产组
|
|
||||||
path('<uuid:pk>/assets/', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'),
|
|
||||||
path('<uuid:pk>/nodes/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'),
|
|
||||||
path('<uuid:pk>/nodes/children/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes-children'),
|
|
||||||
path('<uuid:pk>/nodes/children/tree/', api.UserGroupGrantedNodeChildrenAsTreeApi.as_view(), name='user-group-nodes-children-as-tree'),
|
|
||||||
path('<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), name='user-group-node-assets'),
|
|
||||||
|
|
||||||
# 获取所有和资产-用户组关联的账号列表
|
|
||||||
path('<uuid:pk>/assets/<uuid:asset_id>/accounts/', api.UserGroupGrantedAssetAccountsApi.as_view(), name='user-group-asset-accounts'),
|
|
||||||
]
|
|
||||||
|
|
||||||
permission_urlpatterns = [
|
permission_urlpatterns = [
|
||||||
# 授权规则中授权的资产
|
# 授权规则中授权的资产
|
||||||
|
@ -92,8 +25,6 @@ permission_urlpatterns = [
|
||||||
|
|
||||||
asset_permission_urlpatterns = [
|
asset_permission_urlpatterns = [
|
||||||
# Assets
|
# Assets
|
||||||
path('users/', include(user_permission_urlpatterns)),
|
|
||||||
path('user-groups/', include(user_group_permission_urlpatterns)),
|
|
||||||
path('asset-permissions/', include(permission_urlpatterns)),
|
path('asset-permissions/', include(permission_urlpatterns)),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
from django.urls import path, include
|
||||||
|
|
||||||
|
from .. import api
|
||||||
|
|
||||||
|
user_permission_urlpatterns = [
|
||||||
|
# 以 serializer 格式返回
|
||||||
|
path('<uuid:pk>/assets/', api.UserAllGrantedAssetsApi.as_view(), name='user-assets'),
|
||||||
|
path('assets/', api.MyAllGrantedAssetsApi.as_view(), name='my-assets'),
|
||||||
|
# Tree Node 的数据格式返回
|
||||||
|
path('<uuid:pk>/assets/tree/', api.UserDirectGrantedAssetsAsTreeApi.as_view(), name='user-assets-as-tree'),
|
||||||
|
path('assets/tree/', api.MyAllAssetsAsTreeApi.as_view(), name='my-assets-as-tree'),
|
||||||
|
path('ungroup/assets/tree/', api.MyUngroupAssetsAsTreeApi.as_view(), name='my-ungroup-assets-as-tree'),
|
||||||
|
|
||||||
|
# 获取用户所有`直接授权的节点`与`直接授权资产`关联的节点
|
||||||
|
# 以 serializer 格式返回
|
||||||
|
path('<uuid:pk>/nodes/', api.UserGrantedNodesApi.as_view(), name='user-nodes'),
|
||||||
|
path('nodes/', api.MyGrantedNodesApi.as_view(), name='my-nodes'),
|
||||||
|
# 以 Tree Node 的数据格式返回
|
||||||
|
path('<uuid:pk>/nodes/tree/', api.MyGrantedNodesAsTreeApi.as_view(), name='user-nodes-as-tree'),
|
||||||
|
path('nodes/tree/', api.MyGrantedNodesAsTreeApi.as_view(), name='my-nodes-as-tree'),
|
||||||
|
|
||||||
|
# 一层一层的获取用户授权的节点,
|
||||||
|
# 以 Serializer 的数据格式返回
|
||||||
|
path('<uuid:pk>/nodes/children/', api.UserGrantedNodeChildrenForAdminApi.as_view(), name='user-nodes-children'),
|
||||||
|
path('nodes/children/', api.MyGrantedNodeChildrenApi.as_view(), name='my-nodes-children'),
|
||||||
|
# 以 Tree Node 的数据格式返回
|
||||||
|
path('<uuid:pk>/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeForAdminApi.as_view(),
|
||||||
|
name='user-nodes-children-as-tree'),
|
||||||
|
# 部分调用位置
|
||||||
|
# - 普通用户 -> 我的资产 -> 展开节点 时调用
|
||||||
|
path('nodes/children/tree/', api.MyGrantedNodeChildrenAsTreeApi.as_view(), name='my-nodes-children-as-tree'),
|
||||||
|
|
||||||
|
# 此接口会返回整棵树
|
||||||
|
# 普通用户 -> 命令执行 -> 左侧树
|
||||||
|
path('nodes-with-assets/tree/', api.MyGrantedNodesWithAssetsAsTreeApi.as_view(),
|
||||||
|
name='my-nodes-with-assets-as-tree'),
|
||||||
|
|
||||||
|
# 主要用于 luna 页面,带资产的节点树
|
||||||
|
path('<uuid:pk>/nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(),
|
||||||
|
name='user-nodes-children-with-assets-as-tree'),
|
||||||
|
path('nodes/children-with-assets/tree/', api.MyGrantedNodeChildrenWithAssetsAsTreeApi.as_view(),
|
||||||
|
name='my-nodes-children-with-assets-as-tree'),
|
||||||
|
|
||||||
|
# 查询授权树上某个节点的所有资产
|
||||||
|
path('<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'),
|
||||||
|
path('nodes/<uuid:node_id>/assets/', api.MyGrantedNodeAssetsApi.as_view(), name='my-node-assets'),
|
||||||
|
|
||||||
|
# 未分组的资产
|
||||||
|
path('<uuid:pk>/nodes/ungrouped/assets/', api.UserDirectGrantedAssetsApi.as_view(), name='user-ungrouped-assets'),
|
||||||
|
path('nodes/ungrouped/assets/', api.MyDirectGrantedAssetsApi.as_view(), name='my-ungrouped-assets'),
|
||||||
|
|
||||||
|
# 收藏的资产
|
||||||
|
path('<uuid:pk>/nodes/favorite/assets/', api.UserFavoriteGrantedAssetsApi.as_view(), name='user-ungrouped-assets'),
|
||||||
|
path('nodes/favorite/assets/', api.MyFavoriteGrantedAssetsApi.as_view(),
|
||||||
|
name='my-ungrouped-assets'),
|
||||||
|
|
||||||
|
# 获取授权给用户某个资产的所有账号
|
||||||
|
path('<str:user>/assets/<uuid:asset_id>/accounts/', api.UserGrantedAssetAccountsApi.as_view(),
|
||||||
|
name='user-asset-accounts'),
|
||||||
|
]
|
||||||
|
|
||||||
|
user_group_permission_urlpatterns = [
|
||||||
|
# 查询某个用户组授权的资产和资产组
|
||||||
|
path('<uuid:pk>/assets/', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'),
|
||||||
|
path('<uuid:pk>/nodes/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'),
|
||||||
|
path('<uuid:pk>/nodes/children/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes-children'),
|
||||||
|
path('<uuid:pk>/nodes/children/tree/', api.UserGroupGrantedNodeChildrenAsTreeApi.as_view(),
|
||||||
|
name='user-group-nodes-children-as-tree'),
|
||||||
|
path('<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGroupGrantedNodeAssetsApi.as_view(),
|
||||||
|
name='user-group-node-assets'),
|
||||||
|
|
||||||
|
# 获取所有和资产-用户组关联的账号列表
|
||||||
|
path('<uuid:pk>/assets/<uuid:asset_id>/accounts/', api.UserGroupGrantedAssetAccountsApi.as_view(),
|
||||||
|
name='user-group-asset-accounts'),
|
||||||
|
]
|
||||||
|
|
||||||
|
user_permission_urlpatterns = [
|
||||||
|
path('users/', include(user_permission_urlpatterns)),
|
||||||
|
path('user-groups/', include(user_group_permission_urlpatterns)),
|
||||||
|
]
|
|
@ -1,30 +1,31 @@
|
||||||
|
import time
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import List, Tuple
|
from typing import List, Tuple
|
||||||
import time
|
|
||||||
|
|
||||||
from django.core.cache import cache
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.cache import cache
|
||||||
from django.db.models import Q, QuerySet
|
from django.db.models import Q, QuerySet
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from common.db.models import output_as_string, UnionQuerySet
|
from assets.models import (
|
||||||
from common.utils.common import lazyproperty, timeit
|
Asset, FavoriteAsset, AssetQuerySet, NodeQuerySet
|
||||||
|
)
|
||||||
from assets.utils import NodeAssetsUtil
|
from assets.utils import NodeAssetsUtil
|
||||||
from common.utils import get_logger
|
from common.db.models import output_as_string, UnionQuerySet
|
||||||
from common.decorator import on_transaction_commit
|
from common.decorator import on_transaction_commit
|
||||||
|
from common.utils import get_logger
|
||||||
|
from common.utils.common import lazyproperty, timeit
|
||||||
|
from orgs.models import Organization
|
||||||
from orgs.utils import (
|
from orgs.utils import (
|
||||||
tmp_to_org, current_org,
|
tmp_to_org, current_org,
|
||||||
ensure_in_real_or_default_org, tmp_to_root_org
|
ensure_in_real_or_default_org, tmp_to_root_org
|
||||||
)
|
)
|
||||||
from assets.models import (
|
|
||||||
Asset, FavoriteAsset, AssetQuerySet, NodeQuerySet
|
|
||||||
)
|
|
||||||
from users.models import User
|
|
||||||
from orgs.models import Organization
|
|
||||||
from perms.locks import UserGrantedTreeRebuildLock
|
from perms.locks import UserGrantedTreeRebuildLock
|
||||||
from perms.models import (
|
from perms.models import (
|
||||||
AssetPermission, PermNode, UserAssetGrantedTreeNodeRelation
|
AssetPermission, PermNode, UserAssetGrantedTreeNodeRelation
|
||||||
)
|
)
|
||||||
|
from users.models import User
|
||||||
|
|
||||||
NodeFrom = UserAssetGrantedTreeNodeRelation.NodeFrom
|
NodeFrom = UserAssetGrantedTreeNodeRelation.NodeFrom
|
||||||
NODE_ONLY_FIELDS = ('id', 'key', 'parent_key', 'org_id')
|
NODE_ONLY_FIELDS = ('id', 'key', 'parent_key', 'org_id')
|
||||||
|
|
||||||
|
@ -119,8 +120,7 @@ class UserGrantedTreeRefreshController:
|
||||||
key = cls.key_template.format(user_id=user_id)
|
key = cls.key_template.format(user_id=user_id)
|
||||||
p.srem(key, *org_ids)
|
p.srem(key, *org_ids)
|
||||||
p.execute()
|
p.execute()
|
||||||
logger.info(f'Remove orgs from users built tree: users:{user_ids} '
|
logger.info(f'Remove orgs from users built tree: users:{user_ids} orgs:{org_ids}')
|
||||||
f'orgs:{org_ids}')
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_need_refresh_orgs_for_users(cls, org_ids, user_ids):
|
def add_need_refresh_orgs_for_users(cls, org_ids, user_ids):
|
||||||
|
@ -205,28 +205,30 @@ class UserGrantedTreeRefreshController:
|
||||||
user = self.user
|
user = self.user
|
||||||
|
|
||||||
with tmp_to_root_org():
|
with tmp_to_root_org():
|
||||||
UserAssetGrantedTreeNodeRelation.objects.filter(user=user)\
|
UserAssetGrantedTreeNodeRelation.objects.filter(user=user) \
|
||||||
.exclude(org_id__in=self.org_ids)\
|
.exclude(org_id__in=self.org_ids) \
|
||||||
.delete()
|
.delete()
|
||||||
|
|
||||||
if force or self.have_need_refresh_orgs():
|
if not force and not self.have_need_refresh_orgs():
|
||||||
with UserGrantedTreeRebuildLock(user_id=user.id):
|
return
|
||||||
if force:
|
|
||||||
orgs = self.orgs
|
|
||||||
self.set_all_orgs_as_built()
|
|
||||||
else:
|
|
||||||
orgs = self.get_need_refresh_orgs_and_fill_up()
|
|
||||||
|
|
||||||
for org in orgs:
|
with UserGrantedTreeRebuildLock(user_id=user.id):
|
||||||
with tmp_to_org(org):
|
if force:
|
||||||
t_start = time.time()
|
orgs = self.orgs
|
||||||
logger.info(f'Rebuild user tree: user={self.user} org={current_org}')
|
self.set_all_orgs_as_built()
|
||||||
utils = UserGrantedTreeBuildUtils(user)
|
else:
|
||||||
utils.rebuild_user_granted_tree()
|
orgs = self.get_need_refresh_orgs_and_fill_up()
|
||||||
logger.info(
|
|
||||||
f'Rebuild user tree ok: cost={time.time() - t_start} '
|
for org in orgs:
|
||||||
f'user={self.user} org={current_org}'
|
with tmp_to_org(org):
|
||||||
)
|
t_start = time.time()
|
||||||
|
logger.info(f'Rebuild user tree: user={self.user} org={current_org}')
|
||||||
|
utils = UserGrantedTreeBuildUtils(user)
|
||||||
|
utils.rebuild_user_granted_tree()
|
||||||
|
logger.info(
|
||||||
|
f'Rebuild user tree ok: cost={time.time() - t_start} '
|
||||||
|
f'user={self.user} org={current_org}'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class UserGrantedUtilsBase:
|
class UserGrantedUtilsBase:
|
||||||
|
@ -427,8 +429,8 @@ class UserGrantedTreeBuildUtils(UserGrantedUtilsBase):
|
||||||
for node_id, asset_id in node_asset_pairs:
|
for node_id, asset_id in node_asset_pairs:
|
||||||
if node_id not in node_id_key_mapper:
|
if node_id not in node_id_key_mapper:
|
||||||
continue
|
continue
|
||||||
nkey = node_id_key_mapper[node_id]
|
node_key = node_id_key_mapper[node_id]
|
||||||
nodekey_assetsid_mapper[nkey].add(asset_id)
|
nodekey_assetsid_mapper[node_key].add(asset_id)
|
||||||
|
|
||||||
util = NodeAssetsUtil(nodes, nodekey_assetsid_mapper)
|
util = NodeAssetsUtil(nodes, nodekey_assetsid_mapper)
|
||||||
util.generate()
|
util.generate()
|
||||||
|
@ -604,7 +606,10 @@ class UserGrantedNodesQueryUtils(UserGrantedUtilsBase):
|
||||||
def get_top_level_nodes(self):
|
def get_top_level_nodes(self):
|
||||||
nodes = self.get_special_nodes()
|
nodes = self.get_special_nodes()
|
||||||
real_nodes = self.get_indirect_granted_node_children('')
|
real_nodes = self.get_indirect_granted_node_children('')
|
||||||
nodes.extend(self.sort(real_nodes))
|
nodes.extend(real_nodes)
|
||||||
|
if len(real_nodes) == 1:
|
||||||
|
children = self.get_node_children(real_nodes[0].key)
|
||||||
|
nodes.extend(children)
|
||||||
return nodes
|
return nodes
|
||||||
|
|
||||||
def get_ungrouped_node(self):
|
def get_ungrouped_node(self):
|
||||||
|
@ -649,11 +654,9 @@ class UserGrantedNodesQueryUtils(UserGrantedUtilsBase):
|
||||||
:param with_special:
|
:param with_special:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
nodes = PermNode.objects.filter(
|
nodes = PermNode.objects.filter(granted_node_rels__user=self.user) \
|
||||||
granted_node_rels__user=self.user
|
.annotate(**PermNode.annotate_granted_node_rel_fields) \
|
||||||
).annotate(
|
.distinct()
|
||||||
**PermNode.annotate_granted_node_rel_fields
|
|
||||||
).distinct()
|
|
||||||
|
|
||||||
key_to_node_mapper = {}
|
key_to_node_mapper = {}
|
||||||
nodes_descendant_q = Q()
|
nodes_descendant_q = Q()
|
||||||
|
|
|
@ -2,16 +2,15 @@ from rest_framework import viewsets
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from common.permissions import IsServiceAccount
|
|
||||||
from common.drf.api import JMSModelViewSet
|
from common.drf.api import JMSModelViewSet
|
||||||
|
from common.permissions import IsServiceAccount
|
||||||
from orgs.utils import tmp_to_builtin_org
|
from orgs.utils import tmp_to_builtin_org
|
||||||
|
from terminal.models import AppletHost, AppletHostDeployment
|
||||||
from terminal.serializers import (
|
from terminal.serializers import (
|
||||||
AppletHostSerializer, AppletHostDeploymentSerializer,
|
AppletHostSerializer, AppletHostDeploymentSerializer,
|
||||||
AppletHostStartupSerializer
|
AppletHostStartupSerializer, AppletHostDeployAppletSerializer
|
||||||
)
|
)
|
||||||
from terminal.models import AppletHost, AppletHostDeployment
|
from terminal.tasks import run_applet_host_deployment, run_applet_host_deployment_install_applet
|
||||||
from terminal.tasks import run_applet_host_deployment
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['AppletHostViewSet', 'AppletHostDeploymentViewSet']
|
__all__ = ['AppletHostViewSet', 'AppletHostDeploymentViewSet']
|
||||||
|
|
||||||
|
@ -41,11 +40,24 @@ class AppletHostViewSet(JMSModelViewSet):
|
||||||
class AppletHostDeploymentViewSet(viewsets.ModelViewSet):
|
class AppletHostDeploymentViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = AppletHostDeploymentSerializer
|
serializer_class = AppletHostDeploymentSerializer
|
||||||
queryset = AppletHostDeployment.objects.all()
|
queryset = AppletHostDeployment.objects.all()
|
||||||
|
rbac_perms = (
|
||||||
|
('applets', 'terminal.view_AppletHostDeployment'),
|
||||||
|
)
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
serializer = self.get_serializer(data=request.data)
|
serializer = self.get_serializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
instance = serializer.save()
|
instance = serializer.save()
|
||||||
task = run_applet_host_deployment.delay(instance.id)
|
task = run_applet_host_deployment.delay(instance.id)
|
||||||
|
instance.save_task(task.id)
|
||||||
return Response({'task': str(task.id)}, status=201)
|
return Response({'task': str(task.id)}, status=201)
|
||||||
|
|
||||||
|
@action(methods=['post'], detail=False, serializer_class=AppletHostDeployAppletSerializer)
|
||||||
|
def applets(self, request, *args, **kwargs):
|
||||||
|
serializer = self.get_serializer(data=request.data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
applet_id = serializer.validated_data.get('applet_id')
|
||||||
|
instance = serializer.save()
|
||||||
|
task = run_applet_host_deployment_install_applet.delay(instance.id, applet_id)
|
||||||
|
instance.save_task(task.id)
|
||||||
|
return Response({'task': str(task.id)}, status=201)
|
||||||
|
|
|
@ -1,22 +1,23 @@
|
||||||
import os
|
|
||||||
import datetime
|
import datetime
|
||||||
import shutil
|
import os
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from django.utils import timezone
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from common.utils import get_logger
|
|
||||||
from common.db.utils import safe_db_connection
|
from common.db.utils import safe_db_connection
|
||||||
|
from common.utils import get_logger
|
||||||
from ops.ansible import PlaybookRunner, JMSInventory
|
from ops.ansible import PlaybookRunner, JMSInventory
|
||||||
|
from terminal.models import Applet, AppletHostDeployment
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
|
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
||||||
class DeployAppletHostManager:
|
class DeployAppletHostManager:
|
||||||
def __init__(self, deployment):
|
def __init__(self, deployment: AppletHostDeployment, applet: Applet = None):
|
||||||
self.deployment = deployment
|
self.deployment = deployment
|
||||||
|
self.applet = applet
|
||||||
self.run_dir = self.get_run_dir()
|
self.run_dir = self.get_run_dir()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -25,29 +26,56 @@ class DeployAppletHostManager:
|
||||||
now = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
|
now = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
|
||||||
return os.path.join(base, now)
|
return os.path.join(base, now)
|
||||||
|
|
||||||
def generate_playbook(self):
|
def run(self, **kwargs):
|
||||||
playbook_src = os.path.join(CURRENT_DIR, "playbook.yml")
|
self._run(self._run_initial_deploy, **kwargs)
|
||||||
base_site_url = settings.BASE_SITE_URL
|
|
||||||
|
def install_applet(self, **kwargs):
|
||||||
|
self._run(self._run_install_applet, **kwargs)
|
||||||
|
|
||||||
|
def _run_initial_deploy(self, **kwargs):
|
||||||
|
playbook = self.generate_initial_playbook
|
||||||
|
return self._run_playbook(playbook, **kwargs)
|
||||||
|
|
||||||
|
def _run_install_applet(self, **kwargs):
|
||||||
|
if self.applet:
|
||||||
|
generate_playbook = self.generate_install_applet_playbook
|
||||||
|
else:
|
||||||
|
generate_playbook = self.generate_install_all_playbook
|
||||||
|
return self._run_playbook(generate_playbook, **kwargs)
|
||||||
|
|
||||||
|
def generate_initial_playbook(self):
|
||||||
|
site_url = settings.SITE_URL
|
||||||
bootstrap_token = settings.BOOTSTRAP_TOKEN
|
bootstrap_token = settings.BOOTSTRAP_TOKEN
|
||||||
host_id = str(self.deployment.host.id)
|
host_id = str(self.deployment.host.id)
|
||||||
if not base_site_url:
|
if not site_url:
|
||||||
base_site_url = "http://localhost:8080"
|
site_url = "http://localhost:8080"
|
||||||
with open(playbook_src) as f:
|
options = self.deployment.host.deploy_options
|
||||||
plays = yaml.safe_load(f)
|
|
||||||
for play in plays:
|
|
||||||
play["vars"].update(self.deployment.host.deploy_options)
|
|
||||||
play["vars"]["DownloadHost"] = base_site_url + "/download"
|
|
||||||
play["vars"]["CORE_HOST"] = base_site_url
|
|
||||||
play["vars"]["BOOTSTRAP_TOKEN"] = bootstrap_token
|
|
||||||
play["vars"]["HOST_ID"] = host_id
|
|
||||||
play["vars"]["HOST_NAME"] = self.deployment.host.name
|
|
||||||
|
|
||||||
playbook_dir = os.path.join(self.run_dir, "playbook")
|
def handler(plays):
|
||||||
playbook_dst = os.path.join(playbook_dir, "main.yml")
|
for play in plays:
|
||||||
os.makedirs(playbook_dir, exist_ok=True)
|
play["vars"].update(options)
|
||||||
with open(playbook_dst, "w") as f:
|
play["vars"]["DownloadHost"] = site_url + "/download"
|
||||||
yaml.safe_dump(plays, f)
|
play["vars"]["CORE_HOST"] = site_url
|
||||||
return playbook_dst
|
play["vars"]["BOOTSTRAP_TOKEN"] = bootstrap_token
|
||||||
|
play["vars"]["HOST_ID"] = host_id
|
||||||
|
play["vars"]["HOST_NAME"] = self.deployment.host.name
|
||||||
|
|
||||||
|
return self._generate_playbook("playbook.yml", handler)
|
||||||
|
|
||||||
|
def generate_install_all_playbook(self):
|
||||||
|
return self._generate_playbook("install_all.yml")
|
||||||
|
|
||||||
|
def generate_install_applet_playbook(self):
|
||||||
|
applet_name = self.applet.name
|
||||||
|
options = self.deployment.host.deploy_options
|
||||||
|
|
||||||
|
def handler(plays):
|
||||||
|
for play in plays:
|
||||||
|
play["vars"].update(options)
|
||||||
|
play["vars"]["applet_name"] = applet_name
|
||||||
|
return plays
|
||||||
|
|
||||||
|
return self._generate_playbook("install_applet.yml", handler)
|
||||||
|
|
||||||
def generate_inventory(self):
|
def generate_inventory(self):
|
||||||
inventory = JMSInventory(
|
inventory = JMSInventory(
|
||||||
|
@ -58,18 +86,31 @@ class DeployAppletHostManager:
|
||||||
inventory.write_to_file(inventory_path)
|
inventory.write_to_file(inventory_path)
|
||||||
return inventory_path
|
return inventory_path
|
||||||
|
|
||||||
def _run(self, **kwargs):
|
def _generate_playbook(self, playbook_template_name, plays_handler: callable = None):
|
||||||
|
playbook_src = os.path.join(CURRENT_DIR, playbook_template_name)
|
||||||
|
with open(playbook_src) as f:
|
||||||
|
plays = yaml.safe_load(f)
|
||||||
|
if plays_handler:
|
||||||
|
plays = plays_handler(plays)
|
||||||
|
playbook_dir = os.path.join(self.run_dir, "playbook")
|
||||||
|
playbook_dst = os.path.join(playbook_dir, "main.yml")
|
||||||
|
os.makedirs(playbook_dir, exist_ok=True)
|
||||||
|
with open(playbook_dst, "w") as f:
|
||||||
|
yaml.safe_dump(plays, f)
|
||||||
|
return playbook_dst
|
||||||
|
|
||||||
|
def _run_playbook(self, generate_playbook: callable, **kwargs):
|
||||||
inventory = self.generate_inventory()
|
inventory = self.generate_inventory()
|
||||||
playbook = self.generate_playbook()
|
playbook = generate_playbook()
|
||||||
runner = PlaybookRunner(
|
runner = PlaybookRunner(
|
||||||
inventory=inventory, playbook=playbook, project_dir=self.run_dir
|
inventory=inventory, playbook=playbook, project_dir=self.run_dir
|
||||||
)
|
)
|
||||||
return runner.run(**kwargs)
|
return runner.run(**kwargs)
|
||||||
|
|
||||||
def run(self, **kwargs):
|
def _run(self, cb_func: callable, **kwargs):
|
||||||
try:
|
try:
|
||||||
self.deployment.date_start = timezone.now()
|
self.deployment.date_start = timezone.now()
|
||||||
cb = self._run(**kwargs)
|
cb = cb_func(**kwargs)
|
||||||
self.deployment.status = cb.status
|
self.deployment.status = cb.status
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Error: {}".format(e))
|
logger.error("Error: {}".format(e))
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
- hosts: all
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Install all applets
|
||||||
|
ansible.windows.win_shell:
|
||||||
|
"tinkerd install all"
|
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
- hosts: all
|
||||||
|
vars:
|
||||||
|
applet_name: chrome
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Install applet
|
||||||
|
ansible.windows.win_shell:
|
||||||
|
"tinkerd install --name {{ applet_name }}"
|
||||||
|
when: applet_name != 'all'
|
|
@ -3,7 +3,6 @@
|
||||||
- hosts: all
|
- hosts: all
|
||||||
vars:
|
vars:
|
||||||
DownloadHost: https://demo.jumpserver.org/download
|
DownloadHost: https://demo.jumpserver.org/download
|
||||||
Initial: 0
|
|
||||||
HOST_NAME: test
|
HOST_NAME: test
|
||||||
HOST_ID: 00000000-0000-0000-0000-000000000000
|
HOST_ID: 00000000-0000-0000-0000-000000000000
|
||||||
CORE_HOST: https://demo.jumpserver.org
|
CORE_HOST: https://demo.jumpserver.org
|
||||||
|
@ -17,166 +16,166 @@
|
||||||
TinkerInstaller: Tinker_Installer_v0.0.1.exe
|
TinkerInstaller: Tinker_Installer_v0.0.1.exe
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Install RDS-Licensing (RDS)
|
- name: Install RDS-Licensing (RDS)
|
||||||
ansible.windows.win_feature:
|
ansible.windows.win_feature:
|
||||||
name: RDS-Licensing
|
name: RDS-Licensing
|
||||||
state: present
|
state: present
|
||||||
include_management_tools: yes
|
include_management_tools: yes
|
||||||
when: RDS_Licensing
|
when: RDS_Licensing
|
||||||
|
|
||||||
- name: Install RDS-RD-Server (RDS)
|
- name: Install RDS-RD-Server (RDS)
|
||||||
ansible.windows.win_feature:
|
ansible.windows.win_feature:
|
||||||
name: RDS-RD-Server
|
name: RDS-RD-Server
|
||||||
state: present
|
state: present
|
||||||
include_management_tools: yes
|
include_management_tools: yes
|
||||||
register: rds_install
|
register: rds_install
|
||||||
|
|
||||||
- name: Download JumpServer Tinker installer (jumpserver)
|
- name: Download JumpServer Tinker installer (jumpserver)
|
||||||
ansible.windows.win_get_url:
|
ansible.windows.win_get_url:
|
||||||
url: "{{ DownloadHost }}/{{ TinkerInstaller }}"
|
url: "{{ DownloadHost }}/{{ TinkerInstaller }}"
|
||||||
dest: "{{ ansible_env.TEMP }}\\{{ TinkerInstaller }}"
|
dest: "{{ ansible_env.TEMP }}\\{{ TinkerInstaller }}"
|
||||||
|
|
||||||
- name: Install JumpServer Tinker (jumpserver)
|
- name: Install JumpServer Tinker (jumpserver)
|
||||||
ansible.windows.win_package:
|
ansible.windows.win_package:
|
||||||
path: "{{ ansible_env.TEMP }}\\{{ TinkerInstaller }}"
|
path: "{{ ansible_env.TEMP }}\\{{ TinkerInstaller }}"
|
||||||
arguments:
|
arguments:
|
||||||
- /VERYSILENT
|
- /VERYSILENT
|
||||||
- /SUPPRESSMSGBOXES
|
- /SUPPRESSMSGBOXES
|
||||||
- /NORESTART
|
- /NORESTART
|
||||||
state: present
|
state: present
|
||||||
|
|
||||||
- name: Set remote-server on the global system path (remote-server)
|
- name: Set remote-server on the global system path (remote-server)
|
||||||
ansible.windows.win_path:
|
ansible.windows.win_path:
|
||||||
elements:
|
elements:
|
||||||
- '%USERPROFILE%\AppData\Local\Programs\Tinker\'
|
- '%USERPROFILE%\AppData\Local\Programs\Tinker\'
|
||||||
scope: user
|
scope: user
|
||||||
|
|
||||||
- name: Download python-3.10.8
|
- name: Download python-3.10.8
|
||||||
ansible.windows.win_get_url:
|
ansible.windows.win_get_url:
|
||||||
url: "{{ DownloadHost }}/python-3.10.8-amd64.exe"
|
url: "{{ DownloadHost }}/python-3.10.8-amd64.exe"
|
||||||
dest: "{{ ansible_env.TEMP }}\\python-3.10.8-amd64.exe"
|
dest: "{{ ansible_env.TEMP }}\\python-3.10.8-amd64.exe"
|
||||||
|
|
||||||
- name: Install the python-3.10.8
|
- name: Install the python-3.10.8
|
||||||
ansible.windows.win_package:
|
ansible.windows.win_package:
|
||||||
path: "{{ ansible_env.TEMP }}\\python-3.10.8-amd64.exe"
|
path: "{{ ansible_env.TEMP }}\\python-3.10.8-amd64.exe"
|
||||||
product_id: '{371d0d73-d418-4ffe-b280-58c3e7987525}'
|
product_id: '{371d0d73-d418-4ffe-b280-58c3e7987525}'
|
||||||
arguments:
|
arguments:
|
||||||
- /quiet
|
- /quiet
|
||||||
- InstallAllUsers=1
|
- InstallAllUsers=1
|
||||||
- PrependPath=1
|
- PrependPath=1
|
||||||
- Include_test=0
|
- Include_test=0
|
||||||
- Include_launcher=0
|
- Include_launcher=0
|
||||||
state: present
|
state: present
|
||||||
register: win_install_python
|
register: win_install_python
|
||||||
|
|
||||||
- name: Reboot if installing requires it
|
- name: Reboot if installing requires it
|
||||||
ansible.windows.win_reboot:
|
ansible.windows.win_reboot:
|
||||||
post_reboot_delay: 10
|
post_reboot_delay: 10
|
||||||
test_command: whoami
|
test_command: whoami
|
||||||
when: rds_install.reboot_required or win_install_python.reboot_required
|
when: rds_install.reboot_required or win_install_python.reboot_required
|
||||||
|
|
||||||
- name: Set RDS LicenseServer (regedit)
|
- name: Set RDS LicenseServer (regedit)
|
||||||
ansible.windows.win_regedit:
|
ansible.windows.win_regedit:
|
||||||
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
||||||
name: LicenseServers
|
name: LicenseServers
|
||||||
data: "{{ RDS_LicenseServer }}"
|
data: "{{ RDS_LicenseServer }}"
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
- name: Set RDS LicensingMode (regedit)
|
- name: Set RDS LicensingMode (regedit)
|
||||||
ansible.windows.win_regedit:
|
ansible.windows.win_regedit:
|
||||||
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
||||||
name: LicensingMode
|
name: LicensingMode
|
||||||
data: "{{ RDS_LicensingMode }}"
|
data: "{{ RDS_LicensingMode }}"
|
||||||
type: dword
|
type: dword
|
||||||
|
|
||||||
- name: Set RDS fSingleSessionPerUser (regedit)
|
- name: Set RDS fSingleSessionPerUser (regedit)
|
||||||
ansible.windows.win_regedit:
|
ansible.windows.win_regedit:
|
||||||
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
||||||
name: fSingleSessionPerUser
|
name: fSingleSessionPerUser
|
||||||
data: "{{ RDS_fSingleSessionPerUser }}"
|
data: "{{ RDS_fSingleSessionPerUser }}"
|
||||||
type: dword
|
type: dword
|
||||||
|
|
||||||
- name: Set RDS MaxDisconnectionTime (regedit)
|
- name: Set RDS MaxDisconnectionTime (regedit)
|
||||||
ansible.windows.win_regedit:
|
ansible.windows.win_regedit:
|
||||||
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
||||||
name: MaxDisconnectionTime
|
name: MaxDisconnectionTime
|
||||||
data: "{{ RDS_MaxDisconnectionTime }}"
|
data: "{{ RDS_MaxDisconnectionTime }}"
|
||||||
type: dword
|
type: dword
|
||||||
when: RDS_MaxDisconnectionTime >= 60000
|
when: RDS_MaxDisconnectionTime >= 60000
|
||||||
|
|
||||||
- name: Set RDS RemoteAppLogoffTimeLimit (regedit)
|
- name: Set RDS RemoteAppLogoffTimeLimit (regedit)
|
||||||
ansible.windows.win_regedit:
|
ansible.windows.win_regedit:
|
||||||
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
||||||
name: RemoteAppLogoffTimeLimit
|
name: RemoteAppLogoffTimeLimit
|
||||||
data: "{{ RDS_RemoteAppLogoffTimeLimit }}"
|
data: "{{ RDS_RemoteAppLogoffTimeLimit }}"
|
||||||
type: dword
|
type: dword
|
||||||
|
|
||||||
- name: Download pip packages
|
- name: Download pip packages
|
||||||
ansible.windows.win_get_url:
|
ansible.windows.win_get_url:
|
||||||
url: "{{ DownloadHost }}/pip_packages_v0.0.1.zip"
|
url: "{{ DownloadHost }}/pip_packages_v0.0.1.zip"
|
||||||
dest: "{{ ansible_env.TEMP }}\\pip_packages_v0.0.1.zip"
|
dest: "{{ ansible_env.TEMP }}\\pip_packages_v0.0.1.zip"
|
||||||
|
|
||||||
- name: Unzip pip_packages
|
- name: Unzip pip_packages
|
||||||
community.windows.win_unzip:
|
community.windows.win_unzip:
|
||||||
src: "{{ ansible_env.TEMP }}\\pip_packages_v0.0.1.zip"
|
src: "{{ ansible_env.TEMP }}\\pip_packages_v0.0.1.zip"
|
||||||
dest: "{{ ansible_env.TEMP }}"
|
dest: "{{ ansible_env.TEMP }}"
|
||||||
|
|
||||||
- name: Install python requirements offline
|
- name: Install python requirements offline
|
||||||
ansible.windows.win_shell: >
|
ansible.windows.win_shell: >
|
||||||
pip install -r '{{ ansible_env.TEMP }}\pip_packages_v0.0.1\requirements.txt'
|
pip install -r '{{ ansible_env.TEMP }}\pip_packages_v0.0.1\requirements.txt'
|
||||||
--no-index --find-links='{{ ansible_env.TEMP }}\pip_packages_v0.0.1'
|
--no-index --find-links='{{ ansible_env.TEMP }}\pip_packages_v0.0.1'
|
||||||
|
|
||||||
- name: Download chromedriver (chrome)
|
- name: Download chromedriver (chrome)
|
||||||
ansible.windows.win_get_url:
|
ansible.windows.win_get_url:
|
||||||
url: "{{ DownloadHost }}/chromedriver_win32.107.zip"
|
url: "{{ DownloadHost }}/chromedriver_win32.107.zip"
|
||||||
dest: "{{ ansible_env.TEMP }}\\chromedriver_win32.107.zip"
|
dest: "{{ ansible_env.TEMP }}\\chromedriver_win32.107.zip"
|
||||||
|
|
||||||
- name: Unzip chromedriver (chrome)
|
- name: Unzip chromedriver (chrome)
|
||||||
community.windows.win_unzip:
|
community.windows.win_unzip:
|
||||||
src: "{{ ansible_env.TEMP }}\\chromedriver_win32.107.zip"
|
src: "{{ ansible_env.TEMP }}\\chromedriver_win32.107.zip"
|
||||||
dest: C:\Program Files\JumpServer\drivers
|
dest: C:\Program Files\JumpServer\drivers
|
||||||
|
|
||||||
- name: Set chromedriver on the global system path (chrome)
|
- name: Set chromedriver on the global system path (chrome)
|
||||||
ansible.windows.win_path:
|
ansible.windows.win_path:
|
||||||
elements:
|
elements:
|
||||||
- 'C:\Program Files\JumpServer\drivers'
|
- 'C:\Program Files\JumpServer\drivers'
|
||||||
|
|
||||||
- name: Download chrome msi package (chrome)
|
- name: Download chrome msi package (chrome)
|
||||||
ansible.windows.win_get_url:
|
ansible.windows.win_get_url:
|
||||||
url: "{{ DownloadHost }}/googlechromestandaloneenterprise64.msi"
|
url: "{{ DownloadHost }}/googlechromestandaloneenterprise64.msi"
|
||||||
dest: "{{ ansible_env.TEMP }}\\googlechromestandaloneenterprise64.msi"
|
dest: "{{ ansible_env.TEMP }}\\googlechromestandaloneenterprise64.msi"
|
||||||
|
|
||||||
- name: Install chrome (chrome)
|
- name: Install chrome (chrome)
|
||||||
ansible.windows.win_package:
|
ansible.windows.win_package:
|
||||||
path: "{{ ansible_env.TEMP }}\\googlechromestandaloneenterprise64.msi"
|
path: "{{ ansible_env.TEMP }}\\googlechromestandaloneenterprise64.msi"
|
||||||
state: present
|
state: present
|
||||||
arguments:
|
arguments:
|
||||||
- /quiet
|
- /quiet
|
||||||
|
|
||||||
- name: Generate tinkerd component config
|
- name: Generate tinkerd component config
|
||||||
ansible.windows.win_shell:
|
ansible.windows.win_shell:
|
||||||
"tinkerd config --hostname {{ HOST_NAME }} --core_host {{ CORE_HOST }}
|
"tinkerd config --hostname {{ HOST_NAME }} --core_host {{ CORE_HOST }}
|
||||||
--token {{ BOOTSTRAP_TOKEN }} --host_id {{ HOST_ID }}"
|
--token {{ BOOTSTRAP_TOKEN }} --host_id {{ HOST_ID }}"
|
||||||
|
|
||||||
- name: Install tinkerd service
|
- name: Install tinkerd service
|
||||||
ansible.windows.win_shell:
|
ansible.windows.win_shell:
|
||||||
"tinkerd service install"
|
"tinkerd service install"
|
||||||
|
|
||||||
- name: Start tinkerd service
|
- name: Start tinkerd service
|
||||||
ansible.windows.win_shell:
|
ansible.windows.win_shell:
|
||||||
"tinkerd service start"
|
"tinkerd service start"
|
||||||
|
|
||||||
- name: Wait Tinker api health
|
- name: Wait Tinker api health
|
||||||
ansible.windows.win_uri:
|
ansible.windows.win_uri:
|
||||||
url: http://localhost:6068/api/health/
|
url: http://localhost:6068/api/health/
|
||||||
status_code: 200
|
status_code: 200
|
||||||
method: GET
|
method: GET
|
||||||
register: _result
|
register: _result
|
||||||
until: _result.status_code == 200
|
until: _result.status_code == 200
|
||||||
retries: 30
|
retries: 30
|
||||||
delay: 5
|
delay: 5
|
||||||
|
|
||||||
- name: Sync all remote applets
|
- name: Sync all remote applets
|
||||||
ansible.windows.win_shell:
|
ansible.windows.win_shell:
|
||||||
"tinkerd install all"
|
"tinkerd install all"
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.2.14 on 2022-11-15 05:53
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('terminal', '0058_auto_20221103_1624'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='applethostdeployment',
|
||||||
|
name='task',
|
||||||
|
field=models.UUIDField(null=True, verbose_name='Task'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -105,8 +105,23 @@ class AppletHostDeployment(JMSBaseModel):
|
||||||
date_start = models.DateTimeField(null=True, verbose_name=_('Date start'), db_index=True)
|
date_start = models.DateTimeField(null=True, verbose_name=_('Date start'), db_index=True)
|
||||||
date_finished = models.DateTimeField(null=True, verbose_name=_("Date finished"))
|
date_finished = models.DateTimeField(null=True, verbose_name=_("Date finished"))
|
||||||
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
|
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
|
||||||
|
task = models.UUIDField(null=True, verbose_name=_('Task'))
|
||||||
|
|
||||||
def start(self, **kwargs):
|
def start(self, **kwargs):
|
||||||
from ...automations.deploy_applet_host import DeployAppletHostManager
|
from ...automations.deploy_applet_host import DeployAppletHostManager
|
||||||
manager = DeployAppletHostManager(self)
|
manager = DeployAppletHostManager(self)
|
||||||
manager.run(**kwargs)
|
manager.run(**kwargs)
|
||||||
|
|
||||||
|
def install_applet(self, applet_id, **kwargs):
|
||||||
|
from ...automations.deploy_applet_host import DeployAppletHostManager
|
||||||
|
from .applet import Applet
|
||||||
|
if applet_id:
|
||||||
|
applet = Applet.objects.get(id=applet_id)
|
||||||
|
else:
|
||||||
|
applet = None
|
||||||
|
manager = DeployAppletHostManager(self, applet=applet)
|
||||||
|
manager.install_applet(**kwargs)
|
||||||
|
|
||||||
|
def save_task(self, task):
|
||||||
|
self.task = task
|
||||||
|
self.save(update_fields=['task'])
|
||||||
|
|
|
@ -1,19 +1,18 @@
|
||||||
from rest_framework import serializers
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
from common.validators import ProjectUniqueValidator
|
|
||||||
from common.drf.fields import ObjectRelatedField, LabeledChoiceField
|
|
||||||
from assets.models import Platform, Account
|
from assets.models import Platform, Account
|
||||||
from assets.serializers import HostSerializer
|
from assets.serializers import HostSerializer
|
||||||
from ..models import AppletHost, AppletHostDeployment, Applet
|
from common.drf.fields import LabeledChoiceField
|
||||||
|
from common.validators import ProjectUniqueValidator
|
||||||
from .applet import AppletSerializer
|
from .applet import AppletSerializer
|
||||||
from .. import const
|
from .. import const
|
||||||
|
from ..models import AppletHost, AppletHostDeployment
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AppletHostSerializer', 'AppletHostDeploymentSerializer',
|
'AppletHostSerializer', 'AppletHostDeploymentSerializer',
|
||||||
'AppletHostAccountSerializer', 'AppletHostAppletReportSerializer',
|
'AppletHostAccountSerializer', 'AppletHostAppletReportSerializer',
|
||||||
'AppletHostStartupSerializer',
|
'AppletHostStartupSerializer', 'AppletHostDeployAppletSerializer'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,7 +28,8 @@ class DeployOptionsSerializer(serializers.Serializer):
|
||||||
RDS_Licensing = serializers.BooleanField(default=False, label=_("RDS Licensing"))
|
RDS_Licensing = serializers.BooleanField(default=False, label=_("RDS Licensing"))
|
||||||
RDS_LicenseServer = serializers.CharField(default='127.0.0.1', label=_('RDS License Server'), max_length=1024)
|
RDS_LicenseServer = serializers.CharField(default='127.0.0.1', label=_('RDS License Server'), max_length=1024)
|
||||||
RDS_LicensingMode = serializers.ChoiceField(choices=LICENSE_MODE_CHOICES, default=4, label=_('RDS Licensing Mode'))
|
RDS_LicensingMode = serializers.ChoiceField(choices=LICENSE_MODE_CHOICES, default=4, label=_('RDS Licensing Mode'))
|
||||||
RDS_fSingleSessionPerUser = serializers.ChoiceField(choices=SESSION_PER_USER, default=1, label=_("RDS fSingleSessionPerUser"))
|
RDS_fSingleSessionPerUser = serializers.ChoiceField(choices=SESSION_PER_USER, default=1,
|
||||||
|
label=_("RDS fSingleSessionPerUser"))
|
||||||
RDS_MaxDisconnectionTime = serializers.IntegerField(default=60000, label=_("RDS Max Disconnection Time"))
|
RDS_MaxDisconnectionTime = serializers.IntegerField(default=60000, label=_("RDS Max Disconnection Time"))
|
||||||
RDS_RemoteAppLogoffTimeLimit = serializers.IntegerField(default=0, label=_("RDS Remote App Logoff Time Limit"))
|
RDS_RemoteAppLogoffTimeLimit = serializers.IntegerField(default=0, label=_("RDS Remote App Logoff Time Limit"))
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ class HostAppletSerializer(AppletSerializer):
|
||||||
class AppletHostDeploymentSerializer(serializers.ModelSerializer):
|
class AppletHostDeploymentSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AppletHostDeployment
|
model = AppletHostDeployment
|
||||||
fields_mini = ['id', 'host', 'status']
|
fields_mini = ['id', 'host', 'status', 'task']
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
'status', 'date_created', 'date_updated',
|
'status', 'date_created', 'date_updated',
|
||||||
'date_start', 'date_finished'
|
'date_start', 'date_finished'
|
||||||
|
@ -94,6 +94,18 @@ class AppletHostDeploymentSerializer(serializers.ModelSerializer):
|
||||||
fields = fields_mini + ['comment'] + read_only_fields
|
fields = fields_mini + ['comment'] + read_only_fields
|
||||||
|
|
||||||
|
|
||||||
|
class AppletHostDeployAppletSerializer(AppletHostDeploymentSerializer):
|
||||||
|
applet_id = serializers.UUIDField(write_only=True, allow_null=True, required=False)
|
||||||
|
|
||||||
|
class Meta(AppletHostDeploymentSerializer.Meta):
|
||||||
|
fields = AppletHostDeploymentSerializer.Meta.fields + ['applet_id']
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
applet_id = validated_data.pop('applet_id', None)
|
||||||
|
deployment = super().create(validated_data)
|
||||||
|
return deployment
|
||||||
|
|
||||||
|
|
||||||
class AppletHostAccountSerializer(serializers.ModelSerializer):
|
class AppletHostAccountSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Account
|
model = Account
|
||||||
|
|
|
@ -1,26 +1,25 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import datetime
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import datetime
|
|
||||||
|
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
from celery.utils.log import get_task_logger
|
from celery.utils.log import get_task_logger
|
||||||
from django.utils import timezone
|
|
||||||
from django.core.files.storage import default_storage
|
from django.core.files.storage import default_storage
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from common.utils import get_log_keep_day
|
from common.utils import get_log_keep_day
|
||||||
from ops.celery.decorator import (
|
from ops.celery.decorator import (
|
||||||
register_as_period_task, after_app_ready_start,
|
register_as_period_task, after_app_ready_start,
|
||||||
after_app_shutdown_clean_periodic
|
after_app_shutdown_clean_periodic
|
||||||
)
|
)
|
||||||
from .models import (
|
|
||||||
Status, Session, Command, Task, AppletHost,
|
|
||||||
AppletHostDeployment
|
|
||||||
)
|
|
||||||
from orgs.utils import tmp_to_builtin_org
|
from orgs.utils import tmp_to_builtin_org
|
||||||
from .backends import server_replay_storage
|
from .backends import server_replay_storage
|
||||||
|
from .models import (
|
||||||
|
Status, Session, Command, Task, AppletHostDeployment
|
||||||
|
)
|
||||||
from .utils import find_session_replay_local
|
from .utils import find_session_replay_local
|
||||||
|
|
||||||
CACHE_REFRESH_INTERVAL = 10
|
CACHE_REFRESH_INTERVAL = 10
|
||||||
|
@ -57,7 +56,7 @@ def clean_orphan_session():
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
@register_as_period_task(interval=3600*24)
|
@register_as_period_task(interval=3600 * 24)
|
||||||
@after_app_ready_start
|
@after_app_ready_start
|
||||||
@after_app_shutdown_clean_periodic
|
@after_app_shutdown_clean_periodic
|
||||||
def clean_expired_session_period():
|
def clean_expired_session_period():
|
||||||
|
@ -114,3 +113,10 @@ def run_applet_host_deployment(did):
|
||||||
with tmp_to_builtin_org(system=1):
|
with tmp_to_builtin_org(system=1):
|
||||||
deployment = AppletHostDeployment.objects.get(id=did)
|
deployment = AppletHostDeployment.objects.get(id=did)
|
||||||
deployment.start()
|
deployment.start()
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def run_applet_host_deployment_install_applet(did, applet_id):
|
||||||
|
with tmp_to_builtin_org(system=1):
|
||||||
|
deployment = AppletHostDeployment.objects.get(id=did)
|
||||||
|
deployment.install_applet(applet_id)
|
||||||
|
|
|
@ -2,22 +2,20 @@
|
||||||
#
|
#
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework.exceptions import MethodNotAllowed
|
from rest_framework.exceptions import MethodNotAllowed
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from common.const.http import POST, PUT, PATCH
|
from common.const.http import POST, PUT, PATCH
|
||||||
from common.mixins.api import CommonApiMixin
|
from common.mixins.api import CommonApiMixin
|
||||||
from orgs.utils import tmp_to_root_org
|
from orgs.utils import tmp_to_root_org
|
||||||
|
|
||||||
from rbac.permissions import RBACPermission
|
from rbac.permissions import RBACPermission
|
||||||
|
|
||||||
from tickets import serializers
|
|
||||||
from tickets import filters
|
from tickets import filters
|
||||||
from tickets.permissions.ticket import IsAssignee, IsApplicant
|
from tickets import serializers
|
||||||
from tickets.models import (
|
from tickets.models import (
|
||||||
Ticket, ApplyAssetTicket, ApplyLoginTicket,
|
Ticket, ApplyAssetTicket, ApplyLoginTicket,
|
||||||
ApplyLoginAssetTicket, ApplyCommandTicket
|
ApplyLoginAssetTicket, ApplyCommandTicket
|
||||||
)
|
)
|
||||||
|
from tickets.permissions.ticket import IsAssignee, IsApplicant
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'TicketViewSet', 'ApplyAssetTicketViewSet',
|
'TicketViewSet', 'ApplyAssetTicketViewSet',
|
||||||
|
@ -27,10 +25,8 @@ __all__ = [
|
||||||
|
|
||||||
|
|
||||||
class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet):
|
class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet):
|
||||||
serializer_class = serializers.TicketDisplaySerializer
|
serializer_class = serializers.TicketSerializer
|
||||||
serializer_classes = {
|
serializer_classes = {
|
||||||
'list': serializers.TicketListSerializer,
|
|
||||||
'open': serializers.TicketApplySerializer,
|
|
||||||
'approve': serializers.TicketApproveSerializer
|
'approve': serializers.TicketApproveSerializer
|
||||||
}
|
}
|
||||||
model = Ticket
|
model = Ticket
|
||||||
|
@ -40,8 +36,8 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet):
|
||||||
'title', 'type', 'status'
|
'title', 'type', 'status'
|
||||||
]
|
]
|
||||||
ordering_fields = (
|
ordering_fields = (
|
||||||
'title', 'status', 'state',
|
'title', 'status', 'state', 'action_display',
|
||||||
'action_display', 'date_created', 'serial_num',
|
'date_created', 'serial_num',
|
||||||
)
|
)
|
||||||
ordering = ('-date_created',)
|
ordering = ('-date_created',)
|
||||||
rbac_perms = {
|
rbac_perms = {
|
||||||
|
@ -98,11 +94,7 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet):
|
||||||
|
|
||||||
|
|
||||||
class ApplyAssetTicketViewSet(TicketViewSet):
|
class ApplyAssetTicketViewSet(TicketViewSet):
|
||||||
serializer_class = serializers.ApplyAssetDisplaySerializer
|
serializer_class = serializers.ApplyAssetSerializer
|
||||||
serializer_classes = {
|
|
||||||
'open': serializers.ApplyAssetSerializer,
|
|
||||||
'approve': serializers.ApproveAssetSerializer
|
|
||||||
}
|
|
||||||
model = ApplyAssetTicket
|
model = ApplyAssetTicket
|
||||||
filterset_class = filters.ApplyAssetTicketFilter
|
filterset_class = filters.ApplyAssetTicketFilter
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from perms.const import ActionChoices
|
||||||
from .general import Ticket
|
from .general import Ticket
|
||||||
|
|
||||||
__all__ = ['ApplyAssetTicket']
|
__all__ = ['ApplyAssetTicket']
|
||||||
|
@ -14,13 +15,6 @@ class ApplyAssetTicket(Ticket):
|
||||||
# 申请信息
|
# 申请信息
|
||||||
apply_assets = models.ManyToManyField('assets.Asset', verbose_name=_('Apply assets'))
|
apply_assets = models.ManyToManyField('assets.Asset', verbose_name=_('Apply assets'))
|
||||||
apply_accounts = models.JSONField(default=list, verbose_name=_('Apply accounts'))
|
apply_accounts = models.JSONField(default=list, verbose_name=_('Apply accounts'))
|
||||||
apply_actions = models.IntegerField(default=1, verbose_name=_('Actions'))
|
apply_actions = models.IntegerField(verbose_name=_('Actions'), default=ActionChoices.all())
|
||||||
apply_date_start = models.DateTimeField(verbose_name=_('Date start'), null=True)
|
apply_date_start = models.DateTimeField(verbose_name=_('Date start'), null=True)
|
||||||
apply_date_expired = models.DateTimeField(verbose_name=_('Date expired'), null=True)
|
apply_date_expired = models.DateTimeField(verbose_name=_('Date expired'), null=True)
|
||||||
|
|
||||||
@property
|
|
||||||
def apply_actions_display(self):
|
|
||||||
return 'Todo'
|
|
||||||
|
|
||||||
def get_apply_actions_display(self):
|
|
||||||
return ', '.join(self.apply_actions_display)
|
|
||||||
|
|
|
@ -1,40 +1,40 @@
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from perms.serializers.permission import ActionChoicesField
|
|
||||||
from perms.models import AssetPermission
|
|
||||||
from orgs.utils import tmp_to_org
|
|
||||||
from assets.models import Asset, Node
|
from assets.models import Asset, Node
|
||||||
|
from common.drf.fields import ObjectRelatedField
|
||||||
|
from perms.models import AssetPermission
|
||||||
|
from perms.serializers.permission import ActionChoicesField
|
||||||
from tickets.models import ApplyAssetTicket
|
from tickets.models import ApplyAssetTicket
|
||||||
|
from .common import BaseApplyAssetSerializer
|
||||||
from .ticket import TicketApplySerializer
|
from .ticket import TicketApplySerializer
|
||||||
from .common import BaseApplyAssetApplicationSerializer
|
|
||||||
|
|
||||||
__all__ = ['ApplyAssetSerializer', 'ApplyAssetDisplaySerializer', 'ApproveAssetSerializer']
|
__all__ = ['ApplyAssetSerializer']
|
||||||
|
|
||||||
asset_or_node_help_text = _("Select at least one asset or node")
|
asset_or_node_help_text = _("Select at least one asset or node")
|
||||||
|
|
||||||
|
|
||||||
class ApplyAssetSerializer(BaseApplyAssetApplicationSerializer, TicketApplySerializer):
|
class ApplyAssetSerializer(BaseApplyAssetSerializer, TicketApplySerializer):
|
||||||
apply_actions = ActionChoicesField(required=True, allow_empty=False)
|
apply_assets = ObjectRelatedField(queryset=Asset.objects, many=True, required=False, label=_('Apply assets'))
|
||||||
|
apply_nodes = ObjectRelatedField(queryset=Node.objects, many=True, required=False, label=_('Apply nodes'))
|
||||||
|
apply_actions = ActionChoicesField(required=False, allow_null=True, label=_("Apply actions"))
|
||||||
permission_model = AssetPermission
|
permission_model = AssetPermission
|
||||||
|
|
||||||
class Meta:
|
class Meta(TicketApplySerializer.Meta):
|
||||||
model = ApplyAssetTicket
|
model = ApplyAssetTicket
|
||||||
|
fields_mini = ['id', 'title']
|
||||||
writeable_fields = [
|
writeable_fields = [
|
||||||
'id', 'title', 'type', 'apply_nodes', 'apply_assets',
|
'id', 'title', 'apply_nodes', 'apply_assets',
|
||||||
'apply_accounts', 'apply_actions', 'org_id', 'comment',
|
'apply_accounts', 'apply_actions', 'org_id', 'comment',
|
||||||
'apply_date_start', 'apply_date_expired'
|
'apply_date_start', 'apply_date_expired'
|
||||||
]
|
]
|
||||||
fields = TicketApplySerializer.Meta.fields + writeable_fields + [
|
fields = TicketApplySerializer.Meta.fields + writeable_fields + ['apply_permission_name', ]
|
||||||
'apply_permission_name', 'apply_actions_display'
|
|
||||||
]
|
|
||||||
read_only_fields = list(set(fields) - set(writeable_fields))
|
read_only_fields = list(set(fields) - set(writeable_fields))
|
||||||
ticket_extra_kwargs = TicketApplySerializer.Meta.extra_kwargs
|
ticket_extra_kwargs = TicketApplySerializer.Meta.extra_kwargs
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'apply_nodes': {'required': False, 'allow_empty': True},
|
'apply_nodes': {'required': False},
|
||||||
'apply_assets': {'required': False, 'allow_empty': True},
|
'apply_assets': {'required': False},
|
||||||
'apply_accounts': {'required': False, 'allow_empty': True},
|
'apply_accounts': {'required': False},
|
||||||
}
|
}
|
||||||
extra_kwargs.update(ticket_extra_kwargs)
|
extra_kwargs.update(ticket_extra_kwargs)
|
||||||
|
|
||||||
|
@ -45,9 +45,11 @@ class ApplyAssetSerializer(BaseApplyAssetApplicationSerializer, TicketApplySeria
|
||||||
return self.filter_many_to_many_field(Asset, assets)
|
return self.filter_many_to_many_field(Asset, assets)
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
|
attrs['type'] = 'apply_asset'
|
||||||
attrs = super().validate(attrs)
|
attrs = super().validate(attrs)
|
||||||
if self.is_final_approval and (
|
if self.is_final_approval and (
|
||||||
not attrs.get('apply_nodes') and not attrs.get('apply_assets')
|
not attrs.get('apply_nodes')
|
||||||
|
and not attrs.get('apply_assets')
|
||||||
):
|
):
|
||||||
raise serializers.ValidationError({
|
raise serializers.ValidationError({
|
||||||
'apply_nodes': asset_or_node_help_text,
|
'apply_nodes': asset_or_node_help_text,
|
||||||
|
@ -56,29 +58,7 @@ class ApplyAssetSerializer(BaseApplyAssetApplicationSerializer, TicketApplySeria
|
||||||
|
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
@classmethod
|
||||||
class ApproveAssetSerializer(ApplyAssetSerializer):
|
def setup_eager_loading(cls, queryset):
|
||||||
class Meta(ApplyAssetSerializer.Meta):
|
queryset = queryset.prefetch_related('apply_nodes', 'apply_assets')
|
||||||
read_only_fields = ApplyAssetSerializer.Meta.read_only_fields + [
|
return queryset
|
||||||
'title', 'type'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class ApplyAssetDisplaySerializer(ApplyAssetSerializer):
|
|
||||||
apply_nodes = serializers.SerializerMethodField()
|
|
||||||
apply_assets = serializers.SerializerMethodField()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = ApplyAssetSerializer.Meta.model
|
|
||||||
fields = ApplyAssetSerializer.Meta.fields
|
|
||||||
read_only_fields = fields
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_apply_nodes(instance):
|
|
||||||
with tmp_to_org(instance.org_id):
|
|
||||||
return instance.apply_nodes.values_list('id', flat=True)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_apply_assets(instance):
|
|
||||||
with tmp_to_org(instance.org_id):
|
|
||||||
return instance.apply_assets.values_list('id', flat=True)
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
from django.db.transaction import atomic
|
|
||||||
from django.db.models import Model
|
from django.db.models import Model
|
||||||
|
from django.db.transaction import atomic
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from orgs.utils import tmp_to_org
|
from orgs.utils import tmp_to_org
|
||||||
from tickets.models import Ticket
|
from tickets.models import Ticket
|
||||||
|
|
||||||
__all__ = ['DefaultPermissionName', 'get_default_permission_name', 'BaseApplyAssetApplicationSerializer']
|
__all__ = ['DefaultPermissionName', 'get_default_permission_name', 'BaseApplyAssetSerializer']
|
||||||
|
|
||||||
|
|
||||||
def get_default_permission_name(ticket):
|
def get_default_permission_name(ticket):
|
||||||
|
@ -34,7 +34,7 @@ class DefaultPermissionName(object):
|
||||||
return self.default
|
return self.default
|
||||||
|
|
||||||
|
|
||||||
class BaseApplyAssetApplicationSerializer(serializers.Serializer):
|
class BaseApplyAssetSerializer(serializers.Serializer):
|
||||||
permission_model: Model
|
permission_model: Model
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -3,31 +3,35 @@
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from orgs.models import Organization
|
from common.drf.fields import LabeledChoiceField
|
||||||
from orgs.mixins.serializers import OrgResourceModelSerializerMixin
|
from orgs.mixins.serializers import OrgResourceModelSerializerMixin
|
||||||
|
from orgs.models import Organization
|
||||||
|
from tickets.const import TicketType, TicketStatus, TicketState
|
||||||
from tickets.models import Ticket, TicketFlow
|
from tickets.models import Ticket, TicketFlow
|
||||||
from tickets.const import TicketType
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'TicketDisplaySerializer', 'TicketApplySerializer', 'TicketListSerializer', 'TicketApproveSerializer'
|
'TicketApplySerializer', 'TicketApproveSerializer', 'TicketSerializer',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class TicketSerializer(OrgResourceModelSerializerMixin):
|
class TicketSerializer(OrgResourceModelSerializerMixin):
|
||||||
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type display'))
|
type = LabeledChoiceField(choices=TicketType.choices, read_only=True, label=_('Type'))
|
||||||
status_display = serializers.ReadOnlyField(source='get_status_display', label=_('Status display'))
|
status = LabeledChoiceField(choices=TicketStatus.choices, read_only=True, label=_('Status'))
|
||||||
|
state = LabeledChoiceField(choices=TicketState.choices, read_only=True, label=_("State"))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Ticket
|
model = Ticket
|
||||||
fields_mini = ['id', 'title']
|
fields_mini = ['id', 'title']
|
||||||
fields_small = fields_mini + [
|
fields_small = fields_mini + [
|
||||||
'type', 'type_display', 'status', 'status_display',
|
'type', 'status', 'state', 'approval_step', 'comment',
|
||||||
'state', 'approval_step', 'rel_snapshot', 'comment',
|
|
||||||
'date_created', 'date_updated', 'org_id', 'rel_snapshot',
|
'date_created', 'date_updated', 'org_id', 'rel_snapshot',
|
||||||
'process_map', 'org_name', 'serial_num'
|
'process_map', 'org_name', 'serial_num'
|
||||||
]
|
]
|
||||||
fields_fk = ['applicant', ]
|
fields_fk = ['applicant', ]
|
||||||
fields = fields_small + fields_fk
|
fields = fields_small + fields_fk
|
||||||
|
extra_kwargs = {
|
||||||
|
'type': {'required': True}
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
@ -41,43 +45,20 @@ class TicketSerializer(OrgResourceModelSerializerMixin):
|
||||||
choices.pop(TicketType.general, None)
|
choices.pop(TicketType.general, None)
|
||||||
tp._choices = choices
|
tp._choices = choices
|
||||||
|
|
||||||
|
@classmethod
|
||||||
class TicketListSerializer(TicketSerializer):
|
def setup_eager_loading(cls, queryset):
|
||||||
class Meta:
|
queryset = queryset.prefetch_related('ticket_steps')
|
||||||
model = Ticket
|
return queryset
|
||||||
fields = [
|
|
||||||
'id', 'title', 'serial_num', 'type', 'type_display', 'status',
|
|
||||||
'state', 'rel_snapshot', 'date_created', 'rel_snapshot'
|
|
||||||
]
|
|
||||||
read_only_fields = fields
|
|
||||||
|
|
||||||
|
|
||||||
class TicketDisplaySerializer(TicketSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = Ticket
|
|
||||||
fields = TicketSerializer.Meta.fields
|
|
||||||
read_only_fields = fields
|
|
||||||
|
|
||||||
|
|
||||||
class TicketApproveSerializer(TicketSerializer):
|
class TicketApproveSerializer(TicketSerializer):
|
||||||
class Meta:
|
class Meta(TicketSerializer.Meta):
|
||||||
model = Ticket
|
|
||||||
fields = TicketSerializer.Meta.fields
|
fields = TicketSerializer.Meta.fields
|
||||||
read_only_fields = fields
|
read_only_fields = fields
|
||||||
|
|
||||||
|
|
||||||
class TicketApplySerializer(TicketSerializer):
|
class TicketApplySerializer(TicketSerializer):
|
||||||
org_id = serializers.CharField(
|
org_id = serializers.CharField(required=True, max_length=36, allow_blank=True, label=_("Organization"))
|
||||||
required=True, max_length=36,
|
|
||||||
allow_blank=True, label=_("Organization")
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Ticket
|
|
||||||
fields = TicketSerializer.Meta.fields
|
|
||||||
extra_kwargs = {
|
|
||||||
'type': {'required': True}
|
|
||||||
}
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def validate_org_id(org_id):
|
def validate_org_id(org_id):
|
||||||
|
@ -91,10 +72,13 @@ class TicketApplySerializer(TicketSerializer):
|
||||||
if self.instance:
|
if self.instance:
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
print("Attrs: ", attrs)
|
||||||
|
|
||||||
ticket_type = attrs.get('type')
|
ticket_type = attrs.get('type')
|
||||||
org_id = attrs.get('org_id')
|
org_id = attrs.get('org_id')
|
||||||
flow = TicketFlow.get_org_related_flows(org_id=org_id)\
|
flow = TicketFlow.get_org_related_flows(org_id=org_id) \
|
||||||
.filter(type=ticket_type).first()
|
.filter(type=ticket_type).first()
|
||||||
|
|
||||||
if flow:
|
if flow:
|
||||||
attrs['flow'] = flow
|
attrs['flow'] = flow
|
||||||
else:
|
else:
|
||||||
|
|
Loading…
Reference in New Issue