mirror of https://github.com/jumpserver/jumpserver
				
				
				
			Dev beta (#3167)
* [Update] 添加loading * [Update] 修改避免游离资产 * [Update] 修改nodes * [Update] 修改支持未分组pull/3171/head
							parent
							
								
									1fe18e8073
								
							
						
					
					
						commit
						8cd8f41cb0
					
				| 
						 | 
				
			
			@ -13,8 +13,6 @@
 | 
			
		|||
# See the License for the specific language governing permissions and
 | 
			
		||||
# limitations under the License.
 | 
			
		||||
 | 
			
		||||
import time
 | 
			
		||||
 | 
			
		||||
from rest_framework import generics
 | 
			
		||||
from rest_framework.serializers import ValidationError
 | 
			
		||||
from rest_framework.views import APIView
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,5 +7,7 @@ class AssetsConfig(AppConfig):
 | 
			
		|||
    name = 'assets'
 | 
			
		||||
 | 
			
		||||
    def ready(self):
 | 
			
		||||
        from . import signals_handler
 | 
			
		||||
        super().ready()
 | 
			
		||||
        from . import signals_handler
 | 
			
		||||
        from .models import Node
 | 
			
		||||
        Node.initial_some_nodes()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,7 +11,7 @@ from django.utils.translation import ugettext
 | 
			
		|||
from django.core.cache import cache
 | 
			
		||||
 | 
			
		||||
from orgs.mixins.models import OrgModelMixin, OrgManager
 | 
			
		||||
from orgs.utils import set_current_org, get_current_org
 | 
			
		||||
from orgs.utils import set_current_org, get_current_org, tmp_to_root_org, tmp_to_org
 | 
			
		||||
from orgs.models import Organization
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -25,37 +25,42 @@ class NodeQuerySet(models.QuerySet):
 | 
			
		|||
 | 
			
		||||
class TreeMixin:
 | 
			
		||||
    tree_created_time = None
 | 
			
		||||
    tree_updated_time_cache_key = 'NODE_TREE_CREATED_AT'
 | 
			
		||||
    tree_update_time_cache_time = 3600
 | 
			
		||||
    tree_updated_time_cache_key = 'NODE_TREE_UPDATED_AT'
 | 
			
		||||
    tree_cache_time = 3600
 | 
			
		||||
    tree_assets_cache_key = 'NODE_TREE_ASSETS_UPDATED_AT'
 | 
			
		||||
    tree_assets_created_time = None
 | 
			
		||||
    _tree_service = None
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def tree(cls):
 | 
			
		||||
        # Todo: 有待优化, 因为每次刷新都会导致其他节点的tree失效 完成
 | 
			
		||||
        # TOdo: 游离的资产,在树上显示的数量不对
 | 
			
		||||
        # Todo: ungroup node
 | 
			
		||||
        # Todo: api key页面有bug 完成
 | 
			
		||||
        from ..utils import TreeService
 | 
			
		||||
        tree_updated_time = cache.get(cls.tree_updated_time_cache_key, 0)
 | 
			
		||||
        if not cls.tree_created_time or \
 | 
			
		||||
                tree_updated_time > cls.tree_created_time:
 | 
			
		||||
            print("New tree")
 | 
			
		||||
            tree = TreeService.new()
 | 
			
		||||
            cls.tree_created_time = time.time()
 | 
			
		||||
            cls.tree_assets_created_time = time.time()
 | 
			
		||||
            cls._tree_service = tree
 | 
			
		||||
            return tree
 | 
			
		||||
        node_assets_updated_time = cache.get(cls.tree_assets_cache_key, 0)
 | 
			
		||||
        if not cls.tree_assets_created_time or \
 | 
			
		||||
                node_assets_updated_time > cls.tree_assets_created_time:
 | 
			
		||||
            cls._tree_service.init_assets_async()
 | 
			
		||||
        return cls._tree_service
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def expire_cache_tree(cls):
 | 
			
		||||
    def refresh_tree(cls):
 | 
			
		||||
        key = cls.tree_updated_time_cache_key
 | 
			
		||||
        ttl = cls.tree_update_time_cache_time
 | 
			
		||||
        ttl = cls.tree_cache_time
 | 
			
		||||
        value = time.time()
 | 
			
		||||
        cache.set(key, value, ttl)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def refresh_tree(cls):
 | 
			
		||||
        cls.expire_cache_tree()
 | 
			
		||||
    def refresh_node_assets(cls):
 | 
			
		||||
        key = cls.tree_assets_cache_key
 | 
			
		||||
        ttl = cls.tree_cache_time
 | 
			
		||||
        value = time.time()
 | 
			
		||||
        cache.set(key, value, ttl)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def _tree(self):
 | 
			
		||||
| 
						 | 
				
			
			@ -183,6 +188,31 @@ class FamilyMixin:
 | 
			
		|||
            key_list.pop()
 | 
			
		||||
        return keys
 | 
			
		||||
 | 
			
		||||
    def get_next_child_key(self):
 | 
			
		||||
        mark = self.child_mark
 | 
			
		||||
        self.child_mark += 1
 | 
			
		||||
        self.save()
 | 
			
		||||
        return "{}:{}".format(self.key, mark)
 | 
			
		||||
 | 
			
		||||
    def get_next_child_preset_name(self):
 | 
			
		||||
        name = ugettext("New node")
 | 
			
		||||
        values = [
 | 
			
		||||
            child.value[child.value.rfind(' '):]
 | 
			
		||||
            for child in self.get_children()
 | 
			
		||||
            if child.value.startswith(name)
 | 
			
		||||
        ]
 | 
			
		||||
        values = [int(value) for value in values if value.strip().isdigit()]
 | 
			
		||||
        count = max(values) + 1 if values else 1
 | 
			
		||||
        return '{} {}'.format(name, count)
 | 
			
		||||
 | 
			
		||||
    def create_child(self, value, _id=None):
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            child_key = self.get_next_child_key()
 | 
			
		||||
            child = self.__class__.objects.create(
 | 
			
		||||
                id=_id, key=child_key, value=value
 | 
			
		||||
            )
 | 
			
		||||
            return child
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FullValueMixin:
 | 
			
		||||
    _full_value = None
 | 
			
		||||
| 
						 | 
				
			
			@ -246,7 +276,85 @@ class NodeAssetsMixin:
 | 
			
		|||
        return Asset.objects.filter(nodes__key__regex=pattern)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Node(OrgModelMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixin):
 | 
			
		||||
class SomeNodesMixin:
 | 
			
		||||
    key = ''
 | 
			
		||||
    default_key = '1'
 | 
			
		||||
    default_value = 'Default'
 | 
			
		||||
    ungrouped_key = '-10'
 | 
			
		||||
    ungrouped_value = _('ungrouped')
 | 
			
		||||
    empty_key = '-11'
 | 
			
		||||
    empty_value = _("empty")
 | 
			
		||||
 | 
			
		||||
    def is_default_node(self):
 | 
			
		||||
        return self.key == self.default_key
 | 
			
		||||
 | 
			
		||||
    def is_org_root(self):
 | 
			
		||||
        if self.key.isdigit():
 | 
			
		||||
            return True
 | 
			
		||||
        else:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def create_org_root_node(cls):
 | 
			
		||||
        # 如果使用current_org 在set_current_org时会死循环
 | 
			
		||||
        ori_org = get_current_org()
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            if not ori_org.is_real():
 | 
			
		||||
                return cls.default_node()
 | 
			
		||||
            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)
 | 
			
		||||
            if not org_nodes_roots_keys:
 | 
			
		||||
                org_nodes_roots_keys = ['1']
 | 
			
		||||
            key = max([int(k) for k in org_nodes_roots_keys])
 | 
			
		||||
            key = str(key + 1) if key != 0 else '2'
 | 
			
		||||
            set_current_org(ori_org)
 | 
			
		||||
            root = cls.objects.create(key=key, value=ori_org.name)
 | 
			
		||||
            return root
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def org_root(cls):
 | 
			
		||||
        root = cls.objects.filter(key__regex=r'^[0-9]+$')
 | 
			
		||||
        if root:
 | 
			
		||||
            return root[0]
 | 
			
		||||
        else:
 | 
			
		||||
            return cls.create_org_root_node()
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def ungrouped_node(cls):
 | 
			
		||||
        with tmp_to_org(Organization.system()):
 | 
			
		||||
            defaults = {'value': cls.ungrouped_key}
 | 
			
		||||
            obj, created = cls.objects.get_or_create(
 | 
			
		||||
                defaults=defaults, key=cls.ungrouped_key
 | 
			
		||||
            )
 | 
			
		||||
            return obj
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def empty_node(cls):
 | 
			
		||||
        with tmp_to_org(Organization.system()):
 | 
			
		||||
            defaults = {'value': cls.empty_value}
 | 
			
		||||
            obj, created = cls.objects.get_or_create(
 | 
			
		||||
                defaults=defaults, key=cls.empty_key
 | 
			
		||||
            )
 | 
			
		||||
            return obj
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def default_node(cls):
 | 
			
		||||
        with tmp_to_org(Organization.default()):
 | 
			
		||||
            defaults = {'value': cls.default_value}
 | 
			
		||||
            obj, created = cls.objects.get_or_create(
 | 
			
		||||
                defaults=defaults, key=cls.default_key,
 | 
			
		||||
            )
 | 
			
		||||
            return obj
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def initial_some_nodes(cls):
 | 
			
		||||
        cls.default_node()
 | 
			
		||||
        cls.empty_node()
 | 
			
		||||
        cls.ungrouped_node()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Node(OrgModelMixin, SomeNodesMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixin):
 | 
			
		||||
    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"))
 | 
			
		||||
| 
						 | 
				
			
			@ -290,75 +398,13 @@ class Node(OrgModelMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixi
 | 
			
		|||
    def level(self):
 | 
			
		||||
        return len(self.key.split(':'))
 | 
			
		||||
 | 
			
		||||
    def get_next_child_key(self):
 | 
			
		||||
        mark = self.child_mark
 | 
			
		||||
        self.child_mark += 1
 | 
			
		||||
        self.save()
 | 
			
		||||
        return "{}:{}".format(self.key, mark)
 | 
			
		||||
 | 
			
		||||
    def get_next_child_preset_name(self):
 | 
			
		||||
        name = ugettext("New node")
 | 
			
		||||
        values = [
 | 
			
		||||
            child.value[child.value.rfind(' '):]
 | 
			
		||||
            for child in self.get_children()
 | 
			
		||||
            if child.value.startswith(name)
 | 
			
		||||
        ]
 | 
			
		||||
        values = [int(value) for value in values if value.strip().isdigit()]
 | 
			
		||||
        count = max(values) + 1 if values else 1
 | 
			
		||||
        return '{} {}'.format(name, count)
 | 
			
		||||
 | 
			
		||||
    def create_child(self, value, _id=None):
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            child_key = self.get_next_child_key()
 | 
			
		||||
            child = self.__class__.objects.create(
 | 
			
		||||
                id=_id, key=child_key, value=value
 | 
			
		||||
            )
 | 
			
		||||
            return child
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def refresh_nodes(cls):
 | 
			
		||||
        cls.refresh_tree()
 | 
			
		||||
 | 
			
		||||
    def is_default_node(self):
 | 
			
		||||
        return self.key == '1'
 | 
			
		||||
 | 
			
		||||
    def is_org_root(self):
 | 
			
		||||
        if self.key.isdigit():
 | 
			
		||||
            return True
 | 
			
		||||
        else:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def create_org_root_node(cls):
 | 
			
		||||
        # 如果使用current_org 在set_current_org时会死循环
 | 
			
		||||
        ori_org = get_current_org()
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            if not ori_org.is_real():
 | 
			
		||||
                return cls.default_node()
 | 
			
		||||
            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)
 | 
			
		||||
            if not org_nodes_roots_keys:
 | 
			
		||||
                org_nodes_roots_keys = ['1']
 | 
			
		||||
            key = max([int(k) for k in org_nodes_roots_keys])
 | 
			
		||||
            key = str(key + 1) if key != 0 else '2'
 | 
			
		||||
            set_current_org(ori_org)
 | 
			
		||||
            root = cls.objects.create(key=key, value=ori_org.name)
 | 
			
		||||
            return root
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def org_root(cls):
 | 
			
		||||
        root = cls.objects.filter(key__regex=r'^[0-9]+$')
 | 
			
		||||
        if root:
 | 
			
		||||
            return root[0]
 | 
			
		||||
        else:
 | 
			
		||||
            return cls.create_org_root_node()
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def default_node(cls):
 | 
			
		||||
        defaults = {'value': 'Default'}
 | 
			
		||||
        obj, created = cls.objects.get_or_create(defaults=defaults, key='1')
 | 
			
		||||
        return obj
 | 
			
		||||
    def refresh_assets(cls):
 | 
			
		||||
        cls.refresh_node_assets()
 | 
			
		||||
 | 
			
		||||
    def as_tree_node(self):
 | 
			
		||||
        from common.tree import TreeNode
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,7 +14,9 @@ __all__ = [
 | 
			
		|||
 | 
			
		||||
class NodeSerializer(BulkOrgResourceModelSerializer):
 | 
			
		||||
    name = serializers.ReadOnlyField(source='value')
 | 
			
		||||
    value = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("value"))
 | 
			
		||||
    value = serializers.CharField(
 | 
			
		||||
        required=False, allow_blank=True, allow_null=True, label=_("value")
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = Node
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,8 +2,9 @@
 | 
			
		|||
#
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from django.db.models.signals import (
 | 
			
		||||
    post_save, m2m_changed, pre_delete, pre_save, pre_init, post_init
 | 
			
		||||
    post_save, m2m_changed, post_delete
 | 
			
		||||
)
 | 
			
		||||
from django.db.models.aggregates import Count
 | 
			
		||||
from django.dispatch import receiver
 | 
			
		||||
 | 
			
		||||
from common.utils import get_logger
 | 
			
		||||
| 
						 | 
				
			
			@ -29,29 +30,36 @@ def test_asset_conn_on_created(asset):
 | 
			
		|||
    test_asset_connectivity_util.delay([asset])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier")
 | 
			
		||||
@receiver(post_save, sender=Asset)
 | 
			
		||||
@on_transaction_commit
 | 
			
		||||
def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
 | 
			
		||||
    """
 | 
			
		||||
    当资产创建时,更新硬件信息,更新可连接性
 | 
			
		||||
    确保资产必须属于一个节点
 | 
			
		||||
    """
 | 
			
		||||
    if created:
 | 
			
		||||
        logger.info("Asset `{}` create signal received".format(instance))
 | 
			
		||||
        logger.info("Asset create signal recv: {}".format(instance))
 | 
			
		||||
 | 
			
		||||
        # 获取资产硬件信息
 | 
			
		||||
        update_asset_hardware_info_on_created(instance)
 | 
			
		||||
        test_asset_conn_on_created(instance)
 | 
			
		||||
 | 
			
		||||
        # 确保资产存在一个节点
 | 
			
		||||
        has_node = instance.nodes.all().exists()
 | 
			
		||||
        if not has_node:
 | 
			
		||||
            instance.nodes.add(Node.org_root())
 | 
			
		||||
 | 
			
		||||
@receiver(pre_delete, sender=Asset, dispatch_uid="my_unique_identifier")
 | 
			
		||||
 | 
			
		||||
@receiver(post_delete, sender=Asset)
 | 
			
		||||
def on_asset_delete(sender, instance=None, **kwargs):
 | 
			
		||||
    """
 | 
			
		||||
    当资产删除时,刷新节点,节点中存在节点和资产的关系
 | 
			
		||||
    """
 | 
			
		||||
    Node.refresh_nodes()
 | 
			
		||||
    logger.debug("Asset delete signal recv: {}".format(instance))
 | 
			
		||||
    Node.refresh_assets()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@receiver(post_save, sender=SystemUser, dispatch_uid="my_unique_identifier")
 | 
			
		||||
@receiver(post_save, sender=SystemUser, dispatch_uid="jms")
 | 
			
		||||
def on_system_user_update(sender, instance=None, created=True, **kwargs):
 | 
			
		||||
    """
 | 
			
		||||
    当系统用户更新时,可能更新了秘钥,用户名等,这时要自动推送系统用户到资产上,
 | 
			
		||||
| 
						 | 
				
			
			@ -60,61 +68,126 @@ def on_system_user_update(sender, instance=None, created=True, **kwargs):
 | 
			
		|||
    关联到上面
 | 
			
		||||
    """
 | 
			
		||||
    if instance and not created:
 | 
			
		||||
        logger.info("System user `{}` update signal received".format(instance))
 | 
			
		||||
        logger.info("System user update signal recv: {}".format(instance))
 | 
			
		||||
        assets = instance.assets.all().valid()
 | 
			
		||||
        push_system_user_to_assets.delay(instance, assets)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@receiver(m2m_changed, sender=SystemUser.assets.through, dispatch_uid="my_unique_identifier")
 | 
			
		||||
def on_system_user_assets_change(sender, instance=None, **kwargs):
 | 
			
		||||
@receiver(m2m_changed, sender=SystemUser.assets.through)
 | 
			
		||||
def on_system_user_assets_change(sender, instance=None, action='', model=None, pk_set=None, **kwargs):
 | 
			
		||||
    """
 | 
			
		||||
    当系统用户和资产关系发生变化时,应该重新推送系统用户到新添加的资产中
 | 
			
		||||
    """
 | 
			
		||||
    if instance and kwargs["action"] == "post_add":
 | 
			
		||||
        assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
 | 
			
		||||
        push_system_user_to_assets.delay(instance, assets)
 | 
			
		||||
    if action != "post_add":
 | 
			
		||||
        return
 | 
			
		||||
    logger.debug("System user assets change signal recv: {}".format(instance))
 | 
			
		||||
    queryset = model.objects.filter(pk__in=pk_set)
 | 
			
		||||
    if model == Asset:
 | 
			
		||||
        system_users = [instance]
 | 
			
		||||
        assets = queryset
 | 
			
		||||
    else:
 | 
			
		||||
        system_users = queryset
 | 
			
		||||
        assets = [instance]
 | 
			
		||||
    for system_user in system_users:
 | 
			
		||||
        push_system_user_to_assets.delay(system_user, assets)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@receiver(m2m_changed, sender=SystemUser.nodes.through, dispatch_uid="my_unique_identifier")
 | 
			
		||||
def on_system_user_nodes_change(sender, instance=None, **kwargs):
 | 
			
		||||
@receiver(m2m_changed, sender=SystemUser.nodes.through)
 | 
			
		||||
def on_system_user_nodes_change(sender, instance=None, action=None, model=None, pk_set=None, **kwargs):
 | 
			
		||||
    """
 | 
			
		||||
    当系统用户和节点关系发生变化时,应该将节点关联到新的系统用户上
 | 
			
		||||
    当系统用户和节点关系发生变化时,应该将节点下资产关联到新的系统用户上
 | 
			
		||||
    """
 | 
			
		||||
    if instance and kwargs["action"] == "post_add":
 | 
			
		||||
        logger.info("System user `{}` nodes update signal received".format(instance))
 | 
			
		||||
        nodes_keys = kwargs['model'].objects.filter(
 | 
			
		||||
            pk__in=kwargs['pk_set']
 | 
			
		||||
        ).values_list('key', flat=True)
 | 
			
		||||
    if action != "post_add":
 | 
			
		||||
        return
 | 
			
		||||
    logger.info("System user `{}` nodes update signal recv".format(instance))
 | 
			
		||||
 | 
			
		||||
    queryset = model.objects.filter(pk__in=pk_set)
 | 
			
		||||
    if model == Node:
 | 
			
		||||
        nodes_keys = queryset.values_list('key', flat=True)
 | 
			
		||||
        system_users = [instance]
 | 
			
		||||
    else:
 | 
			
		||||
        nodes_keys = [instance.key]
 | 
			
		||||
        system_users = queryset
 | 
			
		||||
    assets = Node.get_nodes_all_assets(nodes_keys)
 | 
			
		||||
        instance.assets.add(*tuple(assets))
 | 
			
		||||
    for system_user in system_users:
 | 
			
		||||
        system_user.assets.add(*tuple(assets))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@receiver(m2m_changed, sender=Asset.nodes.through, dispatch_uid="my_unique_identifier")
 | 
			
		||||
def on_asset_nodes_changed(sender, instance=None, **kwargs):
 | 
			
		||||
@receiver(m2m_changed, sender=Asset.nodes.through)
 | 
			
		||||
def on_asset_nodes_change(sender, instance=None, action='', **kwargs):
 | 
			
		||||
    """
 | 
			
		||||
    资产节点发生变化时,刷新节点
 | 
			
		||||
    """
 | 
			
		||||
    if action.startswith('post'):
 | 
			
		||||
        logger.debug("Asset nodes change signal recv: {}".format(instance))
 | 
			
		||||
        Node.refresh_assets()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@receiver(m2m_changed, sender=Asset.nodes.through)
 | 
			
		||||
def on_asset_nodes_add(sender, instance=None, action='', model=None, pk_set=None, **kwargs):
 | 
			
		||||
    """
 | 
			
		||||
    当资产的节点发生变化时,或者 当节点的资产关系发生变化时,
 | 
			
		||||
    节点下新增的资产,添加到节点关联的系统用户中
 | 
			
		||||
    并刷新节点
 | 
			
		||||
    """
 | 
			
		||||
    if isinstance(instance, Asset):
 | 
			
		||||
        logger.debug("Asset nodes change signal received: {}".format(instance))
 | 
			
		||||
        # 节点资产发生变化时,将资产关联到节点关联的系统用户
 | 
			
		||||
        if kwargs['action'] == 'post_add':
 | 
			
		||||
            nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
 | 
			
		||||
    if action != "post_add":
 | 
			
		||||
        return
 | 
			
		||||
    logger.debug("Assets node add signal recv: {}".format(action))
 | 
			
		||||
    queryset = model.objects.filter(pk__in=pk_set)
 | 
			
		||||
    if model == Node:
 | 
			
		||||
        nodes = queryset
 | 
			
		||||
        assets = [instance]
 | 
			
		||||
    else:
 | 
			
		||||
        nodes = [instance]
 | 
			
		||||
        assets = queryset
 | 
			
		||||
    # 节点资产发生变化时,将资产关联到节点关联的系统用户, 只关注新增的
 | 
			
		||||
    system_users_assets = defaultdict(set)
 | 
			
		||||
    system_users = SystemUser.objects.filter(nodes__in=nodes)
 | 
			
		||||
    for system_user in system_users:
 | 
			
		||||
                system_users_assets[system_user].add(instance)
 | 
			
		||||
            for system_user, assets in system_users_assets.items():
 | 
			
		||||
                system_user.assets.add(*tuple(assets))
 | 
			
		||||
    if isinstance(instance, Node):
 | 
			
		||||
        logger.debug("Node assets change signal received: {}".format(instance))
 | 
			
		||||
 | 
			
		||||
    Node.refresh_nodes()
 | 
			
		||||
        system_users_assets[system_user].update(set(assets))
 | 
			
		||||
    for system_user, _assets in system_users_assets.items():
 | 
			
		||||
        system_user.assets.add(*tuple(_assets))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@receiver(post_save, sender=Node)
 | 
			
		||||
def on_node_update_or_created(sender, instance=None, created=False, **kwargs):
 | 
			
		||||
@receiver(m2m_changed, sender=Asset.nodes.through)
 | 
			
		||||
def on_asset_nodes_remove(sender, instance=None, action='', model=None,
 | 
			
		||||
                          pk_set=None, **kwargs):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    监控资产删除节点关系, 或节点删除资产,避免产生游离资产
 | 
			
		||||
    """
 | 
			
		||||
    if action not in ["post_remove", "pre_clear", "post_clear"]:
 | 
			
		||||
        return
 | 
			
		||||
    if action == "pre_clear":
 | 
			
		||||
        if model == Node:
 | 
			
		||||
            instance._nodes = list(instance.nodes.all())
 | 
			
		||||
        else:
 | 
			
		||||
            instance._assets = list(instance.assets.all())
 | 
			
		||||
        return
 | 
			
		||||
    logger.debug("Assets node remove signal recv: {}".format(action))
 | 
			
		||||
    if action == "post_remove":
 | 
			
		||||
        queryset = model.objects.filter(pk__in=pk_set)
 | 
			
		||||
    else:
 | 
			
		||||
        if model == Node:
 | 
			
		||||
            queryset = instance._nodes
 | 
			
		||||
        else:
 | 
			
		||||
            queryset = instance._assets
 | 
			
		||||
    if model == Node:
 | 
			
		||||
        assets = [instance]
 | 
			
		||||
    else:
 | 
			
		||||
        assets = queryset
 | 
			
		||||
    if isinstance(assets, list):
 | 
			
		||||
        assets_not_has_node = []
 | 
			
		||||
        for asset in assets:
 | 
			
		||||
            if asset.nodes.all().count() == 0:
 | 
			
		||||
                assets_not_has_node.append(asset.id)
 | 
			
		||||
    else:
 | 
			
		||||
        assets_not_has_node = assets.annotate(nodes_count=Count('nodes'))\
 | 
			
		||||
            .filter(nodes_count=0).values_list('id', flat=True)
 | 
			
		||||
    Node.org_root().assets.add(*tuple(assets_not_has_node))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@receiver([post_save, post_delete], sender=Node)
 | 
			
		||||
def on_node_update_or_created(sender, **kwargs):
 | 
			
		||||
    # 刷新节点
 | 
			
		||||
    Node.refresh_nodes()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -37,6 +37,7 @@
 | 
			
		|||
   <div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
 | 
			
		||||
       <div class="file-manager" id="tree-node-id">
 | 
			
		||||
           <div id="{% block treeID %}nodeTree{% endblock %}" class="ztree">
 | 
			
		||||
           {% trans 'Loading' %} ...
 | 
			
		||||
           </div>
 | 
			
		||||
           <div class="clearfix"></div>
 | 
			
		||||
       </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -124,7 +124,6 @@
 | 
			
		|||
   </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
{% include 'assets/_node_tree.html' %}
 | 
			
		||||
{% include 'assets/_asset_update_modal.html' %}
 | 
			
		||||
{% include 'assets/_asset_import_modal.html' %}
 | 
			
		||||
{% include 'assets/_asset_list_modal.html' %}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -59,6 +59,8 @@ class TreeService(Tree):
 | 
			
		|||
    tag_sep = ' / '
 | 
			
		||||
    cache_key = '_NODE_FULL_TREE'
 | 
			
		||||
    cache_time = 3600
 | 
			
		||||
    has_empty_node = False
 | 
			
		||||
    has_ungrouped_node = False
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
| 
						 | 
				
			
			@ -119,9 +121,9 @@ class TreeService(Tree):
 | 
			
		|||
        return [self.get_node(i, deep=deep) for i in ancestor_ids]
 | 
			
		||||
 | 
			
		||||
    def get_node_full_tag(self, nid):
 | 
			
		||||
        ancestors = self.ancestors(nid)
 | 
			
		||||
        ancestors = self.ancestors(nid, with_self=True)
 | 
			
		||||
        ancestors.reverse()
 | 
			
		||||
        return self.tag_sep.join(n.tag for n in ancestors)
 | 
			
		||||
        return self.tag_sep.join([n.tag for n in ancestors])
 | 
			
		||||
 | 
			
		||||
    def get_family(self, nid, deep=False):
 | 
			
		||||
        ancestors = self.ancestors(nid, with_self=False, deep=deep)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -290,10 +290,10 @@ LOGGING = {
 | 
			
		|||
            'handlers': ['syslog'],
 | 
			
		||||
            'level': 'INFO'
 | 
			
		||||
        },
 | 
			
		||||
        'django.db': {
 | 
			
		||||
            'handlers': ['console', 'file'],
 | 
			
		||||
            'level': 'DEBUG'
 | 
			
		||||
        }
 | 
			
		||||
        # 'django.db': {
 | 
			
		||||
        #     'handlers': ['console', 'file'],
 | 
			
		||||
        #     'level': 'DEBUG'
 | 
			
		||||
        # }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -64,8 +64,11 @@ class OrgModelMixin(models.Model):
 | 
			
		|||
    sep = '@'
 | 
			
		||||
 | 
			
		||||
    def save(self, *args, **kwargs):
 | 
			
		||||
        if current_org is not None and current_org.is_real():
 | 
			
		||||
            self.org_id = current_org.id
 | 
			
		||||
        org = get_current_org()
 | 
			
		||||
        if org is not None and (org.is_real() or org.is_system()):
 | 
			
		||||
            self.org_id = org.id
 | 
			
		||||
        elif org is not None and org.is_default():
 | 
			
		||||
            self.org_id = ''
 | 
			
		||||
        return super().save(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,6 +21,8 @@ class Organization(models.Model):
 | 
			
		|||
    ROOT_NAME = 'ROOT'
 | 
			
		||||
    DEFAULT_ID = 'DEFAULT'
 | 
			
		||||
    DEFAULT_NAME = 'DEFAULT'
 | 
			
		||||
    SYSTEM_ID = '00000000-0000-0000-0000-000000000002'
 | 
			
		||||
    SYSTEM_NAME = 'SYSTEM'
 | 
			
		||||
    _user_admin_orgs = None
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
| 
						 | 
				
			
			@ -55,6 +57,8 @@ class Organization(models.Model):
 | 
			
		|||
            return cls.default()
 | 
			
		||||
        elif id_or_name in [cls.ROOT_ID, cls.ROOT_NAME]:
 | 
			
		||||
            return cls.root()
 | 
			
		||||
        elif id_or_name in [cls.SYSTEM_ID, cls.SYSTEM_NAME]:
 | 
			
		||||
            return cls.system()
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            if is_uuid(id_or_name):
 | 
			
		||||
| 
						 | 
				
			
			@ -89,7 +93,7 @@ class Organization(models.Model):
 | 
			
		|||
        return False
 | 
			
		||||
 | 
			
		||||
    def is_real(self):
 | 
			
		||||
        return self.id not in (self.DEFAULT_NAME, self.ROOT_ID)
 | 
			
		||||
        return self.id not in (self.DEFAULT_NAME, self.ROOT_ID, self.SYSTEM_ID)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_user_admin_orgs(cls, user):
 | 
			
		||||
| 
						 | 
				
			
			@ -111,17 +115,18 @@ class Organization(models.Model):
 | 
			
		|||
    def root(cls):
 | 
			
		||||
        return cls(id=cls.ROOT_ID, name=cls.ROOT_NAME)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def system(cls):
 | 
			
		||||
        return cls(id=cls.SYSTEM_ID, name=cls.SYSTEM_NAME)
 | 
			
		||||
 | 
			
		||||
    def is_root(self):
 | 
			
		||||
        if self.id is self.ROOT_ID:
 | 
			
		||||
            return True
 | 
			
		||||
        else:
 | 
			
		||||
            return False
 | 
			
		||||
        return self.id is self.ROOT_ID
 | 
			
		||||
 | 
			
		||||
    def is_default(self):
 | 
			
		||||
        if self.id is self.DEFAULT_ID:
 | 
			
		||||
            return True
 | 
			
		||||
        else:
 | 
			
		||||
            return False
 | 
			
		||||
        return self.id is self.DEFAULT_ID
 | 
			
		||||
 | 
			
		||||
    def is_system(self):
 | 
			
		||||
        return self.id is self.SYSTEM_ID
 | 
			
		||||
 | 
			
		||||
    def change_to(self):
 | 
			
		||||
        from .utils import set_current_org
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
from werkzeug.local import LocalProxy
 | 
			
		||||
from contextlib import contextmanager
 | 
			
		||||
 | 
			
		||||
from common.local import thread_local
 | 
			
		||||
from .models import Organization
 | 
			
		||||
| 
						 | 
				
			
			@ -52,4 +53,22 @@ def get_current_org_id_for_serializer():
 | 
			
		|||
    return org_id
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@contextmanager
 | 
			
		||||
def tmp_to_root_org():
 | 
			
		||||
    ori_org = get_current_org()
 | 
			
		||||
    set_to_root_org()
 | 
			
		||||
    yield
 | 
			
		||||
    if ori_org is not None:
 | 
			
		||||
        set_current_org(ori_org)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@contextmanager
 | 
			
		||||
def tmp_to_org(org):
 | 
			
		||||
    ori_org = get_current_org()
 | 
			
		||||
    set_current_org(org)
 | 
			
		||||
    yield
 | 
			
		||||
    if ori_org is not None:
 | 
			
		||||
        set_current_org(ori_org)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
current_org = LocalProxy(get_current_org)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -40,24 +40,14 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
 | 
			
		|||
        return self.serializer_class
 | 
			
		||||
 | 
			
		||||
    def filter_valid(self, queryset):
 | 
			
		||||
        valid = self.request.query_params.get('is_valid', None)
 | 
			
		||||
        if valid is None:
 | 
			
		||||
        valid_query = self.request.query_params.get('is_valid', None)
 | 
			
		||||
        if valid_query is None:
 | 
			
		||||
            return queryset
 | 
			
		||||
        if valid in ['0', 'N', 'false', 'False']:
 | 
			
		||||
            valid = False
 | 
			
		||||
        invalid = valid_query in ['0', 'N', 'false', 'False']
 | 
			
		||||
        if invalid:
 | 
			
		||||
            queryset = queryset.invalid()
 | 
			
		||||
        else:
 | 
			
		||||
            valid = True
 | 
			
		||||
        now = timezone.now()
 | 
			
		||||
        if valid:
 | 
			
		||||
            queryset = queryset.filter(is_active=True).filter(
 | 
			
		||||
                date_start__lt=now, date_expired__gt=now,
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            queryset = queryset.filter(
 | 
			
		||||
                Q(is_active=False) |
 | 
			
		||||
                Q(date_start__gt=now) |
 | 
			
		||||
                Q(date_expired__lt=now)
 | 
			
		||||
            )
 | 
			
		||||
            queryset = queryset.valid()
 | 
			
		||||
        return queryset
 | 
			
		||||
 | 
			
		||||
    def filter_system_user(self, queryset):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,10 +15,6 @@ from orgs.utils import set_to_root_org
 | 
			
		|||
from ..utils import (
 | 
			
		||||
    ParserNode, AssetPermissionUtilV2
 | 
			
		||||
)
 | 
			
		||||
from .mixin import (
 | 
			
		||||
    UserPermissionCacheMixin, GrantAssetsMixin, NodesWithUngroupMixin
 | 
			
		||||
)
 | 
			
		||||
from .. import const
 | 
			
		||||
from ..hands import User, Asset, Node, SystemUser, NodeSerializer
 | 
			
		||||
from .. import serializers
 | 
			
		||||
from ..models import Action
 | 
			
		||||
| 
						 | 
				
			
			@ -66,8 +62,8 @@ class UserGrantedAssetsApi(UserPermissionMixin, ListAPIView):
 | 
			
		|||
    permission_classes = (IsOrgAdminOrAppUser,)
 | 
			
		||||
    serializer_class = serializers.AssetGrantedSerializer
 | 
			
		||||
    only_fields = serializers.AssetGrantedSerializer.Meta.only_fields
 | 
			
		||||
    filter_fields = ['hostname', 'ip']
 | 
			
		||||
    search_fields = filter_fields
 | 
			
		||||
    filter_fields = ['hostname', 'ip', 'id', 'comment']
 | 
			
		||||
    search_fields = ['hostname', 'ip', 'comment']
 | 
			
		||||
 | 
			
		||||
    def filter_by_nodes(self, queryset):
 | 
			
		||||
        node_id = self.request.query_params.get("node")
 | 
			
		||||
| 
						 | 
				
			
			@ -109,12 +105,21 @@ class UserGrantedNodesApi(UserPermissionMixin, ListAPIView):
 | 
			
		|||
    查询用户授权的所有节点的API
 | 
			
		||||
    """
 | 
			
		||||
    permission_classes = (IsOrgAdminOrAppUser,)
 | 
			
		||||
    serializer_class = serializers.GrantedNodeSerializer
 | 
			
		||||
    serializer_class = serializers.NodeGrantedSerializer
 | 
			
		||||
    only_fields = NodeSerializer.Meta.only_fields
 | 
			
		||||
    util = None
 | 
			
		||||
 | 
			
		||||
    def get(self, request, *args, **kwargs):
 | 
			
		||||
        self.util = AssetPermissionUtilV2(self.obj)
 | 
			
		||||
        return super().get(request, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def get_serializer_context(self):
 | 
			
		||||
        context = super().get_serializer_context()
 | 
			
		||||
        context["tree"] = self.util.user_tree
 | 
			
		||||
        return context
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        util = AssetPermissionUtilV2(self.obj)
 | 
			
		||||
        node_keys = util.get_nodes()
 | 
			
		||||
        node_keys = self.util.get_nodes()
 | 
			
		||||
        queryset = Node.objects.filter(key__in=node_keys)
 | 
			
		||||
        return queryset
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -131,7 +136,6 @@ class UserGrantedNodeChildrenApi(UserGrantedNodesApi):
 | 
			
		|||
        system_user_id = self.request.query_params.get("system_user")
 | 
			
		||||
        self.util = AssetPermissionUtilV2(self.obj)
 | 
			
		||||
        if system_user_id:
 | 
			
		||||
            system_user = get_object_or_404(SystemUser, id=system_user_id)
 | 
			
		||||
            self.util.filter_permissions(system_users=system_user_id)
 | 
			
		||||
        self.tree = self.util.get_user_tree()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,10 @@
 | 
			
		|||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
from django.utils.translation import ugettext_lazy as _
 | 
			
		||||
 | 
			
		||||
UNGROUPED_NODE_ID = "00000000-0000-0000-0000-000000000002"
 | 
			
		||||
UNGROUPED_NODE_KEY = '-2'
 | 
			
		||||
UNGROUPED_NODE_VALUE = _("Ungrouped")
 | 
			
		||||
EMPTY_NODE_ID = "00000000-0000-0000-0000-000000000003"
 | 
			
		||||
EMPTY_NODE_KEY = "1:-2"
 | 
			
		||||
EMPTY_NODE_KEY = "-3"
 | 
			
		||||
EMPTY_NODE_VALUE = _("Empty")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,7 @@ from django.db import models
 | 
			
		|||
from django.db.models import Q
 | 
			
		||||
from django.utils.translation import ugettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from common.utils import date_expired_default, set_or_append_attr_bulk
 | 
			
		||||
from common.utils import date_expired_default
 | 
			
		||||
from orgs.mixins.models import OrgModelMixin
 | 
			
		||||
from assets.models import Asset, SystemUser, Node
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@
 | 
			
		|||
import uuid
 | 
			
		||||
from django.utils.translation import ugettext_lazy as _
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.db.models import Q
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from orgs.mixins.models import OrgModelMixin
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -24,6 +25,18 @@ class BasePermissionQuerySet(models.QuerySet):
 | 
			
		|||
        return self.active().filter(date_start__lt=timezone.now()) \
 | 
			
		||||
            .filter(date_expired__gt=timezone.now())
 | 
			
		||||
 | 
			
		||||
    def inactive(self):
 | 
			
		||||
        return self.filter(is_active=False)
 | 
			
		||||
 | 
			
		||||
    def invalid(self):
 | 
			
		||||
        now = timezone.now
 | 
			
		||||
        q = (
 | 
			
		||||
            Q(is_active=False) |
 | 
			
		||||
            Q(date_start__gt=now) |
 | 
			
		||||
            Q(date_expired__lt=now)
 | 
			
		||||
        )
 | 
			
		||||
        return self.filter(q)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BasePermissionManager(OrgManager):
 | 
			
		||||
    def valid(self):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,8 +9,8 @@ from assets.serializers import ProtocolsField
 | 
			
		|||
from .asset_permission import ActionsField
 | 
			
		||||
 | 
			
		||||
__all__ = [
 | 
			
		||||
    'GrantedNodeSerializer',
 | 
			
		||||
    'NodeGrantedSerializer', 'AssetGrantedSerializer',
 | 
			
		||||
    'NodeGrantedSerializer',
 | 
			
		||||
    'AssetGrantedSerializer',
 | 
			
		||||
    'ActionsSerializer', 'AssetSystemUserSerializer',
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -43,36 +43,29 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
 | 
			
		|||
            "id", "hostname", "ip", "protocols", "os", 'domain',
 | 
			
		||||
            "platform", "comment", "org_id",
 | 
			
		||||
        ]
 | 
			
		||||
        fields = only_fields
 | 
			
		||||
        fields = only_fields + ['org_name']
 | 
			
		||||
        read_only_fields = fields
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NodeGrantedSerializer(serializers.ModelSerializer):
 | 
			
		||||
    """
 | 
			
		||||
    授权资产组
 | 
			
		||||
    """
 | 
			
		||||
    # assets_granted = AssetGrantedSerializer(many=True, read_only=True)
 | 
			
		||||
    # assets_amount = serializers.ReadOnlyField()
 | 
			
		||||
    name = serializers.ReadOnlyField(source='value')
 | 
			
		||||
    assets_amount = serializers.SerializerMethodField()
 | 
			
		||||
 | 
			
		||||
    # assets_only_fields = AssetGrantedSerializer.Meta.only_fields
 | 
			
		||||
    # system_users_only_fields = AssetGrantedSerializer.system_users_only_fields
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = Node
 | 
			
		||||
        only_fields = ['id', 'key', 'value', "org_id"]
 | 
			
		||||
        fields = only_fields + ['name']
 | 
			
		||||
        read_only_fields = fields
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GrantedNodeSerializer(serializers.ModelSerializer):
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = Node
 | 
			
		||||
        fields = [
 | 
			
		||||
            'id', 'name', 'key', 'value',
 | 
			
		||||
            'id', 'name', 'key', 'value', 'org_id', "assets_amount"
 | 
			
		||||
        ]
 | 
			
		||||
        read_only_fields = fields
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        self.tree = self.context.get("tree")
 | 
			
		||||
 | 
			
		||||
    def get_assets_amount(self, obj):
 | 
			
		||||
        if not self.tree:
 | 
			
		||||
            return 0
 | 
			
		||||
        return self.tree.assets_amount(obj.key)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ActionsSerializer(serializers.Serializer):
 | 
			
		||||
    actions = ActionsField(read_only=True)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,25 +15,23 @@ logger = get_logger(__file__)
 | 
			
		|||
@on_transaction_commit
 | 
			
		||||
def on_permission_created(sender, instance=None, created=False, **kwargs):
 | 
			
		||||
    pass
 | 
			
		||||
    # AssetPermissionUtil.expire_all_cache()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@receiver(post_save, sender=AssetPermission)
 | 
			
		||||
def on_permission_update(sender, **kwargs):
 | 
			
		||||
    pass
 | 
			
		||||
    # AssetPermissionUtil.expire_all_cache()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@receiver(post_delete, sender=AssetPermission)
 | 
			
		||||
def on_permission_delete(sender, **kwargs):
 | 
			
		||||
    pass
 | 
			
		||||
    # AssetPermissionUtil.expire_all_cache()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@receiver(m2m_changed, sender=AssetPermission.nodes.through)
 | 
			
		||||
def on_permission_nodes_changed(sender, instance=None, **kwargs):
 | 
			
		||||
    # AssetPermissionUtil.expire_all_cache()
 | 
			
		||||
    if isinstance(instance, AssetPermission) and kwargs['action'] == 'post_add':
 | 
			
		||||
def on_permission_nodes_changed(sender, instance=None, action='', **kwargs):
 | 
			
		||||
    if action != 'post_add':
 | 
			
		||||
        return
 | 
			
		||||
    if isinstance(instance, AssetPermission):
 | 
			
		||||
        logger.debug("Asset permission nodes change signal received")
 | 
			
		||||
        nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
 | 
			
		||||
        system_users = instance.system_users.all()
 | 
			
		||||
| 
						 | 
				
			
			@ -42,9 +40,10 @@ def on_permission_nodes_changed(sender, instance=None, **kwargs):
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
@receiver(m2m_changed, sender=AssetPermission.assets.through)
 | 
			
		||||
def on_permission_assets_changed(sender, instance=None, **kwargs):
 | 
			
		||||
    # AssetPermissionUtil.expire_all_cache()
 | 
			
		||||
    if isinstance(instance, AssetPermission) and kwargs['action'] == 'post_add':
 | 
			
		||||
def on_permission_assets_changed(sender, instance=None, action='', **kwargs):
 | 
			
		||||
    if action != 'post_add':
 | 
			
		||||
        return
 | 
			
		||||
    if isinstance(instance, AssetPermission):
 | 
			
		||||
        logger.debug("Asset permission assets change signal received")
 | 
			
		||||
        assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
 | 
			
		||||
        system_users = instance.system_users.all()
 | 
			
		||||
| 
						 | 
				
			
			@ -53,13 +52,15 @@ def on_permission_assets_changed(sender, instance=None, **kwargs):
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
@receiver(m2m_changed, sender=AssetPermission.system_users.through)
 | 
			
		||||
def on_permission_system_users_changed(sender, instance=None, **kwargs):
 | 
			
		||||
    # AssetPermissionUtil.expire_all_cache()
 | 
			
		||||
    if isinstance(instance, AssetPermission) and kwargs['action'] == 'post_add':
 | 
			
		||||
        logger.debug("Asset permission system_users change signal received")
 | 
			
		||||
def on_permission_system_users_changed(sender, instance=None, action='', **kwargs):
 | 
			
		||||
    if action != 'post_add':
 | 
			
		||||
        return
 | 
			
		||||
    if isinstance(instance, AssetPermission):
 | 
			
		||||
        system_users = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
 | 
			
		||||
        logger.debug("Asset permission system_users change signal received")
 | 
			
		||||
        assets = instance.assets.all()
 | 
			
		||||
        nodes = instance.nodes.all()
 | 
			
		||||
        for system_user in system_users:
 | 
			
		||||
            system_user.nodes.add(*tuple(nodes))
 | 
			
		||||
            system_user.assets.add(*tuple(assets))
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@ from collections import defaultdict
 | 
			
		|||
from functools import reduce
 | 
			
		||||
 | 
			
		||||
from django.db.models import Q
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
 | 
			
		||||
from orgs.utils import set_to_root_org
 | 
			
		||||
from common.utils import get_logger, timeit
 | 
			
		||||
| 
						 | 
				
			
			@ -11,6 +12,7 @@ from common.tree import TreeNode
 | 
			
		|||
from assets.utils import TreeService
 | 
			
		||||
from ..models import AssetPermission
 | 
			
		||||
from ..hands import Node, Asset, SystemUser
 | 
			
		||||
from .. import const
 | 
			
		||||
 | 
			
		||||
logger = get_logger(__file__)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -129,6 +131,9 @@ class AssetPermissionUtilV2:
 | 
			
		|||
 | 
			
		||||
    @timeit
 | 
			
		||||
    def add_direct_nodes_to_user_tree(self, user_tree):
 | 
			
		||||
        """
 | 
			
		||||
        将授权规则的节点放到用户树上, 从full tree中粘贴子树
 | 
			
		||||
        """
 | 
			
		||||
        nodes_direct_keys = self.permissions \
 | 
			
		||||
            .exclude(nodes__isnull=True) \
 | 
			
		||||
            .values_list('nodes__key', flat=True) \
 | 
			
		||||
| 
						 | 
				
			
			@ -153,6 +158,10 @@ class AssetPermissionUtilV2:
 | 
			
		|||
 | 
			
		||||
    @timeit
 | 
			
		||||
    def add_single_assets_node_to_user_tree(self, user_tree):
 | 
			
		||||
        """
 | 
			
		||||
        将单独授权的资产放到树上,如果设置了单独资产到 未分组中,则放到未分组中
 | 
			
		||||
        如果没有,则查询资产属于的资产组,放到树上
 | 
			
		||||
        """
 | 
			
		||||
        # 添加单独授权资产的节点
 | 
			
		||||
        nodes_single_assets = defaultdict(set)
 | 
			
		||||
        queryset = self.permissions.exclude(assets__isnull=True) \
 | 
			
		||||
| 
						 | 
				
			
			@ -161,13 +170,26 @@ class AssetPermissionUtilV2:
 | 
			
		|||
 | 
			
		||||
        for item in queryset:
 | 
			
		||||
            nodes_single_assets[item[1]].add(item[0])
 | 
			
		||||
        # Todo: 游离资产
 | 
			
		||||
        nodes_single_assets.pop(None, None)
 | 
			
		||||
 | 
			
		||||
        for key in tuple(nodes_single_assets.keys()):
 | 
			
		||||
            if user_tree.contains(key):
 | 
			
		||||
                nodes_single_assets.pop(key)
 | 
			
		||||
 | 
			
		||||
        # 如果要设置到ungroup中
 | 
			
		||||
        if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE:
 | 
			
		||||
            node_key = Node.ungrouped_key
 | 
			
		||||
            node_value = Node.ungrouped_value
 | 
			
		||||
            user_tree.create_node(
 | 
			
		||||
                identifier=node_key, tag=node_value,
 | 
			
		||||
                parent=user_tree.root,
 | 
			
		||||
            )
 | 
			
		||||
            assets = set()
 | 
			
		||||
            for _assets in nodes_single_assets.values():
 | 
			
		||||
                assets.update(set(_assets))
 | 
			
		||||
            user_tree.set_assets(node_key, assets)
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        # 获取单独授权资产,并没有在授权的节点上
 | 
			
		||||
        for key, assets in nodes_single_assets.items():
 | 
			
		||||
            node = self.full_tree.get_node(key, deep=True)
 | 
			
		||||
| 
						 | 
				
			
			@ -180,11 +202,17 @@ class AssetPermissionUtilV2:
 | 
			
		|||
 | 
			
		||||
    @timeit
 | 
			
		||||
    def parse_user_tree_to_full_tree(self, user_tree):
 | 
			
		||||
        """
 | 
			
		||||
        经过前面两个动作,用户授权的节点已放到树上,但是树不是完整的,
 | 
			
		||||
        这里要讲树构造成一个完整的书
 | 
			
		||||
        """
 | 
			
		||||
        # 开始修正user_tree,保证父节点都在树上
 | 
			
		||||
        root_children = user_tree.children('')
 | 
			
		||||
        for child in root_children:
 | 
			
		||||
            if child.identifier.isdigit():
 | 
			
		||||
                continue
 | 
			
		||||
            if child.identifier.startswith('-'):
 | 
			
		||||
                continue
 | 
			
		||||
            ancestors = self.full_tree.ancestors(
 | 
			
		||||
                child.identifier, with_self=False, deep=True
 | 
			
		||||
            )
 | 
			
		||||
| 
						 | 
				
			
			@ -194,6 +222,19 @@ class AssetPermissionUtilV2:
 | 
			
		|||
            user_tree.safe_add_ancestors(ancestors)
 | 
			
		||||
            user_tree.move_node(child.identifier, parent_id)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def add_empty_node_if_need(user_tree):
 | 
			
		||||
        """
 | 
			
		||||
        添加空节点,如果根节点没有子节点的话
 | 
			
		||||
        """
 | 
			
		||||
        if not user_tree.children(user_tree.root):
 | 
			
		||||
            node_key = Node.empty_key
 | 
			
		||||
            node_value = Node.empty_value
 | 
			
		||||
            user_tree.create_node(
 | 
			
		||||
                identifier=node_key, tag=node_value,
 | 
			
		||||
                parent=user_tree.root,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    @timeit
 | 
			
		||||
    def get_user_tree(self):
 | 
			
		||||
        if self._user_tree:
 | 
			
		||||
| 
						 | 
				
			
			@ -207,6 +248,7 @@ class AssetPermissionUtilV2:
 | 
			
		|||
        self.add_direct_nodes_to_user_tree(user_tree)
 | 
			
		||||
        self.add_single_assets_node_to_user_tree(user_tree)
 | 
			
		||||
        self.parse_user_tree_to_full_tree(user_tree)
 | 
			
		||||
        self.add_empty_node_if_need(user_tree)
 | 
			
		||||
        self._user_tree = user_tree
 | 
			
		||||
        return user_tree
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -74,6 +74,14 @@ def construct_remote_apps_tree_root():
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
def parse_remote_app_to_tree_node(parent, remote_app):
 | 
			
		||||
    system_user = remote_app.system_user
 | 
			
		||||
    user = {
 | 
			
		||||
        'id': system_user.id,
 | 
			
		||||
        'name': system_user.name,
 | 
			
		||||
        'username': system_user.username,
 | 
			
		||||
        'protocol': system_user.protocol,
 | 
			
		||||
        'login_mode': system_user.login_mode,
 | 
			
		||||
    }
 | 
			
		||||
    tree_node = {
 | 
			
		||||
        'id': remote_app.id,
 | 
			
		||||
        'name': remote_app.name,
 | 
			
		||||
| 
						 | 
				
			
			@ -82,6 +90,6 @@ def parse_remote_app_to_tree_node(parent, remote_app):
 | 
			
		|||
        'open': False,
 | 
			
		||||
        'isParent': False,
 | 
			
		||||
        'iconSkin': 'file',
 | 
			
		||||
        'meta': {'type': 'remote_app'}
 | 
			
		||||
        'meta': {'type': 'remote_app', 'user': user}
 | 
			
		||||
    }
 | 
			
		||||
    return TreeNode(**tree_node)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@
 | 
			
		|||
       <div class="ibox-content mailbox-content" style="padding-top: 0">
 | 
			
		||||
           <div class="file-manager ">
 | 
			
		||||
               <div id="assetTree" class="ztree">
 | 
			
		||||
                   {% trans 'Loading' %} ...
 | 
			
		||||
               </div>
 | 
			
		||||
               <div class="clearfix"></div>
 | 
			
		||||
           </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue