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 import *
from .application_user import * from .account import *
from .mixin import * from .mixin import *
from .remote_app 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 # 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 ..hands import IsOrgAdminOrAppUser
from .. import serializers from .. import serializers
from ..models import Application from ..models import Application
@ -19,4 +23,15 @@ class ApplicationViewSet(OrgBulkModelViewSet):
} }
search_fields = ('name', 'type', 'category') search_fields = ('name', 'type', 'category')
permission_classes = (IsOrgAdminOrAppUser,) 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'] __all__ = ['SerializeApplicationToTreeNodeMixin']
class 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 @staticmethod
def filter_organizations(applications): def filter_organizations(applications):
organization_ids = set(applications.values_list('org_id', flat=True)) organization_ids = set(applications.values_list('org_id', flat=True))
organizations = [Organization.get_instance(org_id) for org_id in organization_ids] organizations = [Organization.get_instance(org_id) for org_id in organization_ids]
return organizations 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): def serialize_applications_with_org(self, applications):
root_node = self.create_root_node()
tree_nodes = [root_node]
organizations = self.filter_organizations(applications) organizations = self.filter_organizations(applications)
data_organizations = self.serialize_organizations(organizations)
data_applications = self.serialize_applications(applications) for i, org in enumerate(organizations):
data = data_organizations + data_applications # 组织节点
return data 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 # coding: utf-8
# #
from django.db.models import TextChoices from django.db.models import TextChoices
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
class ApplicationCategoryChoices(TextChoices): class AppCategory(TextChoices):
db = 'db', _('Database') db = 'db', _('Database')
remote_app = 'remote_app', _('Remote app') remote_app = 'remote_app', _('Remote app')
cloud = 'cloud', 'Cloud' cloud = 'cloud', 'Cloud'
@ -15,7 +14,7 @@ class ApplicationCategoryChoices(TextChoices):
return dict(cls.choices).get(category, '') return dict(cls.choices).get(category, '')
class ApplicationTypeChoices(TextChoices): class AppType(TextChoices):
# db category # db category
mysql = 'mysql', 'MySQL' mysql = 'mysql', 'MySQL'
oracle = 'oracle', 'Oracle' oracle = 'oracle', 'Oracle'
@ -31,19 +30,38 @@ class ApplicationTypeChoices(TextChoices):
# cloud category # cloud category
k8s = 'k8s', 'Kubernetes' 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 @classmethod
def get_label(cls, tp): def get_label(cls, tp):
return dict(cls.choices).get(tp, '') return dict(cls.choices).get(tp, '')
@classmethod @classmethod
def db_types(cls): 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 @classmethod
def remote_app_types(cls): 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 @classmethod
def cloud_types(cls): 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 import models
from django.db.models import Count
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
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 assets.models import Asset, SystemUser from assets.models import Asset, SystemUser
from .. import const 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')) name = models.CharField(max_length=128, verbose_name=_('Name'))
category = models.CharField( 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( 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( domain = models.ForeignKey(
'assets.Domain', null=True, blank=True, related_name='applications', 'assets.Domain', null=True, blank=True, related_name='applications',
@ -35,7 +186,7 @@ class Application(CommonModelMixin, OrgModelMixin):
@property @property
def category_remote_app(self): 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): def get_rdp_remote_app_setting(self):
from applications.serializers.attrs import get_serializer_class_by_application_type 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 orgs.mixins.serializers import BulkOrgResourceModelSerializer
from common.drf.serializers import MethodSerializer from common.drf.serializers import MethodSerializer
from .attrs import category_serializer_classes_mapping, type_serializer_classes_mapping from .attrs import category_serializer_classes_mapping, type_serializer_classes_mapping
from assets.serializers import SystemUserSerializer
from .. import models from .. import models
from .. import const
__all__ = [ __all__ = [
'ApplicationSerializer', 'ApplicationSerializerMixin', 'ApplicationSerializer', 'ApplicationSerializerMixin',
'ApplicationUserSerializer', 'ApplicationUserWithAuthInfoSerializer' 'ApplicationAccountSerializer', 'ApplicationAccountSecretSerializer'
] ]
@ -52,9 +52,8 @@ class ApplicationSerializer(ApplicationSerializerMixin, BulkOrgResourceModelSeri
model = models.Application model = models.Application
fields_mini = ['id', 'name'] fields_mini = ['id', 'name']
fields_small = fields_mini + [ fields_small = fields_mini + [
'category', 'category_display', 'type', 'type_display', 'attrs', 'category', 'category_display', 'type', 'type_display',
'date_created', 'date_updated', 'attrs', 'date_created', 'date_updated', 'created_by', 'comment'
'created_by', 'comment'
] ]
fields_fk = ['domain'] fields_fk = ['domain']
fields = fields_small + fields_fk fields = fields_small + fields_fk
@ -68,41 +67,34 @@ class ApplicationSerializer(ApplicationSerializerMixin, BulkOrgResourceModelSeri
return _attrs return _attrs
class ApplicationUserSerializer(SystemUserSerializer): class ApplicationAccountSerializer(serializers.Serializer):
application_name = serializers.SerializerMethodField(label=_('Application name')) username = serializers.ReadOnlyField(label=_("Username"))
application_category = serializers.SerializerMethodField(label=_('Application category')) password = serializers.CharField(write_only=True, label=_("Password"))
application_type = serializers.SerializerMethodField(label=_('Application type')) 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): category_mapper = dict(const.AppCategory.choices)
model = models.ApplicationUser type_mapper = dict(const.AppType.choices)
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},
}
@property def create(self, validated_data):
def application(self): pass
return self.context['application']
def get_application_name(self, obj): def update(self, instance, validated_data):
return self.application.name pass
def get_application_category(self, obj): def get_app_category_display(self, obj):
return self.application.get_category_display() return self.category_mapper.get(obj['app_category'])
def get_application_type(self, obj): def get_app_type_display(self, obj):
return self.application.get_type_display() return self.type_mapper.get(obj['app_type'])
class ApplicationUserWithAuthInfoSerializer(ApplicationUserSerializer): class ApplicationAccountSecretSerializer(ApplicationAccountSerializer):
password = serializers.CharField(write_only=False, label=_("Password"))
class Meta(ApplicationUserSerializer.Meta):
fields = ApplicationUserSerializer.Meta.fields + ['password', 'token']

View File

@ -14,9 +14,9 @@ __all__ = [
# --------------------------------------------------- # ---------------------------------------------------
category_serializer_classes_mapping = { category_serializer_classes_mapping = {
const.ApplicationCategoryChoices.db.value: application_category.DBSerializer, const.AppCategory.db.value: application_category.DBSerializer,
const.ApplicationCategoryChoices.remote_app.value: application_category.RemoteAppSerializer, const.AppCategory.remote_app.value: application_category.RemoteAppSerializer,
const.ApplicationCategoryChoices.cloud.value: application_category.CloudSerializer, const.AppCategory.cloud.value: application_category.CloudSerializer,
} }
# define `attrs` field `type serializers mapping` # define `attrs` field `type serializers mapping`
@ -24,17 +24,17 @@ category_serializer_classes_mapping = {
type_serializer_classes_mapping = { type_serializer_classes_mapping = {
# db # db
const.ApplicationTypeChoices.mysql.value: application_type.MySQLSerializer, const.AppType.mysql.value: application_type.MySQLSerializer,
const.ApplicationTypeChoices.mariadb.value: application_type.MariaDBSerializer, const.AppType.mariadb.value: application_type.MariaDBSerializer,
const.ApplicationTypeChoices.oracle.value: application_type.OracleSerializer, const.AppType.oracle.value: application_type.OracleSerializer,
const.ApplicationTypeChoices.pgsql.value: application_type.PostgreSerializer, const.AppType.pgsql.value: application_type.PostgreSerializer,
# remote-app # remote-app
const.ApplicationTypeChoices.chrome.value: application_type.ChromeSerializer, const.AppType.chrome.value: application_type.ChromeSerializer,
const.ApplicationTypeChoices.mysql_workbench.value: application_type.MySQLWorkbenchSerializer, const.AppType.mysql_workbench.value: application_type.MySQLWorkbenchSerializer,
const.ApplicationTypeChoices.vmware_client.value: application_type.VMwareClientSerializer, const.AppType.vmware_client.value: application_type.VMwareClientSerializer,
const.ApplicationTypeChoices.custom.value: application_type.CustomSerializer, const.AppType.custom.value: application_type.CustomSerializer,
# cloud # 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 = BulkRouter()
router.register(r'applications', api.ApplicationViewSet, 'application') 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 = [ urlpatterns = [
path('remote-apps/<uuid:pk>/connection-info/', api.RemoteAppConnectionInfoApi.as_view(), name='remote-app-connection-info'), 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('accounts/', api.ApplicationAccountViewSet.as_view(), name='application-account'),
path('application-user-auth-infos/', api.ApplicationUserAuthInfoListApi.as_view(), name='application-user-auth-info') # 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.db.models import F, Q
from django.conf import settings
from rest_framework.decorators import action from rest_framework.decorators import action
from django_filters import rest_framework as filters from django_filters import rest_framework as filters
from rest_framework.response import Response from rest_framework.response import Response
@ -87,11 +86,6 @@ class AccountSecretsViewSet(AccountViewSet):
permission_classes = (IsOrgAdmin, NeedMFAVerify) permission_classes = (IsOrgAdmin, NeedMFAVerify)
http_method_names = ['get'] 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): class AccountTaskCreateAPI(CreateAPIView):
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)

View File

@ -60,10 +60,10 @@ class ProtocolMixin:
@classmethod @classmethod
def get_protocol_by_application_type(cls, app_type): 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: if app_type in cls.APPLICATION_CATEGORY_PROTOCOLS:
protocol = app_type protocol = app_type
elif app_type in ApplicationTypeChoices.remote_app_types(): elif app_type in AppType.remote_app_types():
protocol = cls.Protocol.rdp protocol = cls.Protocol.rdp
else: else:
protocol = None protocol = None

View File

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

View File

@ -11,7 +11,7 @@ from rest_framework.permissions import AllowAny
from common.utils.timezone import utcnow from common.utils.timezone import utcnow
from common.const.http import POST, GET 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.drf.serializers import EmptySerializer
from common.permissions import IsSuperUser from common.permissions import IsSuperUser
from common.utils import reverse from common.utils import reverse
@ -26,7 +26,7 @@ NEXT_URL = 'next'
AUTH_KEY = 'authkey' AUTH_KEY = 'authkey'
class SSOViewSet(AuthMixin, JmsGenericViewSet): class SSOViewSet(AuthMixin, JMSGenericViewSet):
queryset = SSOToken.objects.all() queryset = SSOToken.objects.all()
serializer_classes = { serializer_classes = {
'login_url': SSOTokenSerializer, '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 rest_framework_bulk import BulkModelViewSet
from ..mixins.api import ( from ..mixins.api import (
@ -15,19 +15,23 @@ class CommonMixin(SerializerMixin,
pass pass
class JmsGenericViewSet(CommonMixin, class JMSGenericViewSet(CommonMixin, GenericViewSet):
GenericViewSet):
pass pass
class JMSModelViewSet(CommonMixin, class JMSViewSet(CommonMixin, ViewSet):
ModelViewSet):
pass pass
class JMSBulkModelViewSet(CommonMixin, class JMSModelViewSet(CommonMixin, ModelViewSet):
AllowBulkDestroyMixin, pass
BulkModelViewSet):
class JMSReadOnlyModelViewSet(CommonMixin, ReadOnlyModelViewSet):
pass
class JMSBulkModelViewSet(CommonMixin, AllowBulkDestroyMixin, BulkModelViewSet):
pass pass

View File

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

View File

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

View File

@ -273,3 +273,17 @@ def bulk_get(d, *keys, default=None):
for key in keys: for key in keys:
values.append(d.get(key, default)) values.append(d.get(key, default))
return values 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: try:
response = proxy_view(request, remote_url) response = proxy_view(request, remote_url)
except Exception as e: 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) '<br><br> <div>{}</div>'.format(e)
response = HttpResponse(msg) response = HttpResponse(msg)
return response return response

View File

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

View File

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

View File

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

View File

@ -9,6 +9,7 @@ from django.utils.translation import ugettext_lazy as _
from common.utils import lazyproperty, settings from common.utils import lazyproperty, settings
from common.const import choices from common.const import choices
from common.tree import TreeNode
from common.db.models import TextChoices from common.db.models import TextChoices
@ -233,6 +234,20 @@ class Organization(models.Model):
with tmp_to_org(self): with tmp_to_org(self):
return resource_model.objects.all().count() 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): def _convert_to_uuid_set(users):
rst = set() rst = set()

View File

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

View File

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

View File

@ -7,7 +7,7 @@
<li id="applications"> <li id="applications">
<a> <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> </a>
<ul class="nav nav-second-level"> <ul class="nav nav-second-level">
{% if LICENSE_VALID %} {% if LICENSE_VALID %}

View File

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

View File

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