Skip to content

Commit

Permalink
perf: 统一应用树 (jumpserver#6535)
Browse files Browse the repository at this point in the history
* perf: 添加应用树api

* perf: perms tree

* perf: 统一应用树

* perf: 修改icon

* perf: stash it

* perf: 优化应用账号

* perf: 基本完成应用账号重构

* perf: 修改翻译

Co-authored-by: ibuler <[email protected]>
  • Loading branch information
fit2bot and ibuler authored Jul 27, 2021
1 parent d347ed9 commit 905d0d5
Show file tree
Hide file tree
Showing 29 changed files with 621 additions and 416 deletions.
2 changes: 1 addition & 1 deletion apps/applications/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .application import *
from .application_user import *
from .account import *
from .mixin import *
from .remote_app import *
74 changes: 74 additions & 0 deletions apps/applications/api/account.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# coding: utf-8
#

from django_filters import rest_framework as filters
from django.conf import settings
from django.db.models import F, Value, CharField
from django.db.models.functions import Concat
from django.http import Http404

from common.drf.filters import BaseFilterSet
from common.drf.api import JMSModelViewSet
from common.utils import unique
from perms.models import ApplicationPermission
from ..hands import IsOrgAdminOrAppUser, IsOrgAdmin, NeedMFAVerify
from .. import serializers


class AccountFilterSet(BaseFilterSet):
username = filters.CharFilter(field_name='username')
app = filters.CharFilter(field_name='applications', lookup_expr='exact')
app_name = filters.CharFilter(field_name='app_name', lookup_expr='exact')
app_type = filters.CharFilter(field_name='app_type', lookup_expr='exact')
app_category = filters.CharFilter(field_name='app_category', lookup_expr='exact')

class Meta:
model = ApplicationPermission
fields = []


class ApplicationAccountViewSet(JMSModelViewSet):
permission_classes = (IsOrgAdmin, )
search_fields = ['username', 'app_name']
filterset_class = AccountFilterSet
filterset_fields = ['username', 'app_name', 'app_type', 'app_category']
serializer_class = serializers.ApplicationAccountSerializer

http_method_names = ['get', 'put', 'patch', 'options']

def get_queryset(self):
queryset = ApplicationPermission.objects.all() \
.annotate(uid=Concat(
'applications', Value('_'), 'system_users', output_field=CharField()
)) \
.annotate(systemuser=F('system_users')) \
.annotate(systemuser_display=F('system_users__name')) \
.annotate(username=F('system_users__username')) \
.annotate(password=F('system_users__password')) \
.annotate(app=F('applications')) \
.annotate(app_name=F("applications__name")) \
.annotate(app_category=F("applications__category")) \
.annotate(app_type=F("applications__type"))\
.values('username', 'password', 'systemuser', 'systemuser_display',
'app', 'app_name', 'app_category', 'app_type', 'uid')
return queryset

def get_object(self):
obj = self.get_queryset().filter(
uid=self.kwargs['pk']
).first()
if not obj:
raise Http404()
return obj

def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
queryset_list = unique(queryset, key=lambda x: (x['app'], x['systemuser']))
return queryset_list


class ApplicationAccountSecretViewSet(ApplicationAccountViewSet):
serializer_class = serializers.ApplicationAccountSecretSerializer
permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
http_method_names = ['get', 'options']

17 changes: 16 additions & 1 deletion apps/applications/api/application.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# coding: utf-8
#

from orgs.mixins.api import OrgBulkModelViewSet
from rest_framework.decorators import action
from rest_framework.response import Response

from common.tree import TreeNodeSerializer
from ..hands import IsOrgAdminOrAppUser
from .. import serializers
from ..models import Application
Expand All @@ -19,4 +23,15 @@ class ApplicationViewSet(OrgBulkModelViewSet):
}
search_fields = ('name', 'type', 'category')
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.ApplicationSerializer
serializer_classes = {
'default': serializers.ApplicationSerializer,
'get_tree': TreeNodeSerializer
}

@action(methods=['GET'], detail=False, url_path='tree')
def get_tree(self, request, *args, **kwargs):
show_count = request.query_params.get('show_count', '1') == '1'
queryset = self.filter_queryset(self.get_queryset())
tree_nodes = Application.create_tree_nodes(queryset, show_count=show_count)
serializer = self.get_serializer(tree_nodes, many=True)
return Response(serializer.data)
55 changes: 0 additions & 55 deletions apps/applications/api/application_user.py

This file was deleted.

107 changes: 35 additions & 72 deletions apps/applications/api/mixin.py
Original file line number Diff line number Diff line change
@@ -1,89 +1,52 @@
from orgs.models import Organization
from django.utils.translation import ugettext as _

from common.tree import TreeNode
from orgs.models import Organization
from ..models import Application

__all__ = ['SerializeApplicationToTreeNodeMixin']


class SerializeApplicationToTreeNodeMixin:

@staticmethod
def _serialize_db(db):
return {
'id': db.id,
'name': db.name,
'title': db.name,
'pId': '',
'open': False,
'iconSkin': 'database',
'meta': {'type': 'database_app'}
}

@staticmethod
def _serialize_remote_app(remote_app):
return {
'id': remote_app.id,
'name': remote_app.name,
'title': remote_app.name,
'pId': '',
'open': False,
'isParent': False,
'iconSkin': 'chrome',
'meta': {'type': 'remote_app'}
}

@staticmethod
def _serialize_cloud(cloud):
return {
'id': cloud.id,
'name': cloud.name,
'title': cloud.name,
'pId': '',
'open': False,
'isParent': False,
'iconSkin': 'k8s',
'meta': {'type': 'k8s_app'}
}

def _serialize_application(self, application):
method_name = f'_serialize_{application.category}'
data = getattr(self, method_name)(application)
data.update({
'pId': application.org.id,
'org_name': application.org_name
})
return data

def serialize_applications(self, applications):
data = [self._serialize_application(application) for application in applications]
return data
def filter_organizations(applications):
organization_ids = set(applications.values_list('org_id', flat=True))
organizations = [Organization.get_instance(org_id) for org_id in organization_ids]
return organizations

@staticmethod
def _serialize_organization(org):
return {
'id': org.id,
'name': org.name,
'title': org.name,
def create_root_node():
name = _('My applications')
node = TreeNode(**{
'id': 'applications',
'name': name,
'title': name,
'pId': '',
'open': True,
'isParent': True,
'meta': {
'type': 'node'
'type': 'root'
}
}

def serialize_organizations(self, organizations):
data = [self._serialize_organization(org) for org in organizations]
return data

@staticmethod
def filter_organizations(applications):
organization_ids = set(applications.values_list('org_id', flat=True))
organizations = [Organization.get_instance(org_id) for org_id in organization_ids]
return organizations
})
return node

def serialize_applications_with_org(self, applications):
root_node = self.create_root_node()
tree_nodes = [root_node]
organizations = self.filter_organizations(applications)
data_organizations = self.serialize_organizations(organizations)
data_applications = self.serialize_applications(applications)
data = data_organizations + data_applications
return data

for i, org in enumerate(organizations):
# 组织节点
org_node = org.as_tree_node(pid=root_node.id)
tree_nodes.append(org_node)
org_applications = applications.filter(org_id=org.id)
count = org_applications.count()
org_node.name += '({})'.format(count)

# 各应用节点
apps_nodes = Application.create_tree_nodes(
queryset=org_applications, root_node=org_node,
show_empty=False
)
tree_nodes += apps_nodes
return tree_nodes
30 changes: 24 additions & 6 deletions apps/applications/const.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# coding: utf-8
#

from django.db.models import TextChoices
from django.utils.translation import ugettext_lazy as _


class ApplicationCategoryChoices(TextChoices):
class AppCategory(TextChoices):
db = 'db', _('Database')
remote_app = 'remote_app', _('Remote app')
cloud = 'cloud', 'Cloud'
Expand All @@ -15,7 +14,7 @@ def get_label(cls, category):
return dict(cls.choices).get(category, '')


class ApplicationTypeChoices(TextChoices):
class AppType(TextChoices):
# db category
mysql = 'mysql', 'MySQL'
oracle = 'oracle', 'Oracle'
Expand All @@ -31,19 +30,38 @@ class ApplicationTypeChoices(TextChoices):
# cloud category
k8s = 'k8s', 'Kubernetes'

@classmethod
def category_types_mapper(cls):
return {
AppCategory.db: [cls.mysql, cls.oracle, cls.pgsql, cls.mariadb],
AppCategory.remote_app: [cls.chrome, cls.mysql_workbench, cls.vmware_client, cls.custom],
AppCategory.cloud: [cls.k8s]
}

@classmethod
def type_category_mapper(cls):
mapper = {}
for category, tps in cls.category_types_mapper().items():
for tp in tps:
mapper[tp] = category
return mapper

@classmethod
def get_label(cls, tp):
return dict(cls.choices).get(tp, '')

@classmethod
def db_types(cls):
return [cls.mysql.value, cls.oracle.value, cls.pgsql.value, cls.mariadb.value]
return [tp.value for tp in cls.category_types_mapper()[AppCategory.db]]

@classmethod
def remote_app_types(cls):
return [cls.chrome.value, cls.mysql_workbench.value, cls.vmware_client.value, cls.custom.value]
return [tp.value for tp in cls.category_types_mapper()[AppCategory.remote_app]]

@classmethod
def cloud_types(cls):
return [cls.k8s.value]
return [tp.value for tp in cls.category_types_mapper()[AppCategory.cloud]]




Empty file.
Loading

0 comments on commit 905d0d5

Please sign in to comment.