mirror of https://github.com/jumpserver/jumpserver
perf: 打算重构 asset application
parent
54d1996507
commit
3de881fa19
|
@ -5,6 +5,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from common.utils import lazyproperty
|
from common.utils import lazyproperty
|
||||||
from assets.models.base import BaseUser
|
from assets.models.base import BaseUser
|
||||||
|
from assets.models import SystemUser
|
||||||
|
|
||||||
|
|
||||||
class Account(BaseUser):
|
class Account(BaseUser):
|
||||||
|
@ -108,3 +109,9 @@ class Account(BaseUser):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.smart_name
|
return self.smart_name
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationUser(SystemUser):
|
||||||
|
class Meta:
|
||||||
|
proxy = True
|
||||||
|
verbose_name = _('Application user')
|
||||||
|
|
|
@ -1,216 +1,13 @@
|
||||||
from collections import defaultdict
|
|
||||||
from urllib.parse import urlencode, parse_qsl
|
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from orgs.mixins.models import OrgModelMixin
|
from orgs.mixins.models import OrgModelMixin
|
||||||
from common.mixins import CommonModelMixin
|
from common.mixins import CommonModelMixin
|
||||||
from common.tree import TreeNode
|
|
||||||
from common.utils import is_uuid
|
from common.utils import is_uuid
|
||||||
from assets.models import Asset, SystemUser
|
from assets.models import Asset
|
||||||
|
|
||||||
from ..utils import KubernetesTree
|
|
||||||
from .. import const
|
from .. import const
|
||||||
|
from .tree import ApplicationTreeNodeMixin
|
||||||
|
|
||||||
class ApplicationTreeNodeMixin:
|
|
||||||
id: str
|
|
||||||
name: str
|
|
||||||
type: str
|
|
||||||
category: str
|
|
||||||
attrs: dict
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_tree_id(pid, type, v):
|
|
||||||
i = dict(parse_qsl(pid))
|
|
||||||
i[type] = v
|
|
||||||
tree_id = urlencode(i)
|
|
||||||
return tree_id
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create_choice_node(cls, c, id_, pid, tp, opened=False, counts=None,
|
|
||||||
show_empty=True, show_count=True):
|
|
||||||
count = counts.get(c.value, 0)
|
|
||||||
if count == 0 and not show_empty:
|
|
||||||
return None
|
|
||||||
label = c.label
|
|
||||||
if count is not None and show_count:
|
|
||||||
label = '{} ({})'.format(label, count)
|
|
||||||
data = {
|
|
||||||
'id': id_,
|
|
||||||
'name': label,
|
|
||||||
'title': label,
|
|
||||||
'pId': pid,
|
|
||||||
'isParent': bool(count),
|
|
||||||
'open': opened,
|
|
||||||
'iconSkin': '',
|
|
||||||
'meta': {
|
|
||||||
'type': tp,
|
|
||||||
'data': {
|
|
||||||
'name': c.name,
|
|
||||||
'value': c.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return TreeNode(**data)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create_root_tree_node(cls, queryset, show_count=True):
|
|
||||||
count = queryset.count() if show_count else None
|
|
||||||
root_id = 'applications'
|
|
||||||
root_name = _('Applications')
|
|
||||||
if count is not None and show_count:
|
|
||||||
root_name = '{} ({})'.format(root_name, count)
|
|
||||||
node = TreeNode(**{
|
|
||||||
'id': root_id,
|
|
||||||
'name': root_name,
|
|
||||||
'title': root_name,
|
|
||||||
'pId': '',
|
|
||||||
'isParent': True,
|
|
||||||
'open': True,
|
|
||||||
'iconSkin': '',
|
|
||||||
'meta': {
|
|
||||||
'type': 'applications_root',
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return node
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create_category_tree_nodes(cls, pid, counts=None, show_empty=True, show_count=True):
|
|
||||||
nodes = []
|
|
||||||
categories = const.AppType.category_types_mapper().keys()
|
|
||||||
for category in categories:
|
|
||||||
if not settings.XPACK_ENABLED and const.AppCategory.is_xpack(category):
|
|
||||||
continue
|
|
||||||
i = cls.create_tree_id(pid, 'category', category.value)
|
|
||||||
node = cls.create_choice_node(
|
|
||||||
category, i, pid=pid, tp='category',
|
|
||||||
counts=counts, opened=False, show_empty=show_empty,
|
|
||||||
show_count=show_count
|
|
||||||
)
|
|
||||||
if not node:
|
|
||||||
continue
|
|
||||||
nodes.append(node)
|
|
||||||
return nodes
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create_types_tree_nodes(cls, pid, counts, show_empty=True, show_count=True):
|
|
||||||
nodes = []
|
|
||||||
temp_pid = pid
|
|
||||||
type_category_mapper = const.AppType.type_category_mapper()
|
|
||||||
types = const.AppType.type_category_mapper().keys()
|
|
||||||
|
|
||||||
for tp in types:
|
|
||||||
if not settings.XPACK_ENABLED and const.AppType.is_xpack(tp):
|
|
||||||
continue
|
|
||||||
category = type_category_mapper.get(tp)
|
|
||||||
pid = cls.create_tree_id(pid, 'category', category.value)
|
|
||||||
i = cls.create_tree_id(pid, 'type', tp.value)
|
|
||||||
node = cls.create_choice_node(
|
|
||||||
tp, i, pid, tp='type', counts=counts, opened=False,
|
|
||||||
show_empty=show_empty, show_count=show_count
|
|
||||||
)
|
|
||||||
pid = temp_pid
|
|
||||||
if not node:
|
|
||||||
continue
|
|
||||||
nodes.append(node)
|
|
||||||
return nodes
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_tree_node_counts(queryset):
|
|
||||||
counts = defaultdict(int)
|
|
||||||
values = queryset.values_list('type', 'category')
|
|
||||||
for i in values:
|
|
||||||
tp = i[0]
|
|
||||||
category = i[1]
|
|
||||||
counts[tp] += 1
|
|
||||||
counts[category] += 1
|
|
||||||
return counts
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create_category_type_tree_nodes(cls, queryset, pid, show_empty=True, show_count=True):
|
|
||||||
counts = cls.get_tree_node_counts(queryset)
|
|
||||||
tree_nodes = []
|
|
||||||
|
|
||||||
# 类别的节点
|
|
||||||
tree_nodes += cls.create_category_tree_nodes(
|
|
||||||
pid, counts, show_empty=show_empty,
|
|
||||||
show_count=show_count
|
|
||||||
)
|
|
||||||
|
|
||||||
# 类型的节点
|
|
||||||
tree_nodes += cls.create_types_tree_nodes(
|
|
||||||
pid, counts, show_empty=show_empty,
|
|
||||||
show_count=show_count
|
|
||||||
)
|
|
||||||
return tree_nodes
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create_tree_nodes(cls, queryset, root_node=None, show_empty=True, show_count=True):
|
|
||||||
tree_nodes = []
|
|
||||||
|
|
||||||
# 根节点有可能是组织名称
|
|
||||||
if root_node is None:
|
|
||||||
root_node = cls.create_root_tree_node(queryset, show_count=show_count)
|
|
||||||
tree_nodes.append(root_node)
|
|
||||||
|
|
||||||
tree_nodes += cls.create_category_type_tree_nodes(
|
|
||||||
queryset, root_node.id, show_empty=show_empty, show_count=show_count
|
|
||||||
)
|
|
||||||
|
|
||||||
# 应用的节点
|
|
||||||
for app in queryset:
|
|
||||||
if not settings.XPACK_ENABLED and const.AppType.is_xpack(app.type):
|
|
||||||
continue
|
|
||||||
node = app.as_tree_node(root_node.id)
|
|
||||||
tree_nodes.append(node)
|
|
||||||
return tree_nodes
|
|
||||||
|
|
||||||
def create_app_tree_pid(self, root_id):
|
|
||||||
pid = self.create_tree_id(root_id, 'category', self.category)
|
|
||||||
pid = self.create_tree_id(pid, 'type', self.type)
|
|
||||||
return pid
|
|
||||||
|
|
||||||
def as_tree_node(self, pid, k8s_as_tree=False):
|
|
||||||
if self.type == const.AppType.k8s and k8s_as_tree:
|
|
||||||
node = KubernetesTree(pid).as_tree_node(self)
|
|
||||||
else:
|
|
||||||
node = self._as_tree_node(pid)
|
|
||||||
return node
|
|
||||||
|
|
||||||
def _attrs_to_tree(self):
|
|
||||||
if self.category == const.AppCategory.db:
|
|
||||||
return self.attrs
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def _as_tree_node(self, pid):
|
|
||||||
icon_skin_category_mapper = {
|
|
||||||
'remote_app': 'chrome',
|
|
||||||
'db': 'database',
|
|
||||||
'cloud': 'cloud'
|
|
||||||
}
|
|
||||||
icon_skin = icon_skin_category_mapper.get(self.category, 'file')
|
|
||||||
pid = self.create_app_tree_pid(pid)
|
|
||||||
node = TreeNode(**{
|
|
||||||
'id': str(self.id),
|
|
||||||
'name': self.name,
|
|
||||||
'title': self.name,
|
|
||||||
'pId': pid,
|
|
||||||
'isParent': False,
|
|
||||||
'open': False,
|
|
||||||
'iconSkin': icon_skin,
|
|
||||||
'meta': {
|
|
||||||
'type': 'application',
|
|
||||||
'data': {
|
|
||||||
'category': self.category,
|
|
||||||
'type': self.type,
|
|
||||||
'attrs': self._attrs_to_tree()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return node
|
|
||||||
|
|
||||||
|
|
||||||
class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin):
|
class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin):
|
||||||
|
@ -279,8 +76,3 @@ class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin):
|
||||||
if raise_exception:
|
if raise_exception:
|
||||||
raise ValueError("Remote App not has asset attr")
|
raise ValueError("Remote App not has asset attr")
|
||||||
|
|
||||||
|
|
||||||
class ApplicationUser(SystemUser):
|
|
||||||
class Meta:
|
|
||||||
proxy = True
|
|
||||||
verbose_name = _('Application user')
|
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from .application import Application
|
||||||
|
|
||||||
|
|
||||||
|
class Database(Application):
|
||||||
|
host = models.CharField(max_length=1024, verbose_name=_('Host'))
|
||||||
|
port = models.IntegerField(verbose_name=_("Port"))
|
||||||
|
database = models.CharField(max_length=1024, blank=True, null=True, verbose_name=_("Database"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Database")
|
|
@ -0,0 +1,208 @@
|
||||||
|
from collections import defaultdict
|
||||||
|
from urllib.parse import urlencode, parse_qsl
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from common.tree import TreeNode
|
||||||
|
from ..utils import KubernetesTree
|
||||||
|
from .. import const
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationTreeNodeMixin:
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
type: str
|
||||||
|
category: str
|
||||||
|
attrs: dict
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_tree_id(pid, type, v):
|
||||||
|
i = dict(parse_qsl(pid))
|
||||||
|
i[type] = v
|
||||||
|
tree_id = urlencode(i)
|
||||||
|
return tree_id
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_choice_node(cls, c, id_, pid, tp, opened=False, counts=None,
|
||||||
|
show_empty=True, show_count=True):
|
||||||
|
count = counts.get(c.value, 0)
|
||||||
|
if count == 0 and not show_empty:
|
||||||
|
return None
|
||||||
|
label = c.label
|
||||||
|
if count is not None and show_count:
|
||||||
|
label = '{} ({})'.format(label, count)
|
||||||
|
data = {
|
||||||
|
'id': id_,
|
||||||
|
'name': label,
|
||||||
|
'title': label,
|
||||||
|
'pId': pid,
|
||||||
|
'isParent': bool(count),
|
||||||
|
'open': opened,
|
||||||
|
'iconSkin': '',
|
||||||
|
'meta': {
|
||||||
|
'type': tp,
|
||||||
|
'data': {
|
||||||
|
'name': c.name,
|
||||||
|
'value': c.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TreeNode(**data)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_root_tree_node(cls, queryset, show_count=True):
|
||||||
|
count = queryset.count() if show_count else None
|
||||||
|
root_id = 'applications'
|
||||||
|
root_name = _('Applications')
|
||||||
|
if count is not None and show_count:
|
||||||
|
root_name = '{} ({})'.format(root_name, count)
|
||||||
|
node = TreeNode(**{
|
||||||
|
'id': root_id,
|
||||||
|
'name': root_name,
|
||||||
|
'title': root_name,
|
||||||
|
'pId': '',
|
||||||
|
'isParent': True,
|
||||||
|
'open': True,
|
||||||
|
'iconSkin': '',
|
||||||
|
'meta': {
|
||||||
|
'type': 'applications_root',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return node
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_category_tree_nodes(cls, pid, counts=None, show_empty=True, show_count=True):
|
||||||
|
nodes = []
|
||||||
|
categories = const.AppType.category_types_mapper().keys()
|
||||||
|
for category in categories:
|
||||||
|
if not settings.XPACK_ENABLED and const.AppCategory.is_xpack(category):
|
||||||
|
continue
|
||||||
|
i = cls.create_tree_id(pid, 'category', category.value)
|
||||||
|
node = cls.create_choice_node(
|
||||||
|
category, i, pid=pid, tp='category',
|
||||||
|
counts=counts, opened=False, show_empty=show_empty,
|
||||||
|
show_count=show_count
|
||||||
|
)
|
||||||
|
if not node:
|
||||||
|
continue
|
||||||
|
nodes.append(node)
|
||||||
|
return nodes
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_types_tree_nodes(cls, pid, counts, show_empty=True, show_count=True):
|
||||||
|
nodes = []
|
||||||
|
temp_pid = pid
|
||||||
|
type_category_mapper = const.AppType.type_category_mapper()
|
||||||
|
types = const.AppType.type_category_mapper().keys()
|
||||||
|
|
||||||
|
for tp in types:
|
||||||
|
if not settings.XPACK_ENABLED and const.AppType.is_xpack(tp):
|
||||||
|
continue
|
||||||
|
category = type_category_mapper.get(tp)
|
||||||
|
pid = cls.create_tree_id(pid, 'category', category.value)
|
||||||
|
i = cls.create_tree_id(pid, 'type', tp.value)
|
||||||
|
node = cls.create_choice_node(
|
||||||
|
tp, i, pid, tp='type', counts=counts, opened=False,
|
||||||
|
show_empty=show_empty, show_count=show_count
|
||||||
|
)
|
||||||
|
pid = temp_pid
|
||||||
|
if not node:
|
||||||
|
continue
|
||||||
|
nodes.append(node)
|
||||||
|
return nodes
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_tree_node_counts(queryset):
|
||||||
|
counts = defaultdict(int)
|
||||||
|
values = queryset.values_list('type', 'category')
|
||||||
|
for i in values:
|
||||||
|
tp = i[0]
|
||||||
|
category = i[1]
|
||||||
|
counts[tp] += 1
|
||||||
|
counts[category] += 1
|
||||||
|
return counts
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_category_type_tree_nodes(cls, queryset, pid, show_empty=True, show_count=True):
|
||||||
|
counts = cls.get_tree_node_counts(queryset)
|
||||||
|
tree_nodes = []
|
||||||
|
|
||||||
|
# 类别的节点
|
||||||
|
tree_nodes += cls.create_category_tree_nodes(
|
||||||
|
pid, counts, show_empty=show_empty,
|
||||||
|
show_count=show_count
|
||||||
|
)
|
||||||
|
|
||||||
|
# 类型的节点
|
||||||
|
tree_nodes += cls.create_types_tree_nodes(
|
||||||
|
pid, counts, show_empty=show_empty,
|
||||||
|
show_count=show_count
|
||||||
|
)
|
||||||
|
return tree_nodes
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_tree_nodes(cls, queryset, root_node=None, show_empty=True, show_count=True):
|
||||||
|
tree_nodes = []
|
||||||
|
|
||||||
|
# 根节点有可能是组织名称
|
||||||
|
if root_node is None:
|
||||||
|
root_node = cls.create_root_tree_node(queryset, show_count=show_count)
|
||||||
|
tree_nodes.append(root_node)
|
||||||
|
|
||||||
|
tree_nodes += cls.create_category_type_tree_nodes(
|
||||||
|
queryset, root_node.id, show_empty=show_empty, show_count=show_count
|
||||||
|
)
|
||||||
|
|
||||||
|
# 应用的节点
|
||||||
|
for app in queryset:
|
||||||
|
if not settings.XPACK_ENABLED and const.AppType.is_xpack(app.type):
|
||||||
|
continue
|
||||||
|
node = app.as_tree_node(root_node.id)
|
||||||
|
tree_nodes.append(node)
|
||||||
|
return tree_nodes
|
||||||
|
|
||||||
|
def create_app_tree_pid(self, root_id):
|
||||||
|
pid = self.create_tree_id(root_id, 'category', self.category)
|
||||||
|
pid = self.create_tree_id(pid, 'type', self.type)
|
||||||
|
return pid
|
||||||
|
|
||||||
|
def as_tree_node(self, pid, k8s_as_tree=False):
|
||||||
|
if self.type == const.AppType.k8s and k8s_as_tree:
|
||||||
|
node = KubernetesTree(pid).as_tree_node(self)
|
||||||
|
else:
|
||||||
|
node = self._as_tree_node(pid)
|
||||||
|
return node
|
||||||
|
|
||||||
|
def _attrs_to_tree(self):
|
||||||
|
if self.category == const.AppCategory.db:
|
||||||
|
return self.attrs
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _as_tree_node(self, pid):
|
||||||
|
icon_skin_category_mapper = {
|
||||||
|
'remote_app': 'chrome',
|
||||||
|
'db': 'database',
|
||||||
|
'cloud': 'cloud'
|
||||||
|
}
|
||||||
|
icon_skin = icon_skin_category_mapper.get(self.category, 'file')
|
||||||
|
pid = self.create_app_tree_pid(pid)
|
||||||
|
node = TreeNode(**{
|
||||||
|
'id': str(self.id),
|
||||||
|
'name': self.name,
|
||||||
|
'title': self.name,
|
||||||
|
'pId': pid,
|
||||||
|
'isParent': False,
|
||||||
|
'open': False,
|
||||||
|
'iconSkin': icon_skin,
|
||||||
|
'meta': {
|
||||||
|
'type': 'application',
|
||||||
|
'data': {
|
||||||
|
'category': self.category,
|
||||||
|
'type': self.type,
|
||||||
|
'attrs': self._attrs_to_tree()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return node
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from .mixin import *
|
from .mixin import *
|
||||||
|
from .platform import *
|
||||||
from .admin_user import *
|
from .admin_user import *
|
||||||
from .asset import *
|
from .asset import *
|
||||||
from .label import *
|
from .label import *
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
from rest_framework.viewsets import ModelViewSet
|
|
||||||
from rest_framework.generics import RetrieveAPIView, ListAPIView
|
from rest_framework.generics import RetrieveAPIView, ListAPIView
|
||||||
|
from rest_framework.decorators import action
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
|
@ -27,8 +27,7 @@ from ..filters import FilterAssetByNodeFilterBackend, LabelFilterBackend, IpInFi
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AssetViewSet', 'AssetPlatformRetrieveApi',
|
'AssetViewSet', 'AssetPlatformRetrieveApi',
|
||||||
'AssetGatewayListApi', 'AssetPlatformViewSet',
|
'AssetGatewayListApi', 'AssetTaskCreateApi', 'AssetsTaskCreateApi',
|
||||||
'AssetTaskCreateApi', 'AssetsTaskCreateApi',
|
|
||||||
'AssetPermUserListApi', 'AssetPermUserPermissionsListApi',
|
'AssetPermUserListApi', 'AssetPermUserPermissionsListApi',
|
||||||
'AssetPermUserGroupListApi', 'AssetPermUserGroupPermissionsListApi',
|
'AssetPermUserGroupListApi', 'AssetPermUserGroupPermissionsListApi',
|
||||||
]
|
]
|
||||||
|
@ -52,7 +51,8 @@ class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet)
|
||||||
ordering = ('hostname', )
|
ordering = ('hostname', )
|
||||||
serializer_classes = {
|
serializer_classes = {
|
||||||
'default': serializers.AssetSerializer,
|
'default': serializers.AssetSerializer,
|
||||||
'suggestion': serializers.MiniAssetSerializer
|
'suggestion': serializers.MiniAssetSerializer,
|
||||||
|
'platform': serializers.PlatformSerializer
|
||||||
}
|
}
|
||||||
rbac_perms = {
|
rbac_perms = {
|
||||||
'match': 'assets.match_asset'
|
'match': 'assets.match_asset'
|
||||||
|
@ -74,6 +74,10 @@ class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet)
|
||||||
assets = serializer.save()
|
assets = serializer.save()
|
||||||
self.set_assets_node(assets)
|
self.set_assets_node(assets)
|
||||||
|
|
||||||
|
@action(methods='GET', detail=True, url_path='platform')
|
||||||
|
def platform(self, request, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class AssetPlatformRetrieveApi(RetrieveAPIView):
|
class AssetPlatformRetrieveApi(RetrieveAPIView):
|
||||||
queryset = Platform.objects.all()
|
queryset = Platform.objects.all()
|
||||||
|
@ -88,20 +92,6 @@ class AssetPlatformRetrieveApi(RetrieveAPIView):
|
||||||
return asset.platform
|
return asset.platform
|
||||||
|
|
||||||
|
|
||||||
class AssetPlatformViewSet(ModelViewSet):
|
|
||||||
queryset = Platform.objects.all()
|
|
||||||
serializer_class = serializers.PlatformSerializer
|
|
||||||
filterset_fields = ['name', 'base']
|
|
||||||
search_fields = ['name']
|
|
||||||
|
|
||||||
def check_object_permissions(self, request, obj):
|
|
||||||
if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal:
|
|
||||||
self.permission_denied(
|
|
||||||
request, message={"detail": "Internal platform"}
|
|
||||||
)
|
|
||||||
return super().check_object_permissions(request, obj)
|
|
||||||
|
|
||||||
|
|
||||||
class AssetsTaskMixin:
|
class AssetsTaskMixin:
|
||||||
|
|
||||||
def perform_assets_task(self, serializer):
|
def perform_assets_task(self, serializer):
|
||||||
|
@ -246,7 +236,7 @@ class AssetPermUserGroupListApi(BaseAssetPermUserOrUserGroupListApi):
|
||||||
return user_groups
|
return user_groups
|
||||||
|
|
||||||
|
|
||||||
class BaseAssetPermUserOrUserGroupPermissionsListApiMixin(generics.ListAPIView):
|
class BasePermedAssetListApi(generics.ListAPIView):
|
||||||
model = AssetPermission
|
model = AssetPermission
|
||||||
serializer_class = AssetPermissionSerializer
|
serializer_class = AssetPermissionSerializer
|
||||||
filterset_class = AssetPermissionFilter
|
filterset_class = AssetPermissionFilter
|
||||||
|
@ -272,7 +262,7 @@ class BaseAssetPermUserOrUserGroupPermissionsListApiMixin(generics.ListAPIView):
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class AssetPermUserPermissionsListApi(BaseAssetPermUserOrUserGroupPermissionsListApiMixin):
|
class AssetPermUserPermissionsListApi(BasePermedAssetListApi):
|
||||||
def filter_queryset(self, queryset):
|
def filter_queryset(self, queryset):
|
||||||
queryset = super().filter_queryset(queryset)
|
queryset = super().filter_queryset(queryset)
|
||||||
queryset = self.filter_user_related(queryset)
|
queryset = self.filter_user_related(queryset)
|
||||||
|
@ -291,7 +281,7 @@ class AssetPermUserPermissionsListApi(BaseAssetPermUserOrUserGroupPermissionsLis
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
class AssetPermUserGroupPermissionsListApi(BaseAssetPermUserOrUserGroupPermissionsListApiMixin):
|
class AssetPermUserGroupPermissionsListApi(BasePermedAssetListApi):
|
||||||
def filter_queryset(self, queryset):
|
def filter_queryset(self, queryset):
|
||||||
queryset = super().filter_queryset(queryset)
|
queryset = super().filter_queryset(queryset)
|
||||||
queryset = self.filter_user_group_related(queryset)
|
queryset = self.filter_user_group_related(queryset)
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
|
from assets.models import Platform
|
||||||
|
from assets.serializers import PlatformSerializer
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['AssetPlatformViewSet']
|
||||||
|
|
||||||
|
|
||||||
|
class AssetPlatformViewSet(ModelViewSet):
|
||||||
|
queryset = Platform.objects.all()
|
||||||
|
serializer_class = PlatformSerializer
|
||||||
|
filterset_fields = ['name', 'base']
|
||||||
|
search_fields = ['name']
|
||||||
|
|
||||||
|
def check_object_permissions(self, request, obj):
|
||||||
|
if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal:
|
||||||
|
self.permission_denied(
|
||||||
|
request, message={"detail": "Internal platform"}
|
||||||
|
)
|
||||||
|
return super().check_object_permissions(request, obj)
|
|
@ -17,6 +17,6 @@ class Migration(migrations.Migration):
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='asset',
|
model_name='asset',
|
||||||
name='cluster',
|
name='cluster',
|
||||||
field=models.ForeignKey(default=assets.models.asset.default_cluster, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='assets', to='assets.Cluster', verbose_name='Cluster'),
|
field=models.ForeignKey(default=assets.models.default_cluster, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='assets', to='assets.Cluster', verbose_name='Cluster'),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -50,7 +50,7 @@ class Migration(migrations.Migration):
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='asset',
|
model_name='asset',
|
||||||
name='nodes',
|
name='nodes',
|
||||||
field=models.ManyToManyField(default=assets.models.asset.default_node, related_name='assets', to='assets.Node', verbose_name='Nodes'),
|
field=models.ManyToManyField(default=assets.models.default_node, related_name='assets', to='assets.Node', verbose_name='Nodes'),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='systemuser',
|
model_name='systemuser',
|
||||||
|
|
|
@ -34,7 +34,7 @@ class Migration(migrations.Migration):
|
||||||
model_name='asset',
|
model_name='asset',
|
||||||
name='platform',
|
name='platform',
|
||||||
field=models.ForeignKey(
|
field=models.ForeignKey(
|
||||||
default=assets.models.asset.Platform.default,
|
default=assets.models.Platform.default,
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
related_name='assets', to='assets.Platform',
|
related_name='assets', to='assets.Platform',
|
||||||
verbose_name='Platform'),
|
verbose_name='Platform'),
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 3.1.14 on 2022-03-30 10:35
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0089_auto_20220310_0616'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Host',
|
||||||
|
fields=[
|
||||||
|
('asset_ptr', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='assets.asset')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,40 @@
|
||||||
|
# Generated by Django 3.1.14 on 2022-04-01 07:58
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_to_host(apps, schema_editor):
|
||||||
|
asset_model = apps.get_model("assets", "Asset")
|
||||||
|
host_model = apps.get_model("assets", 'Host')
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
|
||||||
|
created = 0
|
||||||
|
batch_size = 1000
|
||||||
|
|
||||||
|
while True:
|
||||||
|
start = created
|
||||||
|
end = created + batch_size
|
||||||
|
assets = asset_model.objects.using(db_alias).all()[start:end]
|
||||||
|
if not assets:
|
||||||
|
break
|
||||||
|
|
||||||
|
hosts = [host_model(asset_ptr=asset) for asset in assets]
|
||||||
|
host_model.objects.using(db_alias).bulk_create(hosts, ignore_conflicts=True)
|
||||||
|
created += len(hosts)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0090_add_host'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='host',
|
||||||
|
name='asset_ptr',
|
||||||
|
field=models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset'),
|
||||||
|
),
|
||||||
|
migrations.RunPython(migrate_to_host)
|
||||||
|
]
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Generated by Django 3.1.14 on 2022-04-02 09:09
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0091_auto_20220401_1558'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='HostInfo',
|
||||||
|
fields=[
|
||||||
|
('vendor', models.CharField(blank=True, max_length=64, null=True, verbose_name='Vendor')),
|
||||||
|
('model', models.CharField(blank=True, max_length=54, null=True, verbose_name='Model')),
|
||||||
|
('sn', models.CharField(blank=True, max_length=128, null=True, verbose_name='Serial number')),
|
||||||
|
('cpu_model', models.CharField(blank=True, max_length=64, null=True, verbose_name='CPU model')),
|
||||||
|
('cpu_count', models.IntegerField(null=True, verbose_name='CPU count')),
|
||||||
|
('cpu_cores', models.IntegerField(null=True, verbose_name='CPU cores')),
|
||||||
|
('cpu_vcpus', models.IntegerField(null=True, verbose_name='CPU vcpus')),
|
||||||
|
('memory', models.CharField(blank=True, max_length=64, null=True, verbose_name='Memory')),
|
||||||
|
('disk_total', models.CharField(blank=True, max_length=1024, null=True, verbose_name='Disk total')),
|
||||||
|
('disk_info', models.CharField(blank=True, max_length=1024, null=True, verbose_name='Disk info')),
|
||||||
|
('os', models.CharField(blank=True, max_length=128, null=True, verbose_name='OS')),
|
||||||
|
('os_version', models.CharField(blank=True, max_length=16, null=True, verbose_name='OS version')),
|
||||||
|
('os_arch', models.CharField(blank=True, max_length=16, null=True, verbose_name='OS arch')),
|
||||||
|
('hostname_raw', models.CharField(blank=True, max_length=128, null=True, verbose_name='Hostname raw')),
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
|
||||||
|
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||||
|
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||||
|
('host', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='assets.host', related_name='info', verbose_name='Host')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'HostInfo',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,51 @@
|
||||||
|
# Generated by Django 3.1.14 on 2022-04-02 08:27
|
||||||
|
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_hardware(apps, *args):
|
||||||
|
host_model = apps.get_model('assets', 'Host')
|
||||||
|
asset_model = apps.get_model('assets', 'Asset')
|
||||||
|
hardware_model = apps.get_model('assets', 'HostInfo')
|
||||||
|
|
||||||
|
created = 0
|
||||||
|
batch_size = 1000
|
||||||
|
|
||||||
|
excludes = ['id', 'host', 'date_updated']
|
||||||
|
fields = [f.name for f in hardware_model._meta.fields]
|
||||||
|
fields = [name for name in fields if name not in excludes]
|
||||||
|
|
||||||
|
while True:
|
||||||
|
start = created
|
||||||
|
end = created + batch_size
|
||||||
|
hosts = host_model.objects.all()[start:end]
|
||||||
|
asset_ids = [h.asset_ptr_id for h in hosts]
|
||||||
|
assets = asset_model.objects.filter(id__in=asset_ids)
|
||||||
|
asset_mapper = {a.id: a for a in assets}
|
||||||
|
if not hosts:
|
||||||
|
break
|
||||||
|
|
||||||
|
hardware_list = []
|
||||||
|
for host in hosts:
|
||||||
|
hardware = hardware_model()
|
||||||
|
asset = asset_mapper[host.asset_ptr_id]
|
||||||
|
hardware.host = host
|
||||||
|
hardware.date_updated = timezone.now()
|
||||||
|
for name in fields:
|
||||||
|
setattr(hardware, name, getattr(asset, name))
|
||||||
|
hardware_list.append(hardware)
|
||||||
|
|
||||||
|
hardware_model.objects.bulk_create(hardware_list, ignore_conflicts=True)
|
||||||
|
created += len(hardware_list)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0092_hardware'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(migrate_hardware)
|
||||||
|
]
|
|
@ -0,0 +1,69 @@
|
||||||
|
# Generated by Django 3.1.14 on 2022-04-02 09:36
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0093_auto_20220403_1627'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='asset',
|
||||||
|
name='cpu_cores',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='asset',
|
||||||
|
name='cpu_count',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='asset',
|
||||||
|
name='cpu_model',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='asset',
|
||||||
|
name='cpu_vcpus',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='asset',
|
||||||
|
name='disk_info',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='asset',
|
||||||
|
name='disk_total',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='asset',
|
||||||
|
name='hostname_raw',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='asset',
|
||||||
|
name='memory',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='asset',
|
||||||
|
name='model',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='asset',
|
||||||
|
name='os',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='asset',
|
||||||
|
name='os_arch',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='asset',
|
||||||
|
name='os_version',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='asset',
|
||||||
|
name='sn',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='asset',
|
||||||
|
name='vendor',
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,4 +1,5 @@
|
||||||
from .base import *
|
from .base import *
|
||||||
|
from .platform import *
|
||||||
from .asset import *
|
from .asset import *
|
||||||
from .label import Label
|
from .label import Label
|
||||||
from .user import *
|
from .user import *
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
from .common import *
|
||||||
|
from .host import *
|
|
@ -11,18 +11,17 @@ from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
from common.fields.model import JsonDictTextField
|
|
||||||
from common.utils import lazyproperty
|
from common.utils import lazyproperty
|
||||||
from orgs.mixins.models import OrgModelMixin, OrgManager
|
from orgs.mixins.models import OrgModelMixin, OrgManager
|
||||||
|
from ..platform import Platform
|
||||||
|
from ..base import AbsConnectivity
|
||||||
|
|
||||||
from .base import AbsConnectivity
|
__all__ = ['Asset', 'ProtocolsMixin', 'AssetQuerySet', 'default_node', 'default_cluster']
|
||||||
|
|
||||||
__all__ = ['Asset', 'ProtocolsMixin', 'Platform', 'AssetQuerySet']
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def default_cluster():
|
def default_cluster():
|
||||||
from .cluster import Cluster
|
from assets.models import Cluster
|
||||||
name = "Default"
|
name = "Default"
|
||||||
defaults = {"name": name}
|
defaults = {"name": name}
|
||||||
cluster, created = Cluster.objects.get_or_create(
|
cluster, created = Cluster.objects.get_or_create(
|
||||||
|
@ -33,7 +32,7 @@ def default_cluster():
|
||||||
|
|
||||||
def default_node():
|
def default_node():
|
||||||
try:
|
try:
|
||||||
from .node import Node
|
from assets.models import Node
|
||||||
root = Node.org_root()
|
root = Node.org_root()
|
||||||
return Node.objects.filter(id=root.id)
|
return Node.objects.filter(id=root.id)
|
||||||
except:
|
except:
|
||||||
|
@ -106,7 +105,7 @@ class NodesRelationMixin:
|
||||||
_all_nodes_keys = None
|
_all_nodes_keys = None
|
||||||
|
|
||||||
def get_nodes(self):
|
def get_nodes(self):
|
||||||
from .node import Node
|
from assets.models import Node
|
||||||
nodes = self.nodes.all()
|
nodes = self.nodes.all()
|
||||||
if not nodes:
|
if not nodes:
|
||||||
nodes = Node.objects.filter(id=Node.org_root().id)
|
nodes = Node.objects.filter(id=Node.org_root().id)
|
||||||
|
@ -122,104 +121,25 @@ class NodesRelationMixin:
|
||||||
return nodes
|
return nodes
|
||||||
|
|
||||||
|
|
||||||
class Platform(models.Model):
|
class Asset(AbsConnectivity, ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
||||||
CHARSET_CHOICES = (
|
|
||||||
('utf8', 'UTF-8'),
|
|
||||||
('gbk', 'GBK'),
|
|
||||||
)
|
|
||||||
BASE_CHOICES = (
|
|
||||||
('Linux', 'Linux'),
|
|
||||||
('Unix', 'Unix'),
|
|
||||||
('MacOS', 'MacOS'),
|
|
||||||
('BSD', 'BSD'),
|
|
||||||
('Windows', 'Windows'),
|
|
||||||
('Other', 'Other'),
|
|
||||||
)
|
|
||||||
name = models.SlugField(verbose_name=_("Name"), unique=True, allow_unicode=True)
|
|
||||||
base = models.CharField(choices=BASE_CHOICES, max_length=16, default='Linux', verbose_name=_("Base"))
|
|
||||||
charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset"))
|
|
||||||
meta = JsonDictTextField(blank=True, null=True, verbose_name=_("Meta"))
|
|
||||||
internal = models.BooleanField(default=False, verbose_name=_("Internal"))
|
|
||||||
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def default(cls):
|
|
||||||
linux, created = cls.objects.get_or_create(
|
|
||||||
defaults={'name': 'Linux'}, name='Linux'
|
|
||||||
)
|
|
||||||
return linux.id
|
|
||||||
|
|
||||||
def is_windows(self):
|
|
||||||
return self.base.lower() in ('windows',)
|
|
||||||
|
|
||||||
def is_unixlike(self):
|
|
||||||
return self.base.lower() in ("linux", "unix", "macos", "bsd")
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("Platform")
|
|
||||||
# ordering = ('name',)
|
|
||||||
|
|
||||||
|
|
||||||
class AbsHardwareInfo(models.Model):
|
|
||||||
# Collect
|
|
||||||
vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor'))
|
|
||||||
model = models.CharField(max_length=54, null=True, blank=True, verbose_name=_('Model'))
|
|
||||||
sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number'))
|
|
||||||
|
|
||||||
cpu_model = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU model'))
|
|
||||||
cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count'))
|
|
||||||
cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores'))
|
|
||||||
cpu_vcpus = models.IntegerField(null=True, verbose_name=_('CPU vcpus'))
|
|
||||||
memory = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Memory'))
|
|
||||||
disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total'))
|
|
||||||
disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info'))
|
|
||||||
|
|
||||||
os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS'))
|
|
||||||
os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version'))
|
|
||||||
os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch'))
|
|
||||||
hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw'))
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cpu_info(self):
|
|
||||||
info = ""
|
|
||||||
if self.cpu_model:
|
|
||||||
info += self.cpu_model
|
|
||||||
if self.cpu_count and self.cpu_cores:
|
|
||||||
info += "{}*{}".format(self.cpu_count, self.cpu_cores)
|
|
||||||
return info
|
|
||||||
|
|
||||||
@property
|
|
||||||
def hardware_info(self):
|
|
||||||
if self.cpu_count:
|
|
||||||
return '{} Core {} {}'.format(
|
|
||||||
self.cpu_vcpus or self.cpu_count * self.cpu_cores,
|
|
||||||
self.memory, self.disk_total
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return ''
|
|
||||||
|
|
||||||
|
|
||||||
class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)
|
|
||||||
hostname = models.CharField(max_length=128, verbose_name=_('Hostname'))
|
hostname = models.CharField(max_length=128, verbose_name=_('Hostname'))
|
||||||
|
ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)
|
||||||
protocol = models.CharField(max_length=128, default=ProtocolsMixin.Protocol.ssh,
|
protocol = models.CharField(max_length=128, default=ProtocolsMixin.Protocol.ssh,
|
||||||
choices=ProtocolsMixin.Protocol.choices, verbose_name=_('Protocol'))
|
choices=ProtocolsMixin.Protocol.choices, verbose_name=_('Protocol'))
|
||||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||||
protocols = models.CharField(max_length=128, default='ssh/22', blank=True, verbose_name=_("Protocols"))
|
protocols = models.CharField(max_length=128, default='ssh/22', blank=True, verbose_name=_("Protocols"))
|
||||||
platform = models.ForeignKey(Platform, default=Platform.default, on_delete=models.PROTECT, verbose_name=_("Platform"), related_name='assets')
|
platform = models.ForeignKey(Platform, default=Platform.default, on_delete=models.PROTECT,
|
||||||
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL)
|
verbose_name=_("Platform"), related_name='assets')
|
||||||
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
|
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets',
|
||||||
|
verbose_name=_("Domain"), on_delete=models.SET_NULL)
|
||||||
|
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets',
|
||||||
|
verbose_name=_("Nodes"))
|
||||||
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
||||||
|
|
||||||
# Auth
|
# Auth
|
||||||
admin_user = models.ForeignKey('assets.SystemUser', on_delete=models.SET_NULL, null=True, verbose_name=_("Admin user"), related_name='admin_assets')
|
admin_user = models.ForeignKey('assets.SystemUser', on_delete=models.SET_NULL, null=True,
|
||||||
|
verbose_name=_("Admin user"), related_name='admin_assets')
|
||||||
|
|
||||||
# Some information
|
# Some information
|
||||||
public_ip = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Public IP'))
|
public_ip = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Public IP'))
|
||||||
|
@ -236,7 +156,7 @@ class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin
|
||||||
return '{0.hostname}({0.ip})'.format(self)
|
return '{0.hostname}({0.ip})'.format(self)
|
||||||
|
|
||||||
def set_admin_user_relation(self):
|
def set_admin_user_relation(self):
|
||||||
from .authbook import AuthBook
|
from assets.models import AuthBook
|
||||||
if not self.admin_user:
|
if not self.admin_user:
|
||||||
return
|
return
|
||||||
if self.admin_user.type != 'admin':
|
if self.admin_user.type != 'admin':
|
||||||
|
@ -333,7 +253,7 @@ class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin
|
||||||
return names
|
return names
|
||||||
|
|
||||||
def as_node(self):
|
def as_node(self):
|
||||||
from .node import Node
|
from assets.models import Node
|
||||||
fake_node = Node()
|
fake_node = Node()
|
||||||
fake_node.id = self.id
|
fake_node.id = self.id
|
||||||
fake_node.key = self.id
|
fake_node.key = self.id
|
||||||
|
@ -372,7 +292,7 @@ class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin
|
||||||
return tree_node
|
return tree_node
|
||||||
|
|
||||||
def get_all_system_users(self):
|
def get_all_system_users(self):
|
||||||
from .user import SystemUser
|
from assets.models import SystemUser
|
||||||
system_user_ids = SystemUser.assets.through.objects.filter(asset=self)\
|
system_user_ids = SystemUser.assets.through.objects.filter(asset=self)\
|
||||||
.values_list('systemuser_id', flat=True)
|
.values_list('systemuser_id', flat=True)
|
||||||
system_users = SystemUser.objects.filter(id__in=system_user_ids)
|
system_users = SystemUser.objects.filter(id__in=system_user_ids)
|
|
@ -0,0 +1,9 @@
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from .common import Asset
|
||||||
|
|
||||||
|
|
||||||
|
class Database(Asset):
|
||||||
|
database = models.CharField(max_length=1024, verbose_name=_("Database"), blank=True)
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from common.mixins.models import CommonModelMixin
|
||||||
|
from .common import Asset
|
||||||
|
|
||||||
|
|
||||||
|
class Host(Asset):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class HostInfo(CommonModelMixin):
|
||||||
|
host = models.OneToOneField(Host, related_name='info', on_delete=models.CASCADE,
|
||||||
|
verbose_name=_("Host"), unique=True)
|
||||||
|
# Collect
|
||||||
|
vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor'))
|
||||||
|
model = models.CharField(max_length=54, null=True, blank=True, verbose_name=_('Model'))
|
||||||
|
sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number'))
|
||||||
|
|
||||||
|
cpu_model = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU model'))
|
||||||
|
cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count'))
|
||||||
|
cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores'))
|
||||||
|
cpu_vcpus = models.IntegerField(null=True, verbose_name=_('CPU vcpus'))
|
||||||
|
memory = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Memory'))
|
||||||
|
disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total'))
|
||||||
|
disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info'))
|
||||||
|
|
||||||
|
os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS'))
|
||||||
|
os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version'))
|
||||||
|
os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch'))
|
||||||
|
hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw'))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cpu_info(self):
|
||||||
|
info = ""
|
||||||
|
if self.cpu_model:
|
||||||
|
info += self.cpu_model
|
||||||
|
if self.cpu_count and self.cpu_cores:
|
||||||
|
info += "{}*{}".format(self.cpu_count, self.cpu_cores)
|
||||||
|
return info
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hardware_info(self):
|
||||||
|
if self.cpu_count:
|
||||||
|
return '{} Core {} {}'.format(
|
||||||
|
self.cpu_vcpus or self.cpu_count * self.cpu_cores,
|
||||||
|
self.memory, self.disk_total
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '{} of {}'.format(self.hardware_info, self.host.hostname)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("HostInfo")
|
|
@ -0,0 +1,56 @@
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from common.fields.model import JsonDictTextField
|
||||||
|
|
||||||
|
__all__ = ['Platform']
|
||||||
|
|
||||||
|
|
||||||
|
class Category(models.TextChoices):
|
||||||
|
Host = 'host', _('Host')
|
||||||
|
Network = 'network', _('Network device')
|
||||||
|
Database = 'database', _('Database')
|
||||||
|
RemoteApp = 'remote_app', _('Microsoft remote app')
|
||||||
|
Cloud = 'cloud', _("Cloud")
|
||||||
|
|
||||||
|
|
||||||
|
class Platform(models.Model):
|
||||||
|
CHARSET_CHOICES = (
|
||||||
|
('utf8', 'UTF-8'),
|
||||||
|
('gbk', 'GBK'),
|
||||||
|
)
|
||||||
|
BASE_CHOICES = (
|
||||||
|
('Linux', 'Linux'),
|
||||||
|
('Unix', 'Unix'),
|
||||||
|
('MacOS', 'MacOS'),
|
||||||
|
('BSD', 'BSD'),
|
||||||
|
('Windows', 'Windows'),
|
||||||
|
('Other', 'Other'),
|
||||||
|
)
|
||||||
|
name = models.SlugField(verbose_name=_("Name"), unique=True, allow_unicode=True)
|
||||||
|
base = models.CharField(choices=BASE_CHOICES, max_length=16, default='Linux', verbose_name=_("Base"))
|
||||||
|
charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset"))
|
||||||
|
meta = JsonDictTextField(blank=True, null=True, verbose_name=_("Meta"))
|
||||||
|
internal = models.BooleanField(default=False, verbose_name=_("Internal"))
|
||||||
|
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def default(cls):
|
||||||
|
linux, created = cls.objects.get_or_create(
|
||||||
|
defaults={'name': 'Linux'}, name='Linux'
|
||||||
|
)
|
||||||
|
return linux.id
|
||||||
|
|
||||||
|
def is_windows(self):
|
||||||
|
return self.base.lower() in ('windows',)
|
||||||
|
|
||||||
|
def is_unixlike(self):
|
||||||
|
return self.base.lower() in ("linux", "unix", "macos", "bsd")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Platform")
|
||||||
|
# ordering = ('name',)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
from .common import *
|
|
@ -5,7 +5,7 @@ from django.core.validators import RegexValidator
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
from ..models import Asset, Node, Platform, SystemUser
|
from ...models import Asset, Node, Platform, SystemUser
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AssetSerializer', 'AssetSimpleSerializer', 'MiniAssetSerializer',
|
'AssetSerializer', 'AssetSimpleSerializer', 'MiniAssetSerializer',
|
||||||
|
@ -82,12 +82,6 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
||||||
'protocol', 'port', 'protocols', 'is_active',
|
'protocol', 'port', 'protocols', 'is_active',
|
||||||
'public_ip', 'number', 'comment',
|
'public_ip', 'number', 'comment',
|
||||||
]
|
]
|
||||||
fields_hardware = [
|
|
||||||
'vendor', 'model', 'sn', 'cpu_model', 'cpu_count',
|
|
||||||
'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info',
|
|
||||||
'os', 'os_version', 'os_arch', 'hostname_raw',
|
|
||||||
'cpu_info', 'hardware_info',
|
|
||||||
]
|
|
||||||
fields_fk = [
|
fields_fk = [
|
||||||
'domain', 'domain_display', 'platform', 'admin_user', 'admin_user_display'
|
'domain', 'domain_display', 'platform', 'admin_user', 'admin_user_display'
|
||||||
]
|
]
|
||||||
|
@ -95,16 +89,13 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
||||||
'nodes', 'nodes_display', 'labels', 'labels_display',
|
'nodes', 'nodes_display', 'labels', 'labels_display',
|
||||||
]
|
]
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
'connectivity', 'date_verified', 'cpu_info', 'hardware_info',
|
'connectivity', 'date_verified', 'created_by', 'date_created',
|
||||||
'created_by', 'date_created',
|
|
||||||
]
|
]
|
||||||
fields = fields_small + fields_hardware + fields_fk + fields_m2m + read_only_fields
|
fields = fields_small + fields_fk + fields_m2m + read_only_fields
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'protocol': {'write_only': True},
|
'protocol': {'write_only': True},
|
||||||
'port': {'write_only': True},
|
'port': {'write_only': True},
|
||||||
'hardware_info': {'label': _('Hardware info'), 'read_only': True},
|
|
||||||
'admin_user_display': {'label': _('Admin user display'), 'read_only': True},
|
'admin_user_display': {'label': _('Admin user display'), 'read_only': True},
|
||||||
'cpu_info': {'label': _('CPU info')},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_fields(self):
|
def get_fields(self):
|
|
@ -0,0 +1,19 @@
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from .common import AssetSerializer
|
||||||
|
from assets.models import HostInfo
|
||||||
|
|
||||||
|
|
||||||
|
class HardwareSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = HostInfo
|
||||||
|
fields = [
|
||||||
|
'id', 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count',
|
||||||
|
'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info',
|
||||||
|
'os', 'os_version', 'os_arch', 'hostname_raw',
|
||||||
|
'cpu_info', 'hardware_info', 'date_updated'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class HostSerializer(AssetSerializer):
|
||||||
|
hardware_info = HardwareSerializer(read_only=True)
|
|
@ -2,31 +2,31 @@ from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelV
|
||||||
from rest_framework_bulk import BulkModelViewSet
|
from rest_framework_bulk import BulkModelViewSet
|
||||||
|
|
||||||
from ..mixins.api import (
|
from ..mixins.api import (
|
||||||
RelationMixin, AllowBulkDestroyMixin, CommonMixin
|
RelationMixin, AllowBulkDestroyMixin, CommonApiMixin
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class JMSGenericViewSet(CommonMixin, GenericViewSet):
|
class JMSGenericViewSet(CommonApiMixin, GenericViewSet):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class JMSViewSet(CommonMixin, ViewSet):
|
class JMSViewSet(CommonApiMixin, ViewSet):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class JMSModelViewSet(CommonMixin, ModelViewSet):
|
class JMSModelViewSet(CommonApiMixin, ModelViewSet):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class JMSReadOnlyModelViewSet(CommonMixin, ReadOnlyModelViewSet):
|
class JMSReadOnlyModelViewSet(CommonApiMixin, ReadOnlyModelViewSet):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class JMSBulkModelViewSet(CommonMixin, AllowBulkDestroyMixin, BulkModelViewSet):
|
class JMSBulkModelViewSet(CommonApiMixin, AllowBulkDestroyMixin, BulkModelViewSet):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class JMSBulkRelationModelViewSet(CommonMixin,
|
class JMSBulkRelationModelViewSet(CommonApiMixin,
|
||||||
RelationMixin,
|
RelationMixin,
|
||||||
AllowBulkDestroyMixin,
|
AllowBulkDestroyMixin,
|
||||||
BulkModelViewSet):
|
BulkModelViewSet):
|
||||||
|
|
|
@ -13,7 +13,7 @@ from .queryset import QuerySetMixin
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'CommonApiMixin', 'PaginatedResponseMixin', 'RelationMixin', 'CommonMixin'
|
'CommonApiMixin', 'PaginatedResponseMixin', 'RelationMixin',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -82,15 +82,10 @@ class RelationMixin:
|
||||||
self.send_m2m_changed_signal(instance, 'post_remove')
|
self.send_m2m_changed_signal(instance, 'post_remove')
|
||||||
|
|
||||||
|
|
||||||
class CommonApiMixin(SerializerMixin, ExtraFilterFieldsMixin, RenderToJsonMixin):
|
class CommonApiMixin(SerializerMixin, ExtraFilterFieldsMixin,
|
||||||
pass
|
QuerySetMixin, RenderToJsonMixin):
|
||||||
|
|
||||||
|
|
||||||
class CommonMixin(SerializerMixin,
|
|
||||||
QuerySetMixin,
|
|
||||||
ExtraFilterFieldsMixin,
|
|
||||||
RenderToJsonMixin):
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 244ace5a95503ffaf41b73037692e1121f7c066f
|
Loading…
Reference in New Issue