diff --git a/apps/assets/migrations/0059_auto_20201027_1905.py b/apps/assets/migrations/0059_auto_20201027_1905.py index 3b5641eb5..a01120fd7 100644 --- a/apps/assets/migrations/0059_auto_20201027_1905.py +++ b/apps/assets/migrations/0059_auto_20201027_1905.py @@ -20,4 +20,9 @@ class Migration(migrations.Migration): name='ad_domain', field=models.CharField(default='', max_length=256), ), + migrations.AlterField( + model_name='gateway', + name='ip', + field=models.CharField(db_index=True, max_length=128, verbose_name='IP'), + ), ] diff --git a/apps/assets/migrations/0060_auto_20201028_1652.py b/apps/assets/migrations/0060_auto_20201028_1652.py deleted file mode 100644 index d405ad803..000000000 --- a/apps/assets/migrations/0060_auto_20201028_1652.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.13 on 2020-10-28 08:52 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0059_auto_20201027_1905'), - ] - - operations = [ - migrations.AlterField( - model_name='gateway', - name='ip', - field=models.CharField(db_index=True, max_length=128, verbose_name='IP'), - ), - ] diff --git a/apps/assets/migrations/0060_node_full_value.py b/apps/assets/migrations/0060_node_full_value.py new file mode 100644 index 000000000..30eba691a --- /dev/null +++ b/apps/assets/migrations/0060_node_full_value.py @@ -0,0 +1,58 @@ +# Generated by Django 2.2.13 on 2020-10-26 11:31 + +from django.db import migrations, models + + +def get_node_ancestor_keys(key, with_self=False): + parent_keys = [] + key_list = key.split(":") + if not with_self: + key_list.pop() + for i in range(len(key_list)): + parent_keys.append(":".join(key_list)) + key_list.pop() + return parent_keys + + +def migrate_nodes_value_with_slash(apps, schema_editor): + model = apps.get_model("assets", "Node") + db_alias = schema_editor.connection.alias + nodes = model.objects.using(db_alias).filter(value__contains='/') + print('') + print("- Start migrate node value if has /") + for i, node in enumerate(list(nodes)): + new_value = node.value.replace('/', '|') + print("{} start migrate node value: {} => {}".format(i, node.value, new_value)) + node.value = new_value + node.save() + + +def migrate_nodes_full_value(apps, schema_editor): + model = apps.get_model("assets", "Node") + db_alias = schema_editor.connection.alias + nodes = model.objects.using(db_alias).all() + print("- Start migrate node full value") + for i, node in enumerate(list(nodes)): + print("{} start migrate {} node full value".format(i, node.value)) + ancestor_keys = get_node_ancestor_keys(node.key, True) + values = model.objects.filter(key__in=ancestor_keys).values_list('key', 'value') + values = [v for k, v in sorted(values, key=lambda x: len(x[0]))] + node.full_value = '/'.join(values) + node.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0059_auto_20201027_1905'), + ] + + operations = [ + migrations.AddField( + model_name='node', + name='full_value', + field=models.CharField(default='', max_length=4096, verbose_name='Full value'), + ), + migrations.RunPython(migrate_nodes_value_with_slash), + migrations.RunPython(migrate_nodes_full_value) + ] diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 61412a922..bedcb917f 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -313,6 +313,12 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin): } return info + def nodes_display(self): + names = [] + for n in self.nodes.all(): + names.append(self.full_value) + return names + def as_node(self): from .node import Node fake_node = Node() diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py index 7c1c81cde..71b007a09 100644 --- a/apps/assets/models/node.py +++ b/apps/assets/models/node.py @@ -13,7 +13,7 @@ from django.db.transaction import atomic from common.utils import get_logger from common.utils.common import lazyproperty from orgs.mixins.models import OrgModelMixin, OrgManager -from orgs.utils import get_current_org, tmp_to_org, current_org +from orgs.utils import get_current_org, tmp_to_org from orgs.models import Organization @@ -205,6 +205,28 @@ class FamilyMixin: sibling = sibling.exclude(key=self.key) return sibling + @classmethod + def create_node_by_full_value(cls, full_value): + if not full_value: + return [] + nodes_family = full_value.split('/') + org_root = cls.org_root() + if nodes_family[0] == org_root.value: + nodes_family = nodes_family[1:] + return cls.create_nodes_recurse(nodes_family, org_root) + + @classmethod + def create_nodes_recurse(cls, values, parent=None): + if not values: + return None + if parent is None: + parent = cls.org_root() + value = values[0] + child, created = parent.get_or_create_child(value=value) + if len(values) == 1: + return child + return cls.create_nodes_recurse(values[1:], child) + def get_family(self): ancestors = self.get_ancestors() children = self.get_all_children() @@ -372,6 +394,7 @@ class Node(OrgModelMixin, SomeNodesMixin, FamilyMixin, 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")) + full_value = models.CharField(max_length=4096, verbose_name=_('Full value'), default='') child_mark = models.IntegerField(default=0) date_create = models.DateTimeField(auto_now_add=True) parent_key = models.CharField(max_length=64, verbose_name=_("Parent key"), @@ -412,14 +435,14 @@ class Node(OrgModelMixin, SomeNodesMixin, FamilyMixin, NodeAssetsMixin): return self.value @lazyproperty - def full_value(self): + def computed_full_value(self): # 不要在列表中调用该属性 values = self.__class__.objects.filter( key__in=self.get_ancestor_keys() ).values_list('key', 'value') values = [v for k, v in sorted(values, key=lambda x: len(x[0]))] values.append(self.value) - return ' / '.join(values) + return '/'.join(values) @property def level(self): diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py index 75fc220e0..5cf0184c8 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset.py @@ -67,8 +67,9 @@ class AssetSerializer(BulkOrgResourceModelSerializer): slug_field='name', queryset=Platform.objects.all(), label=_("Platform") ) protocols = ProtocolsField(label=_('Protocols'), required=False) - domain_display = serializers.ReadOnlyField(source='domain.name') - admin_user_display = serializers.ReadOnlyField(source='admin_user.name') + domain_display = serializers.ReadOnlyField(source='domain.name', label=_('Domain name')) + admin_user_display = serializers.ReadOnlyField(source='admin_user.name', label=_('Admin user name')) + nodes_display = serializers.ListField(child=serializers.CharField(), label=_('Nodes name'), required=False) """ 资产的数据结构 @@ -90,7 +91,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer): 'platform': ['name'] } fields_m2m = [ - 'nodes', 'labels', + 'nodes', 'nodes_display', 'labels', ] annotates_fields = { # 'admin_user_display': 'admin_user__name' @@ -133,14 +134,32 @@ class AssetSerializer(BulkOrgResourceModelSerializer): if protocols_data: validated_data["protocols"] = ' '.join(protocols_data) + def perform_nodes_display_create(self, instance, nodes_display): + if not nodes_display: + return + nodes_to_set = [] + for full_value in nodes_display: + node = Node.objects.filter(full_value=full_value).first() + if node: + nodes_to_set.append(node) + else: + node = Node.create_node_by_full_value(full_value) + nodes_to_set.append(node) + instance.nodes.set(nodes_to_set) + def create(self, validated_data): self.compatible_with_old_protocol(validated_data) + nodes_display = validated_data.pop('nodes_display') instance = super().create(validated_data) + self.perform_nodes_display_create(instance, nodes_display) return instance def update(self, instance, validated_data): + nodes_display = validated_data.pop('nodes_display') self.compatible_with_old_protocol(validated_data) - return super().update(instance, validated_data) + instance = super().update(instance, validated_data) + self.perform_nodes_display_create(instance, nodes_display) + return instance class AssetDisplaySerializer(AssetSerializer): diff --git a/apps/assets/serializers/node.py b/apps/assets/serializers/node.py index 063c91e0f..fd04486fe 100644 --- a/apps/assets/serializers/node.py +++ b/apps/assets/serializers/node.py @@ -25,6 +25,9 @@ class NodeSerializer(BulkOrgResourceModelSerializer): read_only_fields = ['key', 'org_id'] def validate_value(self, data): + if '/' in data: + error = _("Can't contains: " + "/") + raise serializers.ValidationError(error) if self.instance: instance = self.instance siblings = instance.get_siblings() diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 90af880a6..b3a4addee 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-10-27 20:00+0800\n" +"POT-Creation-Date: 2020-10-27 10:29+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -114,7 +114,7 @@ msgstr "集群" msgid "KubernetesApp" msgstr "Kubernetes应用" -#: applications/models/remote_app.py:23 assets/models/asset.py:357 +#: applications/models/remote_app.py:23 assets/models/asset.py:364 #: assets/models/authbook.py:26 assets/models/gathered_user.py:14 #: assets/serializers/admin_user.py:32 assets/serializers/asset_user.py:47 #: assets/serializers/asset_user.py:84 assets/serializers/system_user.py:46 @@ -563,7 +563,7 @@ msgstr "默认资产组" msgid "User" msgstr "用户" -#: assets/models/label.py:19 assets/models/node.py:374 settings/models.py:28 +#: assets/models/label.py:19 assets/models/node.py:397 settings/models.py:28 msgid "Value" msgstr "值" @@ -575,19 +575,23 @@ msgstr "分类" msgid "New node" msgstr "新节点" -#: assets/models/node.py:280 users/templates/users/_granted_assets.html:130 +#: assets/models/node.py:303 users/templates/users/_granted_assets.html:130 msgid "empty" msgstr "空" -#: assets/models/node.py:373 perms/models/asset_permission.py:144 +#: assets/models/node.py:396 perms/models/asset_permission.py:144 msgid "Key" msgstr "键" -#: assets/models/node.py:377 perms/models/asset_permission.py:148 +#: assets/models/node.py:398 +msgid "Full value" +msgstr "全称" + +#: assets/models/node.py:401 perms/models/asset_permission.py:148 msgid "Parent key" msgstr "ssh私钥" -#: assets/models/node.py:386 assets/serializers/system_user.py:45 +#: assets/models/node.py:410 assets/serializers/system_user.py:45 #: assets/serializers/system_user.py:185 perms/forms/asset_permission.py:92 #: perms/forms/asset_permission.py:99 #: users/templates/users/user_asset_permission.html:41 @@ -701,15 +705,27 @@ msgstr "协议格式 {}/{}" msgid "Protocol duplicate: {}" msgstr "协议重复: {}" -#: assets/serializers/asset.py:110 +#: assets/serializers/asset.py:70 +msgid "Domain name" +msgstr "网域名称" + +#: assets/serializers/asset.py:71 +msgid "Admin user name" +msgstr "管理用户名称" + +#: assets/serializers/asset.py:72 +msgid "Nodes name" +msgstr "节点名称" + +#: assets/serializers/asset.py:111 msgid "Hardware info" msgstr "硬件信息" -#: assets/serializers/asset.py:111 orgs/mixins/serializers.py:26 +#: assets/serializers/asset.py:112 orgs/mixins/serializers.py:26 msgid "Org name" msgstr "组织名称" -#: assets/serializers/asset.py:147 assets/serializers/asset.py:178 +#: assets/serializers/asset.py:168 assets/serializers/asset.py:199 msgid "Connectivity" msgstr "连接"