perf: 统一应用树 (#6535)

* perf: 添加应用树api

* perf: perms tree

* perf: 统一应用树

* perf: 修改icon

* perf: stash it

* perf: 优化应用账号

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

* perf: 修改翻译

Co-authored-by: ibuler <ibuler@qq.com>
pull/6552/head
fit2bot 2021-07-27 16:06:00 +08:00 committed by GitHub
parent d347ed9862
commit 905d0d5131
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 626 additions and 421 deletions

View File

@ -1,4 +1,4 @@
from .application import *
from .application_user import *
from .account import *
from .mixin import *
from .remote_app import *

View File

@ -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']

View File

@ -1,7 +1,11 @@
# coding: utf-8
#
from orgs.mixins.api import OrgBulkModelViewSet
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
@ -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)

View File

@ -1,55 +0,0 @@
# coding: utf-8
#
from rest_framework import generics
from django.conf import settings
from ..hands import IsOrgAdminOrAppUser, IsOrgAdmin, NeedMFAVerify
from .. import serializers
from ..models import Application, ApplicationUser
from perms.models import ApplicationPermission
class ApplicationUserListApi(generics.ListAPIView):
permission_classes = (IsOrgAdmin, )
filterset_fields = ('name', 'username')
search_fields = filterset_fields
serializer_class = serializers.ApplicationUserSerializer
_application = None
@property
def application(self):
if self._application is None:
app_id = self.request.query_params.get('application_id')
if app_id:
self._application = Application.objects.get(id=app_id)
return self._application
def get_serializer_context(self):
context = super().get_serializer_context()
context.update({
'application': self.application
})
return context
def get_queryset(self):
queryset = ApplicationUser.objects.none()
if not self.application:
return queryset
system_user_ids = ApplicationPermission.objects.filter(applications=self.application)\
.values_list('system_users', flat=True)
if not system_user_ids:
return queryset
queryset = ApplicationUser.objects.filter(id__in=system_user_ids)
return queryset
class ApplicationUserAuthInfoListApi(ApplicationUserListApi):
serializer_class = serializers.ApplicationUserWithAuthInfoSerializer
http_method_names = ['get']
permission_classes = [IsOrgAdminOrAppUser]
def get_permissions(self):
if settings.SECURITY_VIEW_AUTH_NEED_MFA:
self.permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
return super().get_permissions()

View File

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

View File

@ -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'
@ -15,7 +14,7 @@ class ApplicationCategoryChoices(TextChoices):
return dict(cls.choices).get(category, '')
class ApplicationTypeChoices(TextChoices):
class AppType(TextChoices):
# db category
mysql = 'mysql', 'MySQL'
oracle = 'oracle', 'Oracle'
@ -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]]

View File

View File

@ -1,19 +1,170 @@
from django.db import models
from django.db.models import Count
from django.utils.translation import ugettext_lazy as _
from orgs.mixins.models import OrgModelMixin
from common.mixins import CommonModelMixin
from common.tree import TreeNode
from assets.models import Asset, SystemUser
from .. import const
class Application(CommonModelMixin, OrgModelMixin):
class ApplicationTreeNodeMixin:
id: str
name: str
type: str
category: str
@classmethod
def create_choice_node(cls, c, pid, tp, opened=False, counts=None,
show_empty=True, show_count=True):
count = counts.get(c.name, 0)
if count == 0 and not show_empty:
return None
name = c.name
label = c.label
if count is not None and show_count:
label = '{} ({})'.format(label, count)
data = {
'id': name,
'name': label,
'title': label,
'pId': pid,
'isParent': bool(count),
'open': opened,
'iconSkin': '',
'meta': {
'type': tp,
}
}
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, root_node, counts=None, show_empty=True, show_count=True):
nodes = []
categories = const.AppType.category_types_mapper().keys()
for category in categories:
node = cls.create_choice_node(
category, pid=root_node.id, tp='category',
counts=counts, opened=True, show_empty=show_empty,
show_count=show_count
)
if not node:
continue
nodes.append(node)
return nodes
@classmethod
def create_types_tree_nodes(cls, counts, show_empty=True, show_count=True):
nodes = []
type_category_mapper = const.AppType.type_category_mapper()
for tp in const.AppType.type_category_mapper().keys():
category = type_category_mapper.get(tp)
node = cls.create_choice_node(
tp, pid=category.name, tp='type', counts=counts,
show_empty=show_empty, show_count=show_count
)
if not node:
continue
nodes.append(node)
return nodes
@staticmethod
def get_tree_node_counts(queryset):
counts = {'applications': queryset.count()}
category_counts = queryset.values('category') \
.annotate(count=Count('id')) \
.order_by()
for item in category_counts:
counts[item['category']] = item['count']
type_counts = queryset.values('type') \
.annotate(count=Count('id')) \
.order_by()
for item in type_counts:
counts[item['type']] = item['count']
return counts
@classmethod
def create_tree_nodes(cls, queryset, root_node=None, show_empty=True, show_count=True):
counts = cls.get_tree_node_counts(queryset) if show_count else {}
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_tree_nodes(
root_node, counts, show_empty=show_empty,
show_count=show_count
)
# 类型的节点
tree_nodes += cls.create_types_tree_nodes(
counts, show_empty=show_empty, show_count=show_count
)
# 应用的节点
for app in queryset:
tree_nodes.append(app.as_tree_node())
return tree_nodes
def as_tree_node(self):
icon_skin_category_mapper = {
'remote_app': 'chrome',
'db': 'database',
'cloud': 'cloud'
}
icon_skin = icon_skin_category_mapper.get(self.category, 'file')
node = TreeNode(**{
'id': str(self.id),
'name': self.name,
'title': self.name,
'pId': str(self.type),
'isParent': False,
'open': False,
'iconSkin': icon_skin,
'meta': {
'type': 'application',
'data': {
'category': self.category,
'type': self.type,
}
}
})
return node
class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin):
name = models.CharField(max_length=128, verbose_name=_('Name'))
category = models.CharField(
max_length=16, choices=const.ApplicationCategoryChoices.choices, verbose_name=_('Category')
max_length=16, choices=const.AppCategory.choices, verbose_name=_('Category')
)
type = models.CharField(
max_length=16, choices=const.ApplicationTypeChoices.choices, verbose_name=_('Type')
max_length=16, choices=const.AppType.choices, verbose_name=_('Type')
)
domain = models.ForeignKey(
'assets.Domain', null=True, blank=True, related_name='applications',
@ -35,7 +186,7 @@ class Application(CommonModelMixin, OrgModelMixin):
@property
def category_remote_app(self):
return self.category == const.ApplicationCategoryChoices.remote_app.value
return self.category == const.AppCategory.remote_app.value
def get_rdp_remote_app_setting(self):
from applications.serializers.attrs import get_serializer_class_by_application_type

View File

@ -6,12 +6,12 @@ from django.utils.translation import ugettext_lazy as _
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from common.drf.serializers import MethodSerializer
from .attrs import category_serializer_classes_mapping, type_serializer_classes_mapping
from assets.serializers import SystemUserSerializer
from .. import models
from .. import const
__all__ = [
'ApplicationSerializer', 'ApplicationSerializerMixin',
'ApplicationUserSerializer', 'ApplicationUserWithAuthInfoSerializer'
'ApplicationAccountSerializer', 'ApplicationAccountSecretSerializer'
]
@ -52,9 +52,8 @@ class ApplicationSerializer(ApplicationSerializerMixin, BulkOrgResourceModelSeri
model = models.Application
fields_mini = ['id', 'name']
fields_small = fields_mini + [
'category', 'category_display', 'type', 'type_display', 'attrs',
'date_created', 'date_updated',
'created_by', 'comment'
'category', 'category_display', 'type', 'type_display',
'attrs', 'date_created', 'date_updated', 'created_by', 'comment'
]
fields_fk = ['domain']
fields = fields_small + fields_fk
@ -68,41 +67,34 @@ class ApplicationSerializer(ApplicationSerializerMixin, BulkOrgResourceModelSeri
return _attrs
class ApplicationUserSerializer(SystemUserSerializer):
application_name = serializers.SerializerMethodField(label=_('Application name'))
application_category = serializers.SerializerMethodField(label=_('Application category'))
application_type = serializers.SerializerMethodField(label=_('Application type'))
class ApplicationAccountSerializer(serializers.Serializer):
username = serializers.ReadOnlyField(label=_("Username"))
password = serializers.CharField(write_only=True, label=_("Password"))
systemuser = serializers.ReadOnlyField(label=_('System user'))
systemuser_display = serializers.ReadOnlyField(label=_("System user display"))
app = serializers.ReadOnlyField(label=_('App'))
uid = serializers.ReadOnlyField(label=_("Union id"))
app_name = serializers.ReadOnlyField(label=_("Application name"), read_only=True)
app_category = serializers.ChoiceField(label=_('Category'), choices=const.AppCategory.choices, read_only=True)
app_category_display = serializers.SerializerMethodField(label=_('Category'))
app_type = serializers.ChoiceField(label=_('Type'), choices=const.AppType.choices, read_only=True)
app_type_display = serializers.SerializerMethodField(label=_('Type'))
class Meta(SystemUserSerializer.Meta):
model = models.ApplicationUser
fields_mini = [
'id', 'application_name', 'application_category', 'application_type', 'name', 'username'
]
fields_small = fields_mini + [
'protocol', 'login_mode', 'login_mode_display', 'priority',
"username_same_with_user", 'comment',
]
fields = fields_small
extra_kwargs = {
'login_mode_display': {'label': _('Login mode display')},
'created_by': {'read_only': True},
}
category_mapper = dict(const.AppCategory.choices)
type_mapper = dict(const.AppType.choices)
@property
def application(self):
return self.context['application']
def create(self, validated_data):
pass
def get_application_name(self, obj):
return self.application.name
def update(self, instance, validated_data):
pass
def get_application_category(self, obj):
return self.application.get_category_display()
def get_app_category_display(self, obj):
return self.category_mapper.get(obj['app_category'])
def get_application_type(self, obj):
return self.application.get_type_display()
def get_app_type_display(self, obj):
return self.type_mapper.get(obj['app_type'])
class ApplicationUserWithAuthInfoSerializer(ApplicationUserSerializer):
class Meta(ApplicationUserSerializer.Meta):
fields = ApplicationUserSerializer.Meta.fields + ['password', 'token']
class ApplicationAccountSecretSerializer(ApplicationAccountSerializer):
password = serializers.CharField(write_only=False, label=_("Password"))

View File

@ -14,9 +14,9 @@ __all__ = [
# ---------------------------------------------------
category_serializer_classes_mapping = {
const.ApplicationCategoryChoices.db.value: application_category.DBSerializer,
const.ApplicationCategoryChoices.remote_app.value: application_category.RemoteAppSerializer,
const.ApplicationCategoryChoices.cloud.value: application_category.CloudSerializer,
const.AppCategory.db.value: application_category.DBSerializer,
const.AppCategory.remote_app.value: application_category.RemoteAppSerializer,
const.AppCategory.cloud.value: application_category.CloudSerializer,
}
# define `attrs` field `type serializers mapping`
@ -24,17 +24,17 @@ category_serializer_classes_mapping = {
type_serializer_classes_mapping = {
# db
const.ApplicationTypeChoices.mysql.value: application_type.MySQLSerializer,
const.ApplicationTypeChoices.mariadb.value: application_type.MariaDBSerializer,
const.ApplicationTypeChoices.oracle.value: application_type.OracleSerializer,
const.ApplicationTypeChoices.pgsql.value: application_type.PostgreSerializer,
const.AppType.mysql.value: application_type.MySQLSerializer,
const.AppType.mariadb.value: application_type.MariaDBSerializer,
const.AppType.oracle.value: application_type.OracleSerializer,
const.AppType.pgsql.value: application_type.PostgreSerializer,
# remote-app
const.ApplicationTypeChoices.chrome.value: application_type.ChromeSerializer,
const.ApplicationTypeChoices.mysql_workbench.value: application_type.MySQLWorkbenchSerializer,
const.ApplicationTypeChoices.vmware_client.value: application_type.VMwareClientSerializer,
const.ApplicationTypeChoices.custom.value: application_type.CustomSerializer,
const.AppType.chrome.value: application_type.ChromeSerializer,
const.AppType.mysql_workbench.value: application_type.MySQLWorkbenchSerializer,
const.AppType.vmware_client.value: application_type.VMwareClientSerializer,
const.AppType.custom.value: application_type.CustomSerializer,
# cloud
const.ApplicationTypeChoices.k8s.value: application_type.K8SSerializer
const.AppType.k8s.value: application_type.K8SSerializer
}

View File

@ -10,12 +10,14 @@ app_name = 'applications'
router = BulkRouter()
router.register(r'applications', api.ApplicationViewSet, 'application')
router.register(r'accounts', api.ApplicationAccountViewSet, 'application-account')
router.register(r'account-secrets', api.ApplicationAccountSecretViewSet, 'application-account-secret')
urlpatterns = [
path('remote-apps/<uuid:pk>/connection-info/', api.RemoteAppConnectionInfoApi.as_view(), name='remote-app-connection-info'),
path('application-users/', api.ApplicationUserListApi.as_view(), name='application-user'),
path('application-user-auth-infos/', api.ApplicationUserAuthInfoListApi.as_view(), name='application-user-auth-info')
# path('accounts/', api.ApplicationAccountViewSet.as_view(), name='application-account'),
# path('account-secrets/', api.ApplicationAccountSecretViewSet.as_view(), name='application-account-secret')
]

View File

@ -1,5 +1,4 @@
from django.db.models import F, Q
from django.conf import settings
from rest_framework.decorators import action
from django_filters import rest_framework as filters
from rest_framework.response import Response
@ -87,11 +86,6 @@ class AccountSecretsViewSet(AccountViewSet):
permission_classes = (IsOrgAdmin, NeedMFAVerify)
http_method_names = ['get']
def get_permissions(self):
if not settings.SECURITY_VIEW_AUTH_NEED_MFA:
self.permission_classes = [IsOrgAdminOrAppUser]
return super().get_permissions()
class AccountTaskCreateAPI(CreateAPIView):
permission_classes = (IsOrgAdminOrAppUser,)

View File

@ -60,10 +60,10 @@ class ProtocolMixin:
@classmethod
def get_protocol_by_application_type(cls, app_type):
from applications.const import ApplicationTypeChoices
from applications.const import AppType
if app_type in cls.APPLICATION_CATEGORY_PROTOCOLS:
protocol = app_type
elif app_type in ApplicationTypeChoices.remote_app_types():
elif app_type in AppType.remote_app_types():
protocol = cls.Protocol.rdp
else:
protocol = None

View File

@ -28,6 +28,7 @@ class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
},
'private_key': {'write_only': True},
'public_key': {'write_only': True},
'systemuser_display': {'label': _('System user display')}
}
ref_name = 'AssetAccountSerializer'

View File

@ -11,7 +11,7 @@ from rest_framework.permissions import AllowAny
from common.utils.timezone import utcnow
from common.const.http import POST, GET
from common.drf.api import JmsGenericViewSet
from common.drf.api import JMSGenericViewSet
from common.drf.serializers import EmptySerializer
from common.permissions import IsSuperUser
from common.utils import reverse
@ -26,7 +26,7 @@ NEXT_URL = 'next'
AUTH_KEY = 'authkey'
class SSOViewSet(AuthMixin, JmsGenericViewSet):
class SSOViewSet(AuthMixin, JMSGenericViewSet):
queryset = SSOToken.objects.all()
serializer_classes = {
'login_url': SSOTokenSerializer,

View File

@ -1,4 +1,4 @@
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet, ViewSet
from rest_framework_bulk import BulkModelViewSet
from ..mixins.api import (
@ -15,19 +15,23 @@ class CommonMixin(SerializerMixin,
pass
class JmsGenericViewSet(CommonMixin,
GenericViewSet):
class JMSGenericViewSet(CommonMixin, GenericViewSet):
pass
class JMSModelViewSet(CommonMixin,
ModelViewSet):
class JMSViewSet(CommonMixin, ViewSet):
pass
class JMSBulkModelViewSet(CommonMixin,
AllowBulkDestroyMixin,
BulkModelViewSet):
class JMSModelViewSet(CommonMixin, ModelViewSet):
pass
class JMSReadOnlyModelViewSet(CommonMixin, ReadOnlyModelViewSet):
pass
class JMSBulkModelViewSet(CommonMixin, AllowBulkDestroyMixin, BulkModelViewSet):
pass

View File

@ -24,6 +24,7 @@ from ..utils import lazyproperty
__all__ = [
'JSONResponseMixin', 'CommonApiMixin', 'AsyncApiMixin', 'RelationMixin',
'QuerySetMixin', 'ExtraFilterFieldsMixin', 'RenderToJsonMixin',
'SerializerMixin', 'AllowBulkDestroyMixin', 'PaginatedResponseMixin'
]

View File

@ -112,6 +112,9 @@ class UserCanUpdateSSHKey(permissions.BasePermission):
class NeedMFAVerify(permissions.BasePermission):
def has_permission(self, request, view):
if not settings.SECURITY_VIEW_AUTH_NEED_MFA:
return True
mfa_verify_time = request.session.get('MFA_VERIFY_TIME', 0)
if time.time() - mfa_verify_time < settings.SECURITY_MFA_VERIFY_TTL:
return True

View File

@ -273,3 +273,17 @@ def bulk_get(d, *keys, default=None):
for key in keys:
values.append(d.get(key, default))
return values
def unique(objects, key=None):
seen = OrderedDict()
if key is None:
key = lambda item: item
for obj in objects:
v = key(obj)
if v not in seen:
seen[v] = obj
return list(seen.values())

View File

@ -20,7 +20,7 @@ def celery_flower_view(request, path):
try:
response = proxy_view(request, remote_url)
except Exception as e:
msg = _("<h1>Flow service unavailable, check it</h1>") + \
msg = _("<h1>Flower service unavailable, check it</h1>") + \
'<br><br> <div>{}</div>'.format(e)
response = HttpResponse(msg)
return response

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-07-26 19:02+0800\n"
"POT-Creation-Date: 2021-07-27 15:57+0800\n"
"PO-Revision-Date: 2021-05-20 10:54+0800\n"
"Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\n"
@ -18,11 +18,11 @@ msgstr ""
"X-Generator: Poedit 2.4.3\n"
#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:47
#: applications/models/application.py:11 assets/models/asset.py:139
#: applications/models/application.py:162 assets/models/asset.py:139
#: assets/models/base.py:175 assets/models/cluster.py:18
#: assets/models/cmd_filter.py:21 assets/models/domain.py:21
#: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24
#: orgs/models.py:23 perms/models/base.py:49 settings/models.py:29
#: orgs/models.py:24 perms/models/base.py:49 settings/models.py:29
#: terminal/models/storage.py:23 terminal/models/task.py:16
#: terminal/models/terminal.py:100 users/forms/profile.py:32
#: users/models/group.py:15 users/models/user.py:551
@ -52,17 +52,17 @@ msgstr "激活中"
# msgid "Date created"
# msgstr "创建日期"
#: acls/models/base.py:32 applications/models/application.py:24
#: acls/models/base.py:32 applications/models/application.py:175
#: assets/models/asset.py:144 assets/models/asset.py:220
#: assets/models/base.py:180 assets/models/cluster.py:29
#: assets/models/cmd_filter.py:23 assets/models/cmd_filter.py:64
#: assets/models/domain.py:22 assets/models/domain.py:53
#: assets/models/group.py:23 assets/models/label.py:23 ops/models/adhoc.py:37
#: orgs/models.py:26 perms/models/base.py:57 settings/models.py:34
#: orgs/models.py:27 perms/models/base.py:57 settings/models.py:34
#: terminal/models/storage.py:26 terminal/models/terminal.py:114
#: tickets/models/ticket.py:73 users/models/group.py:16
#: users/models/user.py:584 xpack/plugins/change_auth_plan/models.py:77
#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:108
#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:98
#: xpack/plugins/gathered_user/models.py:26
msgid "Comment"
msgstr "备注"
@ -94,7 +94,7 @@ msgstr "动作"
#: acls/serializers/login_acl.py:33 assets/models/label.py:15
#: audits/models.py:36 audits/models.py:56 audits/models.py:69
#: audits/serializers.py:93 authentication/models.py:44
#: authentication/models.py:97 orgs/models.py:18 orgs/models.py:418
#: authentication/models.py:97 orgs/models.py:19 orgs/models.py:433
#: perms/models/base.py:50 templates/index.html:78
#: terminal/backends/command/models.py:18
#: terminal/backends/command/serializers.py:12 terminal/models/session.py:38
@ -126,8 +126,8 @@ msgstr "系统用户"
#: terminal/backends/command/serializers.py:13 terminal/models/session.py:40
#: users/templates/users/user_asset_permission.html:40
#: users/templates/users/user_asset_permission.html:70
#: xpack/plugins/change_auth_plan/models.py:290
#: xpack/plugins/cloud/models.py:212
#: xpack/plugins/change_auth_plan/models.py:282
#: xpack/plugins/cloud/models.py:202
msgid "Asset"
msgstr "资产"
@ -140,7 +140,7 @@ msgstr "审批人"
msgid "Login asset confirm"
msgstr "登录资产复核"
#: acls/serializers/login_acl.py:18 xpack/plugins/cloud/serializers.py:165
#: acls/serializers/login_acl.py:18
msgid "IP address invalid: `{}`"
msgstr "IP 地址无效: `{}`"
@ -172,6 +172,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. "
#: acls/serializers/login_asset_acl.py:17
#: acls/serializers/login_asset_acl.py:51
#: applications/serializers/application.py:71
#: applications/serializers/attrs/application_type/chrome.py:20
#: applications/serializers/attrs/application_type/custom.py:21
#: applications/serializers/attrs/application_type/mysql_workbench.py:30
@ -181,8 +182,8 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. "
#: ops/models/adhoc.py:148 users/forms/profile.py:31 users/models/user.py:549
#: users/templates/users/_select_user_modal.html:14
#: xpack/plugins/change_auth_plan/models.py:47
#: xpack/plugins/change_auth_plan/models.py:286
#: xpack/plugins/cloud/serializers.py:67
#: xpack/plugins/change_auth_plan/models.py:278
#: xpack/plugins/cloud/serializers.py:65
msgid "Username"
msgstr "用户名"
@ -228,21 +229,31 @@ msgstr "组织 `{}` 不存在"
msgid "None of the reviewers belong to Organization `{}`"
msgstr "所有复核人都不属于组织 `{}`"
#: applications/const.py:9
#: applications/api/mixin.py:19 templates/_nav_user.html:10
msgid "My applications"
msgstr "我的应用"
#: applications/const.py:8
#: applications/serializers/attrs/application_category/db.py:14
#: applications/serializers/attrs/application_type/mysql_workbench.py:26
msgid "Database"
msgstr "数据库"
#: applications/const.py:10
#: applications/const.py:9
msgid "Remote app"
msgstr "远程应用"
#: applications/const.py:29
#: applications/const.py:28
msgid "Custom"
msgstr "自定义"
#: applications/models/application.py:13 assets/models/label.py:21
#: applications/models/application.py:46 templates/_nav.html:60
msgid "Applications"
msgstr "应用管理"
#: applications/models/application.py:164
#: applications/serializers/application.py:78
#: applications/serializers/application.py:79 assets/models/label.py:21
#: perms/models/application_permission.py:20
#: perms/serializers/application/permission.py:16
#: perms/serializers/application/user_permission.py:33
@ -250,7 +261,9 @@ msgstr "自定义"
msgid "Category"
msgstr "类别"
#: applications/models/application.py:16 assets/models/cmd_filter.py:53
#: applications/models/application.py:167
#: applications/serializers/application.py:80
#: applications/serializers/application.py:81 assets/models/cmd_filter.py:53
#: assets/models/user.py:202 perms/models/application_permission.py:23
#: perms/serializers/application/permission.py:17
#: perms/serializers/application/user_permission.py:34
@ -260,12 +273,12 @@ msgstr "类别"
msgid "Type"
msgstr "类型"
#: applications/models/application.py:20 assets/models/asset.py:188
#: applications/models/application.py:171 assets/models/asset.py:188
#: assets/models/domain.py:27 assets/models/domain.py:52
msgid "Domain"
msgstr "网域"
#: applications/models/application.py:22 xpack/plugins/cloud/models.py:33
#: applications/models/application.py:173 xpack/plugins/cloud/models.py:33
msgid "Attrs"
msgstr ""
@ -278,29 +291,63 @@ msgid "Type(Dispaly)"
msgstr "类型 (显示名称)"
#: applications/serializers/application.py:72
#: applications/serializers/application.py:100
#: applications/serializers/attrs/application_type/chrome.py:23
#: applications/serializers/attrs/application_type/custom.py:25
#: applications/serializers/attrs/application_type/mysql_workbench.py:34
#: applications/serializers/attrs/application_type/vmware_client.py:30
#: assets/models/base.py:177 audits/signals_handler.py:63
#: authentication/forms.py:22
#: authentication/templates/authentication/login.html:164
#: settings/serializers/settings.py:94 users/forms/profile.py:21
#: users/templates/users/user_otp_check_password.html:13
#: users/templates/users/user_password_update.html:43
#: users/templates/users/user_password_verify.html:18
#: xpack/plugins/change_auth_plan/models.py:68
#: xpack/plugins/change_auth_plan/models.py:190
#: xpack/plugins/change_auth_plan/models.py:285
#: xpack/plugins/cloud/serializers.py:67
msgid "Password"
msgstr "密码"
#: applications/serializers/application.py:73 assets/models/authbook.py:16
#: assets/models/user.py:277 audits/models.py:39
#: perms/models/application_permission.py:31
#: perms/models/asset_permission.py:101 templates/_nav.html:45
#: terminal/backends/command/models.py:20
#: terminal/backends/command/serializers.py:14 terminal/models/session.py:42
#: users/templates/users/_granted_assets.html:27
#: users/templates/users/user_asset_permission.html:42
#: users/templates/users/user_asset_permission.html:76
#: users/templates/users/user_asset_permission.html:159
#: users/templates/users/user_database_app_permission.html:40
#: users/templates/users/user_database_app_permission.html:67
msgid "System user"
msgstr "系统用户"
#: applications/serializers/application.py:74 assets/serializers/account.py:31
msgid "System user display"
msgstr "系统用户显示"
#: applications/serializers/application.py:75
msgid "App"
msgstr "应用"
#: applications/serializers/application.py:76
msgid "Union id"
msgstr "联合ID"
#: applications/serializers/application.py:77
msgid "Application name"
msgstr "应用名称"
#: applications/serializers/application.py:73
msgid "Application category"
msgstr "应用类别"
#: applications/serializers/application.py:74
msgid "Application type"
msgstr "应用类型"
#: applications/serializers/application.py:87
#: assets/serializers/system_user.py:53 assets/serializers/system_user.py:201
msgid "Login mode display"
msgstr "认证方式(显示名称)"
#: applications/serializers/attrs/application_category/cloud.py:9
#: assets/models/cluster.py:40
msgid "Cluster"
msgstr "集群"
#: applications/serializers/attrs/application_category/db.py:11
#: ops/models/adhoc.py:146 xpack/plugins/cloud/serializers.py:65
#: ops/models/adhoc.py:146 xpack/plugins/cloud/serializers.py:63
msgid "Host"
msgstr "主机"
@ -310,7 +357,7 @@ msgstr "主机"
#: applications/serializers/attrs/application_type/oracle.py:11
#: applications/serializers/attrs/application_type/pgsql.py:11
#: assets/models/asset.py:185 assets/models/domain.py:50
#: xpack/plugins/cloud/serializers.py:66
#: xpack/plugins/cloud/serializers.py:64
msgid "Port"
msgstr "端口"
@ -326,24 +373,6 @@ msgstr "应用路径"
msgid "Target URL"
msgstr "目标URL"
#: applications/serializers/attrs/application_type/chrome.py:23
#: applications/serializers/attrs/application_type/custom.py:25
#: applications/serializers/attrs/application_type/mysql_workbench.py:34
#: applications/serializers/attrs/application_type/vmware_client.py:30
#: assets/models/base.py:177 audits/signals_handler.py:63
#: authentication/forms.py:22
#: authentication/templates/authentication/login.html:164
#: settings/serializers/settings.py:94 users/forms/profile.py:21
#: users/templates/users/user_otp_check_password.html:13
#: users/templates/users/user_password_update.html:43
#: users/templates/users/user_password_verify.html:18
#: xpack/plugins/change_auth_plan/models.py:68
#: xpack/plugins/change_auth_plan/models.py:194
#: xpack/plugins/change_auth_plan/models.py:293
#: xpack/plugins/cloud/serializers.py:69
msgid "Password"
msgstr "密码"
#: applications/serializers/attrs/application_type/custom.py:13
msgid "Operating parameter"
msgstr "运行参数"
@ -392,7 +421,6 @@ msgstr "系统平台"
#: assets/models/asset.py:186 assets/serializers/asset.py:65
#: perms/serializers/asset/user_permission.py:41
#: xpack/plugins/cloud/models.py:99 xpack/plugins/cloud/serializers.py:183
msgid "Protocols"
msgstr "协议组"
@ -411,7 +439,7 @@ msgstr "激活"
#: assets/models/asset.py:193 assets/models/cluster.py:19
#: assets/models/user.py:191 assets/models/user.py:326 templates/_nav.html:44
#: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers.py:205
#: xpack/plugins/cloud/models.py:92 xpack/plugins/cloud/serializers.py:179
msgid "Admin user"
msgstr "特权用户"
@ -486,10 +514,10 @@ msgstr "标签管理"
#: assets/models/asset.py:218 assets/models/base.py:183
#: assets/models/cluster.py:28 assets/models/cmd_filter.py:26
#: assets/models/cmd_filter.py:67 assets/models/group.py:21
#: common/db/models.py:70 common/mixins/models.py:49 orgs/models.py:24
#: orgs/models.py:422 perms/models/base.py:55 users/models/user.py:592
#: common/db/models.py:70 common/mixins/models.py:49 orgs/models.py:25
#: orgs/models.py:437 perms/models/base.py:55 users/models/user.py:592
#: users/serializers/group.py:33 xpack/plugins/change_auth_plan/models.py:81
#: xpack/plugins/cloud/models.py:114 xpack/plugins/gathered_user/models.py:30
#: xpack/plugins/cloud/models.py:104 xpack/plugins/gathered_user/models.py:30
msgid "Created by"
msgstr "创建者"
@ -499,26 +527,12 @@ msgstr "创建者"
#: assets/models/cluster.py:26 assets/models/domain.py:24
#: assets/models/gathered_user.py:19 assets/models/group.py:22
#: assets/models/label.py:25 common/db/models.py:72 common/mixins/models.py:50
#: ops/models/adhoc.py:38 ops/models/command.py:29 orgs/models.py:25
#: orgs/models.py:420 perms/models/base.py:56 users/models/group.py:18
#: users/models/user.py:774 xpack/plugins/cloud/models.py:117
#: ops/models/adhoc.py:38 ops/models/command.py:29 orgs/models.py:26
#: orgs/models.py:435 perms/models/base.py:56 users/models/group.py:18
#: users/models/user.py:774 xpack/plugins/cloud/models.py:107
msgid "Date created"
msgstr "创建日期"
#: assets/models/authbook.py:16 assets/models/user.py:277 audits/models.py:39
#: perms/models/application_permission.py:31
#: perms/models/asset_permission.py:101 templates/_nav.html:45
#: terminal/backends/command/models.py:20
#: terminal/backends/command/serializers.py:14 terminal/models/session.py:42
#: users/templates/users/_granted_assets.html:27
#: users/templates/users/user_asset_permission.html:42
#: users/templates/users/user_asset_permission.html:76
#: users/templates/users/user_asset_permission.html:159
#: users/templates/users/user_database_app_permission.html:40
#: users/templates/users/user_database_app_permission.html:67
msgid "System user"
msgstr "系统用户"
#: assets/models/authbook.py:17
msgid "Version"
msgstr "版本"
@ -548,20 +562,20 @@ msgid "Date verified"
msgstr "校验日期"
#: assets/models/base.py:178 xpack/plugins/change_auth_plan/models.py:72
#: xpack/plugins/change_auth_plan/models.py:201
#: xpack/plugins/change_auth_plan/models.py:300
#: xpack/plugins/change_auth_plan/models.py:197
#: xpack/plugins/change_auth_plan/models.py:292
msgid "SSH private key"
msgstr "SSH密钥"
#: assets/models/base.py:179 xpack/plugins/change_auth_plan/models.py:75
#: xpack/plugins/change_auth_plan/models.py:197
#: xpack/plugins/change_auth_plan/models.py:296
#: xpack/plugins/change_auth_plan/models.py:193
#: xpack/plugins/change_auth_plan/models.py:288
msgid "SSH public key"
msgstr "SSH公钥"
#: assets/models/base.py:182 assets/models/gathered_user.py:20
#: common/db/models.py:73 common/mixins/models.py:51 ops/models/adhoc.py:39
#: orgs/models.py:421
#: orgs/models.py:436
msgid "Date updated"
msgstr "更新日期"
@ -713,7 +727,7 @@ msgstr "ssh私钥"
#: users/templates/users/user_asset_permission.html:41
#: users/templates/users/user_asset_permission.html:73
#: users/templates/users/user_asset_permission.html:158
#: xpack/plugins/cloud/models.py:93 xpack/plugins/cloud/serializers.py:206
#: xpack/plugins/cloud/models.py:89 xpack/plugins/cloud/serializers.py:180
msgid "Node"
msgstr "节点"
@ -866,6 +880,10 @@ msgstr "密钥指纹"
msgid "Nodes amount"
msgstr "节点数量"
#: assets/serializers/system_user.py:53 assets/serializers/system_user.py:201
msgid "Login mode display"
msgstr "认证方式(显示名称)"
#: assets/serializers/system_user.py:55
msgid "Ad domain"
msgstr "Ad 网域"
@ -1066,8 +1084,8 @@ msgstr "成功"
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:74
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:40
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:78
#: xpack/plugins/change_auth_plan/models.py:181
#: xpack/plugins/change_auth_plan/models.py:315
#: xpack/plugins/change_auth_plan/models.py:177
#: xpack/plugins/change_auth_plan/models.py:307
#: xpack/plugins/gathered_user/models.py:76
msgid "Date start"
msgstr "开始日期"
@ -1138,13 +1156,13 @@ msgstr "用户代理"
msgid "MFA"
msgstr "多因子认证"
#: audits/models.py:106 xpack/plugins/change_auth_plan/models.py:311
#: xpack/plugins/cloud/models.py:171
#: audits/models.py:106 xpack/plugins/change_auth_plan/models.py:303
#: xpack/plugins/cloud/models.py:161
msgid "Reason"
msgstr "原因"
#: audits/models.py:107 tickets/models/ticket.py:47
#: xpack/plugins/cloud/models.py:167 xpack/plugins/cloud/models.py:216
#: xpack/plugins/cloud/models.py:157 xpack/plugins/cloud/models.py:206
msgid "Status"
msgstr "状态"
@ -1178,7 +1196,7 @@ msgid "Hosts display"
msgstr "主机名称"
#: audits/serializers.py:89 ops/models/command.py:26
#: xpack/plugins/cloud/models.py:165
#: xpack/plugins/cloud/models.py:155
msgid "Result"
msgstr "结果"
@ -1391,7 +1409,7 @@ msgstr "{ApplicationPermission} *添加了* {SystemUser}"
msgid "{ApplicationPermission} *REMOVE* {SystemUser}"
msgstr "{ApplicationPermission} *移除了* {SystemUser}"
#: authentication/api/connection_token.py:258
#: authentication/api/connection_token.py:268
msgid "Invalid token"
msgstr "无效的令牌"
@ -1694,7 +1712,7 @@ msgstr "OpenID"
#: authentication/templates/authentication/login.html:205
msgid "CAS"
msgstr ""
msgstr "CAS"
#: authentication/templates/authentication/login_otp.html:17
msgid "One-time password"
@ -1896,7 +1914,7 @@ msgstr "您的请求超时了"
#: common/exceptions.py:35
msgid "M2M reverse not allowed"
msgstr ""
msgstr "多对多反向是不被允许的"
#: common/exceptions.py:41
msgid "Is referenced by other objects and cannot be deleted"
@ -1904,35 +1922,35 @@ msgstr "被其他对象关联,不能删除"
#: common/exceptions.py:47
msgid "This action require verify your MFA"
msgstr ""
msgstr "这个操作需要验证 MFA"
#: common/fields/model.py:80
msgid "Marshal dict data to char field"
msgstr ""
msgstr "编码 dict 为 char"
#: common/fields/model.py:84
msgid "Marshal dict data to text field"
msgstr ""
msgstr "编码 dict 为 text"
#: common/fields/model.py:96
msgid "Marshal list data to char field"
msgstr ""
msgstr "编码 list 为 char"
#: common/fields/model.py:100
msgid "Marshal list data to text field"
msgstr ""
msgstr "编码 list 为 text"
#: common/fields/model.py:104
msgid "Marshal data to char field"
msgstr ""
msgstr "编码数据为 char"
#: common/fields/model.py:108
msgid "Marshal data to text field"
msgstr ""
msgstr "编码数据为 text"
#: common/fields/model.py:150
msgid "Encrypt field using Secret Key"
msgstr ""
msgstr "加密的字段"
#: common/message/backends/exceptions.py:23
msgid "Network error, please contact system administrator"
@ -1942,17 +1960,17 @@ msgstr "网络错误,请联系系统管理员"
msgid "WeCom error, please contact system administrator"
msgstr "企业微信错误,请联系系统管理员"
#: common/mixins/api.py:57
#: common/mixins/api.py:58
msgid "Request file format may be wrong"
msgstr "上传的文件格式错误 或 其它类型资源的文件"
#: common/mixins/models.py:33
msgid "is discard"
msgstr ""
msgstr "忽略的"
#: common/mixins/models.py:34
msgid "discard time"
msgstr ""
msgstr "忽略时间"
#: common/utils/ipip/utils.py:15
msgid "Invalid ip"
@ -1975,8 +1993,8 @@ msgid "JumpServer Open Source Bastion Host"
msgstr "JumpServer 开源堡垒机"
#: jumpserver/views/celery_flower.py:23
msgid "<h1>Flow service unavailable, check it</h1>"
msgstr ""
msgid "<h1>Flower service unavailable, check it</h1>"
msgstr "Flower 服务不可用,请检查"
#: jumpserver/views/other.py:25
msgid ""
@ -2031,7 +2049,7 @@ msgid "Regularly perform"
msgstr "定期执行"
#: ops/mixin.py:106 ops/mixin.py:147
#: xpack/plugins/change_auth_plan/serializers.py:55
#: xpack/plugins/change_auth_plan/serializers.py:51
msgid "Periodic perform"
msgstr "定时执行"
@ -2110,8 +2128,8 @@ msgstr "开始时间"
msgid "End time"
msgstr "完成时间"
#: ops/models/adhoc.py:246 xpack/plugins/change_auth_plan/models.py:184
#: xpack/plugins/change_auth_plan/models.py:318
#: ops/models/adhoc.py:246 xpack/plugins/change_auth_plan/models.py:180
#: xpack/plugins/change_auth_plan/models.py:310
#: xpack/plugins/gathered_user/models.py:79
msgid "Time"
msgstr "时间"
@ -2177,25 +2195,25 @@ msgstr "当前组织 ({}) 不能被删除"
msgid "The organization have resource ({}) cannot be deleted"
msgstr "组织存在资源 ({}) 不能被删除"
#: orgs/mixins/models.py:45 orgs/mixins/serializers.py:25 orgs/models.py:36
#: orgs/models.py:417 orgs/serializers.py:106
#: orgs/mixins/models.py:45 orgs/mixins/serializers.py:25 orgs/models.py:37
#: orgs/models.py:432 orgs/serializers.py:106
#: tickets/serializers/ticket/ticket.py:83
msgid "Organization"
msgstr "组织"
#: orgs/models.py:16
#: orgs/models.py:17
msgid "Organization administrator"
msgstr "组织管理员"
#: orgs/models.py:17
#: orgs/models.py:18
msgid "Organization auditor"
msgstr "组织审计员"
#: orgs/models.py:30
#: orgs/models.py:31
msgid "GLOBAL"
msgstr "全局组织"
#: orgs/models.py:419 users/models/user.py:561
#: orgs/models.py:434 users/models/user.py:561
#: users/templates/users/_select_user_modal.html:15
msgid "Role"
msgstr "角色"
@ -2476,7 +2494,7 @@ msgstr "LDAP 地址"
#: settings/serializers/settings.py:91
msgid "eg: ldap://localhost:389"
msgstr ""
msgstr "如: ldap://localhost:389"
#: settings/serializers/settings.py:93
msgid "Bind DN"
@ -2845,7 +2863,7 @@ msgstr "用户页面"
#: templates/_header_bar.html:78
msgid "API Key"
msgstr ""
msgstr "API Key"
#: templates/_header_bar.html:79
msgid "Logout"
@ -2948,10 +2966,6 @@ msgstr "命令过滤"
msgid "Platform list"
msgstr "平台列表"
#: templates/_nav.html:60
msgid "Applications"
msgstr "应用管理"
#: templates/_nav.html:64 templates/_nav.html:82 templates/_nav_user.html:16
msgid "RemoteApp"
msgstr "远程应用"
@ -3037,7 +3051,7 @@ msgstr "改密日志"
#: templates/_nav.html:163
msgid "XPack"
msgstr ""
msgstr "XPack"
#: templates/_nav.html:171
msgid "Account list"
@ -3055,10 +3069,6 @@ msgstr "系统设置"
msgid "My assets"
msgstr "我的资产"
#: templates/_nav_user.html:10
msgid "My Applications"
msgstr "我的应用"
#: templates/_nav_user.html:31
msgid "Command execution"
msgstr "命令执行"
@ -3540,18 +3550,18 @@ msgstr "桶名称"
#: terminal/serializers/storage.py:30
msgid "Access key"
msgstr ""
msgstr "Access key"
#: terminal/serializers/storage.py:34
msgid "Secret key"
msgstr ""
msgstr "Secret key"
#: terminal/serializers/storage.py:39 terminal/serializers/storage.py:51
#: terminal/serializers/storage.py:81 terminal/serializers/storage.py:91
msgid "Endpoint"
msgstr "端点"
#: terminal/serializers/storage.py:66 xpack/plugins/cloud/models.py:209
#: terminal/serializers/storage.py:66 xpack/plugins/cloud/models.py:199
msgid "Region"
msgstr "地域"
@ -3595,7 +3605,7 @@ msgstr "文档类型"
msgid "Ignore Certificate Verification"
msgstr "忽略证书认证"
#: terminal/serializers/terminal.py:73 terminal/serializers/terminal.py:81
#: terminal/serializers/terminal.py:78 terminal/serializers/terminal.py:86
msgid "Not found"
msgstr "没有发现"
@ -4141,7 +4151,7 @@ msgid "Set password"
msgstr "设置密码"
#: users/serializers/user.py:27 xpack/plugins/change_auth_plan/models.py:61
#: xpack/plugins/change_auth_plan/serializers.py:30
#: xpack/plugins/change_auth_plan/serializers.py:29
msgid "Password strategy"
msgstr "密码策略"
@ -4773,7 +4783,7 @@ msgstr "重置密码成功,返回到登录页面"
#: xpack/plugins/change_auth_plan/meta.py:9
#: xpack/plugins/change_auth_plan/models.py:89
#: xpack/plugins/change_auth_plan/models.py:188
#: xpack/plugins/change_auth_plan/models.py:184
msgid "Change auth plan"
msgstr "改密计划"
@ -4793,73 +4803,60 @@ msgstr "所有资产使用不同的随机密码"
msgid "Password rules"
msgstr "密码规则"
#: xpack/plugins/change_auth_plan/models.py:176
msgid "Manual trigger"
msgstr "手动触发"
#: xpack/plugins/change_auth_plan/models.py:177
msgid "Timing trigger"
msgstr "定时触发"
#: xpack/plugins/change_auth_plan/models.py:191
#: xpack/plugins/change_auth_plan/models.py:187
msgid "Change auth plan snapshot"
msgstr "改密计划快照"
#: xpack/plugins/change_auth_plan/models.py:205
#: xpack/plugins/change_auth_plan/serializers.py:132
msgid "Trigger mode"
msgstr "触发模式"
#: xpack/plugins/change_auth_plan/models.py:210
#: xpack/plugins/change_auth_plan/models.py:304
#: xpack/plugins/change_auth_plan/models.py:202
#: xpack/plugins/change_auth_plan/models.py:296
msgid "Change auth plan execution"
msgstr "改密计划执行"
#: xpack/plugins/change_auth_plan/models.py:277
#: xpack/plugins/change_auth_plan/models.py:269
msgid "Ready"
msgstr ""
#: xpack/plugins/change_auth_plan/models.py:278
#: xpack/plugins/change_auth_plan/models.py:270
msgid "Preflight check"
msgstr ""
#: xpack/plugins/change_auth_plan/models.py:279
#: xpack/plugins/change_auth_plan/models.py:271
msgid "Change auth"
msgstr ""
#: xpack/plugins/change_auth_plan/models.py:280
#: xpack/plugins/change_auth_plan/models.py:272
msgid "Verify auth"
msgstr ""
#: xpack/plugins/change_auth_plan/models.py:281
#: xpack/plugins/change_auth_plan/models.py:273
msgid "Keep auth"
msgstr ""
#: xpack/plugins/change_auth_plan/models.py:282
#: xpack/plugins/change_auth_plan/models.py:274
msgid "Finished"
msgstr "结束"
#: xpack/plugins/change_auth_plan/models.py:308
#: xpack/plugins/change_auth_plan/models.py:300
msgid "Step"
msgstr "步骤"
#: xpack/plugins/change_auth_plan/models.py:325
#: xpack/plugins/change_auth_plan/models.py:317
msgid "Change auth plan task"
msgstr "改密计划任务"
#: xpack/plugins/change_auth_plan/serializers.py:56
#: xpack/plugins/change_auth_plan/serializers.py:52
msgid "Run times"
msgstr "执行次数"
#: xpack/plugins/change_auth_plan/serializers.py:72
#: xpack/plugins/change_auth_plan/serializers.py:68
msgid "* Please enter custom password"
msgstr "* 请输入自定义密码"
#: xpack/plugins/change_auth_plan/serializers.py:82
#: xpack/plugins/change_auth_plan/serializers.py:78
msgid "* Please enter the correct password length"
msgstr "* 请输入正确的密码长度"
#: xpack/plugins/change_auth_plan/serializers.py:85
#: xpack/plugins/change_auth_plan/serializers.py:81
msgid "* Password length range 6-30 bits"
msgstr "* 密码长度范围 6-30 位"
@ -4967,47 +4964,43 @@ msgstr "云服务商"
msgid "Cloud account"
msgstr "云账号"
#: xpack/plugins/cloud/models.py:82 xpack/plugins/cloud/serializers.py:204
#: xpack/plugins/cloud/models.py:78 xpack/plugins/cloud/serializers.py:178
msgid "Account"
msgstr "账户"
#: xpack/plugins/cloud/models.py:85 xpack/plugins/cloud/serializers.py:179
#: xpack/plugins/cloud/models.py:81 xpack/plugins/cloud/serializers.py:160
msgid "Regions"
msgstr "地域"
#: xpack/plugins/cloud/models.py:88
#: xpack/plugins/cloud/models.py:84
msgid "Hostname strategy"
msgstr "主机名策略"
#: xpack/plugins/cloud/models.py:102 xpack/plugins/cloud/serializers.py:186
msgid "IP network segment group"
msgstr "IP网段组"
#: xpack/plugins/cloud/models.py:105 xpack/plugins/cloud/serializers.py:208
#: xpack/plugins/cloud/models.py:95 xpack/plugins/cloud/serializers.py:182
msgid "Always update"
msgstr "总是更新"
#: xpack/plugins/cloud/models.py:111
#: xpack/plugins/cloud/models.py:101
msgid "Date last sync"
msgstr "最后同步日期"
#: xpack/plugins/cloud/models.py:122 xpack/plugins/cloud/models.py:163
#: xpack/plugins/cloud/models.py:112 xpack/plugins/cloud/models.py:153
msgid "Sync instance task"
msgstr "同步实例任务"
#: xpack/plugins/cloud/models.py:174 xpack/plugins/cloud/models.py:219
#: xpack/plugins/cloud/models.py:164 xpack/plugins/cloud/models.py:209
msgid "Date sync"
msgstr "同步日期"
#: xpack/plugins/cloud/models.py:199
#: xpack/plugins/cloud/models.py:189
msgid "Sync task"
msgstr "同步任务"
#: xpack/plugins/cloud/models.py:203
#: xpack/plugins/cloud/models.py:193
msgid "Sync instance task history"
msgstr "同步实例任务历史"
#: xpack/plugins/cloud/models.py:206
#: xpack/plugins/cloud/models.py:196
msgid "Instance"
msgstr "实例"
@ -5151,56 +5144,47 @@ msgstr "西南-贵阳1"
msgid "EU-Paris"
msgstr "欧洲-巴黎"
#: xpack/plugins/cloud/serializers.py:21
#: xpack/plugins/cloud/serializers.py:19
msgid "AccessKey ID"
msgstr ""
#: xpack/plugins/cloud/serializers.py:24
#: xpack/plugins/cloud/serializers.py:22
msgid "AccessKey Secret"
msgstr ""
#: xpack/plugins/cloud/serializers.py:30
#: xpack/plugins/cloud/serializers.py:28
msgid "Client ID"
msgstr ""
#: xpack/plugins/cloud/serializers.py:33
#: xpack/plugins/cloud/serializers.py:31
msgid "Client Secret"
msgstr ""
#: xpack/plugins/cloud/serializers.py:36
#: xpack/plugins/cloud/serializers.py:34
msgid "Tenant ID"
msgstr ""
#: xpack/plugins/cloud/serializers.py:39
#: xpack/plugins/cloud/serializers.py:37
msgid "Subscription ID"
msgstr ""
#: xpack/plugins/cloud/serializers.py:51
#: xpack/plugins/cloud/serializers.py:49
msgid "This field is required"
msgstr "这个字段是必填项"
#: xpack/plugins/cloud/serializers.py:85 xpack/plugins/cloud/serializers.py:89
#: xpack/plugins/cloud/serializers.py:83 xpack/plugins/cloud/serializers.py:87
msgid "API Endpoint"
msgstr "API 端点"
#: xpack/plugins/cloud/serializers.py:171
msgid ""
"The IP address that is first matched to will be used as the IP of the "
"created asset. <br>The default * indicates a random match. <br>Format for "
"comma-delimited string, Such as: 192.168.1.0/24, 10.1.1.1-10.1.1.20"
msgstr ""
"第一个匹配到的 IP 地址将被用作创建的资产的 IP。<br> 默认值 * 表示随机匹配。"
"<br> 格式为以逗号分隔的字符串,例如:192.168.1.0/24,10.1.1.1-10.1.1.20"
#: xpack/plugins/cloud/serializers.py:177
#: xpack/plugins/cloud/serializers.py:158
msgid "History count"
msgstr "执行次数"
#: xpack/plugins/cloud/serializers.py:178
#: xpack/plugins/cloud/serializers.py:159
msgid "Instance count"
msgstr "实例个数"
#: xpack/plugins/cloud/serializers.py:207
#: xpack/plugins/cloud/serializers.py:181
#: xpack/plugins/gathered_user/serializers.py:20
msgid "Periodic display"
msgstr "定时执行"
@ -5293,5 +5277,31 @@ msgstr "旗舰版"
msgid "Community edition"
msgstr "社区版"
#~ msgid "Application category"
#~ msgstr "应用类别"
#~ msgid "Application type"
#~ msgstr "应用类型"
#~ msgid "Manual trigger"
#~ msgstr "手动触发"
#~ msgid "Timing trigger"
#~ msgstr "定时触发"
#~ msgid "Trigger mode"
#~ msgstr "触发模式"
#~ msgid "IP network segment group"
#~ msgstr "IP网段组"
#~ msgid ""
#~ "The IP address that is first matched to will be used as the IP of the "
#~ "created asset. <br>The default * indicates a random match. <br>Format for "
#~ "comma-delimited string, Such as: 192.168.1.0/24, 10.1.1.1-10.1.1.20"
#~ msgstr ""
#~ "第一个匹配到的 IP 地址将被用作创建的资产的 IP。<br> 默认值 * 表示随机匹"
#~ "配。<br> 格式为以逗号分隔的字符串,例如:192.168.1.0/24,10.1.1.1-10.1.1.20"
#~ msgid "Trigger"
#~ msgstr "触发"

View File

@ -4,7 +4,7 @@ from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from common.drf.api import JmsGenericViewSet
from common.drf.api import JMSGenericViewSet
from notifications.notifications import system_msgs
from notifications.models import SystemMsgSubscription
from notifications.backends import BACKEND
@ -30,7 +30,7 @@ class BackendListView(APIView):
class SystemMsgSubscriptionViewSet(ListModelMixin,
UpdateModelMixin,
JmsGenericViewSet):
JMSGenericViewSet):
lookup_field = 'message_type'
queryset = SystemMsgSubscription.objects.all()
serializer_classes = {

View File

@ -5,7 +5,7 @@ from rest_framework.decorators import action
from common.http import is_true
from common.permissions import IsValidUser
from common.const.http import GET, PATCH, POST
from common.drf.api import JmsGenericViewSet
from common.drf.api import JMSGenericViewSet
from ..serializers import (
SiteMessageDetailSerializer, SiteMessageIdsSerializer,
SiteMessageSendSerializer,
@ -16,7 +16,7 @@ from ..filters import SiteMsgFilter
__all__ = ('SiteMessageViewSet', )
class SiteMessageViewSet(ListModelMixin, RetrieveModelMixin, JmsGenericViewSet):
class SiteMessageViewSet(ListModelMixin, RetrieveModelMixin, JMSGenericViewSet):
permission_classes = (IsValidUser,)
serializer_classes = {
'default': SiteMessageDetailSerializer,

View File

@ -9,6 +9,7 @@ from django.utils.translation import ugettext_lazy as _
from common.utils import lazyproperty, settings
from common.const import choices
from common.tree import TreeNode
from common.db.models import TextChoices
@ -233,6 +234,20 @@ class Organization(models.Model):
with tmp_to_org(self):
return resource_model.objects.all().count()
def as_tree_node(self, pid, opened=True):
node = TreeNode(**{
'id': self.id,
'name': self.name,
'title': self.name,
'pId': pid,
'open': opened,
'isParent': True,
'meta': {
'type': 'org'
}
})
return node
def _convert_to_uuid_set(users):
rst = set()

View File

@ -4,6 +4,7 @@ from rest_framework.generics import ListAPIView
from rest_framework.response import Response
from common.mixins.api import CommonApiMixin
from common.tree import TreeNodeSerializer
from applications.api.mixin import (
SerializeApplicationToTreeNodeMixin
)
@ -52,11 +53,13 @@ class ApplicationsAsTreeMixin(SerializeApplicationToTreeNodeMixin):
"""
将应用序列化成树的结构返回
"""
serializer_class = TreeNodeSerializer
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
data = self.serialize_applications_with_org(queryset)
return Response(data=data)
tree_nodes = self.serialize_applications_with_org(queryset)
serializer = self.get_serializer(tree_nodes, many=True)
return Response(data=serializer.data)
class UserAllGrantedApplicationsAsTreeApi(ApplicationsAsTreeMixin, UserAllGrantedApplicationsApi):

View File

@ -8,7 +8,7 @@ from django.utils.translation import ugettext_lazy as _
from common.utils import lazyproperty
from .base import BasePermission
from users.models import User
from applications.const import ApplicationCategoryChoices, ApplicationTypeChoices
from applications.const import AppCategory, AppType
__all__ = [
'ApplicationPermission',
@ -17,10 +17,10 @@ __all__ = [
class ApplicationPermission(BasePermission):
category = models.CharField(
max_length=16, choices=ApplicationCategoryChoices.choices, verbose_name=_('Category')
max_length=16, choices=AppCategory.choices, verbose_name=_('Category')
)
type = models.CharField(
max_length=16, choices=ApplicationTypeChoices.choices, verbose_name=_('Type')
max_length=16, choices=AppType.choices, verbose_name=_('Type')
)
applications = models.ManyToManyField(
'applications.Application', related_name='granted_by_permissions', blank=True,
@ -38,15 +38,15 @@ class ApplicationPermission(BasePermission):
@property
def category_remote_app(self):
return self.category == ApplicationCategoryChoices.remote_app.value
return self.category == AppCategory.remote_app.value
@property
def category_db(self):
return self.category == ApplicationCategoryChoices.db.value
return self.category == AppCategory.db.value
@property
def category_cloud(self):
return self.category == ApplicationCategoryChoices.cloud.value
return self.category == AppCategory.cloud.value
@lazyproperty
def users_amount(self):

View File

@ -7,7 +7,7 @@
<li id="applications">
<a>
<i class="fa fa-th" style="width: 14px"></i> <span class="nav-label">{% trans 'My Applications' %}</span><span class="fa arrow"></span>
<i class="fa fa-th" style="width: 14px"></i> <span class="nav-label">{% trans 'My applications' %}</span><span class="fa arrow"></span>
</a>
<ul class="nav nav-second-level">
{% if LICENSE_VALID %}

View File

@ -1,7 +1,7 @@
from django.utils.translation import ugettext as _
from orgs.utils import tmp_to_org, tmp_to_root_org
from applications.models import Application
from applications.const import ApplicationCategoryChoices, ApplicationTypeChoices
from applications.const import AppCategory, AppType
from assets.models import SystemUser
from perms.models import ApplicationPermission
from .base import BaseHandler
@ -17,9 +17,9 @@ class Handler(BaseHandler):
def _construct_meta_display_of_open(self):
meta_display_fields = ['apply_category_display', 'apply_type_display']
apply_category = self.ticket.meta.get('apply_category')
apply_category_display = ApplicationCategoryChoices.get_label(apply_category)
apply_category_display = AppCategory.get_label(apply_category)
apply_type = self.ticket.meta.get('apply_type')
apply_type_display = ApplicationTypeChoices.get_label(apply_type)
apply_type_display = AppType.get_label(apply_type)
meta_display_values = [apply_category_display, apply_type_display]
meta_display = dict(zip(meta_display_fields, meta_display_values))
return meta_display

View File

@ -3,7 +3,7 @@ from django.utils.translation import ugettext_lazy as _
from django.db.models import Q
from perms.models import ApplicationPermission
from applications.models import Application
from applications.const import ApplicationCategoryChoices, ApplicationTypeChoices
from applications.const import AppCategory, AppType
from assets.models import SystemUser
from orgs.utils import tmp_to_org
from tickets.models import Ticket
@ -17,14 +17,14 @@ __all__ = [
class ApplySerializer(serializers.Serializer):
# 申请信息
apply_category = serializers.ChoiceField(
required=True, choices=ApplicationCategoryChoices.choices, label=_('Category'),
required=True, choices=AppCategory.choices, label=_('Category'),
allow_null=True,
)
apply_category_display = serializers.CharField(
read_only=True, label=_('Category display'), allow_null=True,
)
apply_type = serializers.ChoiceField(
required=True, choices=ApplicationTypeChoices.choices, label=_('Type'),
required=True, choices=AppType.choices, label=_('Type'),
allow_null=True
)
apply_type_display = serializers.CharField(