From c5f6c564a7d6c1e0f223621c95f23b835703f3c6 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Thu, 21 Oct 2021 13:02:27 +0800 Subject: [PATCH 01/24] =?UTF-8?q?fix:=20=E7=B3=BB=E7=BB=9F=E5=90=AF?= =?UTF-8?q?=E5=8A=A8bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/models/user.py | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 90b6b1976..4c14f83cc 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -17,7 +17,6 @@ from django.utils.translation import ugettext_lazy as _ from django.utils import timezone from django.shortcuts import reverse -from acls.models import LoginACL from orgs.utils import current_org from orgs.models import OrganizationMember, Organization from common.exceptions import JMSException From 526928518de86f0b4e64c16dd3c6e6dbbeee6e6c Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Thu, 21 Oct 2021 15:00:29 +0800 Subject: [PATCH 02/24] perf: acl filter --- apps/acls/filters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/acls/filters.py b/apps/acls/filters.py index 23cd0bc61..f5d1c73b3 100644 --- a/apps/acls/filters.py +++ b/apps/acls/filters.py @@ -11,5 +11,5 @@ class LoginAclFilter(BaseFilterSet): class Meta: model = LoginACL fields = ( - 'name', 'user', 'user_display' + 'name', 'user', 'user_display', 'action' ) From 8c118b6f47f80821a6d61ad9425f9d2b234209a0 Mon Sep 17 00:00:00 2001 From: xinwen Date: Thu, 21 Oct 2021 15:10:16 +0800 Subject: [PATCH 03/24] =?UTF-8?q?fix:=20=E7=BB=84=E7=BB=87=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E6=8A=A5=E9=94=99=E6=9C=AA=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/orgs/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/orgs/api.py b/apps/orgs/api.py index 14ad9cb20..bc07a6833 100644 --- a/apps/orgs/api.py +++ b/apps/orgs/api.py @@ -74,7 +74,7 @@ class OrgViewSet(BulkModelViewSet): def perform_destroy(self, instance): if str(current_org) == str(instance): - msg = _('The current organization ({}) cannot be deleted'.format(current_org)) + msg = _('The current organization ({}) cannot be deleted').format(current_org) raise PermissionDenied(detail=msg) for model in org_related_models: From a1221a39fd33d073a947ee887edabecf22d9d8d4 Mon Sep 17 00:00:00 2001 From: xinwen Date: Thu, 21 Oct 2021 11:33:06 +0800 Subject: [PATCH 04/24] =?UTF-8?q?fix:=20=E4=BC=9A=E8=AF=9D=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/serializers/session.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/terminal/serializers/session.py b/apps/terminal/serializers/session.py index fdf130210..50b0f9ada 100644 --- a/apps/terminal/serializers/session.py +++ b/apps/terminal/serializers/session.py @@ -42,13 +42,10 @@ class SessionSerializer(BulkOrgResourceModelSerializer): class SessionDisplaySerializer(SessionSerializer): - command_amount = serializers.IntegerField(read_only=True) + command_amount = serializers.IntegerField(read_only=True, label=_('Command amount')) class Meta(SessionSerializer.Meta): fields = SessionSerializer.Meta.fields + ['command_amount'] - extra_kwargs = { - 'command_amount': {'label': _('Command amount')}, - } class ReplaySerializer(serializers.Serializer): From a1f1dce56b3e9c039700845d3997a5b831988edd Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 20 Oct 2021 20:25:21 +0800 Subject: [PATCH 05/24] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/terminal/_msg_command_execute_alert.html | 6 +++--- apps/tickets/notifications.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/terminal/templates/terminal/_msg_command_execute_alert.html b/apps/terminal/templates/terminal/_msg_command_execute_alert.html index fcea490c6..e9136c6a1 100644 --- a/apps/terminal/templates/terminal/_msg_command_execute_alert.html +++ b/apps/terminal/templates/terminal/_msg_command_execute_alert.html @@ -8,9 +8,9 @@

{% trans 'Command' %}:
-
-    {{ command }}
-    
+
+{{ command }}
+
{% trans 'Assets' %}:
diff --git a/apps/tickets/notifications.py b/apps/tickets/notifications.py index 22e3477c6..a8f3a5417 100644 --- a/apps/tickets/notifications.py +++ b/apps/tickets/notifications.py @@ -39,7 +39,7 @@ class BaseTicketMessage(UserMessage): @classmethod def gen_test_msg(cls): - return cls(None) + return None class TicketAppliedToAssignee(BaseTicketMessage): From a78b2f4b62f99742ebb89ef7e6e5033f1880def1 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Thu, 21 Oct 2021 15:45:10 +0800 Subject: [PATCH 06/24] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=B7=A5?= =?UTF-8?q?=E5=8D=95=E5=8F=91=E6=B6=88=E6=81=AF=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/tickets/handler/apply_application.py | 4 ++-- apps/tickets/handler/apply_asset.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/tickets/handler/apply_application.py b/apps/tickets/handler/apply_application.py index 988f70855..61ef64258 100644 --- a/apps/tickets/handler/apply_application.py +++ b/apps/tickets/handler/apply_application.py @@ -53,8 +53,8 @@ class Handler(BaseHandler): _('Applied type'), apply_type_display, _('Applied application group'), ','.join(apply_applications), _('Applied system user group'), ','.join(apply_system_users), - _('Applied date start'), apply_date_start.strftime('%Y-%m-%d %H:%M:%S'), - _('Applied date expired'), apply_date_expired.strftime('%Y-%m-%d %H:%M:%S'), + _('Applied date start'), apply_date_start, + _('Applied date expired'), apply_date_expired, ) return applied_body diff --git a/apps/tickets/handler/apply_asset.py b/apps/tickets/handler/apply_asset.py index 3f35998c1..584f3a12b 100644 --- a/apps/tickets/handler/apply_asset.py +++ b/apps/tickets/handler/apply_asset.py @@ -47,8 +47,8 @@ class Handler(BaseHandler): _("Applied hostname group"), ','.join(apply_assets), _("Applied system user group"), ','.join(apply_system_users), _("Applied actions"), ','.join(apply_actions_display), - _('Applied date start'), apply_date_start.strftime('%Y-%m-%d %H:%M:%S'), - _('Applied date expired'), apply_date_expired.strftime('%Y-%m-%d %H:%M:%S'), + _('Applied date start'), apply_date_start, + _('Applied date expired'), apply_date_expired, ) return applied_body From 487c945d1d5fec5571524047a69e5b1ccf594b0e Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 21 Oct 2021 16:16:50 +0800 Subject: [PATCH 07/24] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E4=BD=8D=E7=BD=AE=EF=BC=8C=E7=94=A8=E6=88=B7sugestion?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=88=B06=E4=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/applications/api/application.py | 2 +- apps/assets/api/asset.py | 2 +- apps/assets/api/system_user.py | 2 +- apps/common/mixins/api.py | 347 --------------------------- apps/common/mixins/api/__init__.py | 7 + apps/common/mixins/api/action.py | 55 +++++ apps/common/mixins/api/common.py | 30 +++ apps/common/mixins/api/filter.py | 35 +++ apps/common/mixins/api/patch.py | 136 +++++++++++ apps/common/mixins/api/permission.py | 37 +++ apps/common/mixins/api/queryset.py | 14 ++ apps/common/mixins/api/serializer.py | 95 ++++++++ apps/common/mixins/views.py | 53 +--- apps/users/api/user.py | 9 +- 14 files changed, 420 insertions(+), 404 deletions(-) delete mode 100644 apps/common/mixins/api.py create mode 100644 apps/common/mixins/api/__init__.py create mode 100644 apps/common/mixins/api/action.py create mode 100644 apps/common/mixins/api/common.py create mode 100644 apps/common/mixins/api/filter.py create mode 100644 apps/common/mixins/api/patch.py create mode 100644 apps/common/mixins/api/permission.py create mode 100644 apps/common/mixins/api/queryset.py create mode 100644 apps/common/mixins/api/serializer.py diff --git a/apps/applications/api/application.py b/apps/applications/api/application.py index 245379630..9428df39f 100644 --- a/apps/applications/api/application.py +++ b/apps/applications/api/application.py @@ -6,7 +6,7 @@ from rest_framework.decorators import action from rest_framework.response import Response from common.tree import TreeNodeSerializer -from common.mixins.views import SuggestionMixin +from common.mixins.api import SuggestionMixin from ..hands import IsOrgAdminOrAppUser from .. import serializers from ..models import Application diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index 79229e832..410020d39 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -8,7 +8,7 @@ from django.db.models import Q from common.utils import get_logger, get_object_or_none from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser, IsSuperUser -from common.mixins.views import SuggestionMixin +from common.mixins.api import SuggestionMixin from users.models import User, UserGroup from users.serializers import UserSerializer, UserGroupSerializer from users.filters import UserFilter diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py index 908ab87b9..f03f2e7d4 100644 --- a/apps/assets/api/system_user.py +++ b/apps/assets/api/system_user.py @@ -6,7 +6,7 @@ from common.utils import get_logger from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser, IsValidUser from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins import generics -from common.mixins.views import SuggestionMixin +from common.mixins.api import SuggestionMixin from orgs.utils import tmp_to_root_org from ..models import SystemUser, Asset from .. import serializers diff --git a/apps/common/mixins/api.py b/apps/common/mixins/api.py deleted file mode 100644 index caef88127..000000000 --- a/apps/common/mixins/api.py +++ /dev/null @@ -1,347 +0,0 @@ -# -*- coding: utf-8 -*- -# -import time -from hashlib import md5 -from threading import Thread -from collections import defaultdict -from itertools import chain - -from django.conf import settings -from django.db.models.signals import m2m_changed -from django.core.cache import cache -from django.http import JsonResponse -from django.utils.translation import ugettext as _ -from django.contrib.auth import get_user_model -from rest_framework.response import Response -from rest_framework.settings import api_settings -from rest_framework.decorators import action -from rest_framework.request import Request - -from common.const.http import POST -from common.drf.filters import IDSpmFilter, CustomFilter, IDInFilter -from ..utils import lazyproperty - -__all__ = [ - 'JSONResponseMixin', 'CommonApiMixin', 'AsyncApiMixin', 'RelationMixin', - 'QuerySetMixin', 'ExtraFilterFieldsMixin', 'RenderToJsonMixin', - 'SerializerMixin', 'AllowBulkDestroyMixin', 'PaginatedResponseMixin' -] - - -UserModel = get_user_model() - - -class JSONResponseMixin(object): - """JSON mixin""" - @staticmethod - def render_json_response(context): - return JsonResponse(context) - - -# SerializerMixin -# ---------------------- - - -class RenderToJsonMixin: - @action(methods=[POST], detail=False, url_path='render-to-json') - def render_to_json(self, request: Request): - data = { - 'title': (), - 'data': request.data, - } - - jms_context = getattr(request, 'jms_context', {}) - column_title_field_pairs = jms_context.get('column_title_field_pairs', ()) - data['title'] = column_title_field_pairs - - if isinstance(request.data, (list, tuple)) and not any(request.data): - error = _("Request file format may be wrong") - return Response(data={"error": error}, status=400) - return Response(data=data) - - -class SerializerMixin: - """ 根据用户请求动作的不同,获取不同的 `serializer_class `""" - - action: str - request: Request - - serializer_classes = None - single_actions = ['put', 'retrieve', 'patch'] - - def get_serializer_class_by_view_action(self): - if not hasattr(self, 'serializer_classes'): - return None - if not isinstance(self.serializer_classes, dict): - return None - - view_action = self.request.query_params.get('action') or self.action or 'list' - serializer_class = self.serializer_classes.get(view_action) - - if serializer_class is None: - view_method = self.request.method.lower() - serializer_class = self.serializer_classes.get(view_method) - - if serializer_class is None and view_action in self.single_actions: - serializer_class = self.serializer_classes.get('single') - if serializer_class is None: - serializer_class = self.serializer_classes.get('display') - if serializer_class is None: - serializer_class = self.serializer_classes.get('default') - return serializer_class - - def get_serializer_class(self): - serializer_class = self.get_serializer_class_by_view_action() - if serializer_class is None: - serializer_class = super().get_serializer_class() - return serializer_class - - -class ExtraFilterFieldsMixin: - """ - 额外的 api filter - """ - default_added_filters = [CustomFilter, IDSpmFilter, IDInFilter] - filter_backends = api_settings.DEFAULT_FILTER_BACKENDS - extra_filter_fields = [] - extra_filter_backends = [] - - def get_filter_backends(self): - if self.filter_backends != self.__class__.filter_backends: - return self.filter_backends - backends = list(chain( - self.filter_backends, - self.default_added_filters, - self.extra_filter_backends - )) - return backends - - def filter_queryset(self, queryset): - for backend in self.get_filter_backends(): - queryset = backend().filter_queryset(self.request, queryset, self) - return queryset - - -class PaginatedResponseMixin: - def get_paginated_response_with_query_set(self, queryset): - page = self.paginate_queryset(queryset) - if page is not None: - serializer = self.get_serializer(page, many=True) - return self.get_paginated_response(serializer.data) - - serializer = self.get_serializer(queryset, many=True) - return Response(serializer.data) - - -class CommonApiMixin(SerializerMixin, ExtraFilterFieldsMixin, RenderToJsonMixin): - pass - - -class InterceptMixin: - """ - Hack默认的dispatch, 让用户可以实现 self.do - """ - def dispatch(self, request, *args, **kwargs): - self.args = args - self.kwargs = kwargs - request = self.initialize_request(request, *args, **kwargs) - self.request = request - self.headers = self.default_response_headers # deprecate? - - try: - self.initial(request, *args, **kwargs) - - # Get the appropriate handler method - if request.method.lower() in self.http_method_names: - handler = getattr(self, request.method.lower(), - self.http_method_not_allowed) - else: - handler = self.http_method_not_allowed - - response = self.do(handler, request, *args, **kwargs) - - except Exception as exc: - response = self.handle_exception(exc) - - self.response = self.finalize_response(request, response, *args, **kwargs) - return self.response - - -class AsyncApiMixin(InterceptMixin): - def get_request_user_id(self): - user = self.request.user - if hasattr(user, 'id'): - return str(user.id) - return '' - - @lazyproperty - def async_cache_key(self): - method = self.request.method - path = self.get_request_md5() - user = self.get_request_user_id() - key = '{}_{}_{}'.format(method, path, user) - return key - - def get_request_md5(self): - path = self.request.path - query = {k: v for k, v in self.request.GET.items()} - query.pop("_", None) - query.pop('refresh', None) - query = "&".join(["{}={}".format(k, v) for k, v in query.items()]) - full_path = "{}?{}".format(path, query) - return md5(full_path.encode()).hexdigest() - - @lazyproperty - def initial_data(self): - data = { - "status": "running", - "start_time": time.time(), - "key": self.async_cache_key, - } - return data - - def get_cache_data(self): - key = self.async_cache_key - if self.is_need_refresh(): - cache.delete(key) - return None - data = cache.get(key) - return data - - def do(self, handler, *args, **kwargs): - if not self.is_need_async(): - return handler(*args, **kwargs) - resp = self.do_async(handler, *args, **kwargs) - return resp - - def is_need_refresh(self): - if self.request.GET.get("refresh"): - return True - return False - - def is_need_async(self): - return False - - def do_async(self, handler, *args, **kwargs): - data = self.get_cache_data() - if not data: - t = Thread( - target=self.do_in_thread, - args=(handler, *args), - kwargs=kwargs - ) - t.start() - resp = Response(self.initial_data) - return resp - status = data.get("status") - resp = data.get("resp") - if status == "ok" and resp: - resp = Response(**resp) - else: - resp = Response(data) - return resp - - def do_in_thread(self, handler, *args, **kwargs): - key = self.async_cache_key - data = self.initial_data - cache.set(key, data, 600) - try: - response = handler(*args, **kwargs) - data["status"] = "ok" - data["resp"] = { - "data": response.data, - "status": response.status_code - } - cache.set(key, data, 600) - except Exception as e: - data["error"] = str(e) - data["status"] = "error" - cache.set(key, data, 600) - - -class RelationMixin: - m2m_field = None - from_field = None - to_field = None - to_model = None - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - assert self.m2m_field is not None, ''' - `m2m_field` should not be `None` - ''' - - self.from_field = self.m2m_field.m2m_field_name() - self.to_field = self.m2m_field.m2m_reverse_field_name() - self.to_model = self.m2m_field.related_model - self.through = getattr(self.m2m_field.model, self.m2m_field.attname).through - - def get_queryset(self): - # 注意,此处拦截了 `get_queryset` 没有 `super` - queryset = self.through.objects.all() - return queryset - - def send_m2m_changed_signal(self, instances, action): - if not isinstance(instances, list): - instances = [instances] - - from_to_mapper = defaultdict(list) - - for i in instances: - to_id = getattr(i, self.to_field).id - # TODO 优化,不应该每次都查询数据库 - from_obj = getattr(i, self.from_field) - from_to_mapper[from_obj].append(to_id) - - for from_obj, to_ids in from_to_mapper.items(): - m2m_changed.send( - sender=self.through, instance=from_obj, action=action, - reverse=False, model=self.to_model, pk_set=to_ids - ) - - def perform_create(self, serializer): - instance = serializer.save() - self.send_m2m_changed_signal(instance, 'post_add') - - def perform_destroy(self, instance): - instance.delete() - self.send_m2m_changed_signal(instance, 'post_remove') - - -class QuerySetMixin: - def get_queryset(self): - queryset = super().get_queryset() - serializer_class = self.get_serializer_class() - - if serializer_class and hasattr(serializer_class, 'setup_eager_loading'): - queryset = serializer_class.setup_eager_loading(queryset) - - return queryset - - -class AllowBulkDestroyMixin: - def allow_bulk_destroy(self, qs, filtered): - """ - 我们规定,批量删除的情况必须用 `id` 指定要删除的数据。 - """ - query = str(filtered.query) - return '`id` IN (' in query or '`id` =' in query - - -class RoleAdminMixin: - kwargs: dict - user_id_url_kwarg = 'pk' - - @lazyproperty - def user(self): - user_id = self.kwargs.get(self.user_id_url_kwarg) - return UserModel.objects.get(id=user_id) - - -class RoleUserMixin: - request: Request - - @lazyproperty - def user(self): - return self.request.user diff --git a/apps/common/mixins/api/__init__.py b/apps/common/mixins/api/__init__.py new file mode 100644 index 000000000..a5827ffef --- /dev/null +++ b/apps/common/mixins/api/__init__.py @@ -0,0 +1,7 @@ +from .common import * +from .action import * +from .patch import * +from .filter import * +from .permission import * +from .queryset import * +from .serializer import * diff --git a/apps/common/mixins/api/action.py b/apps/common/mixins/api/action.py new file mode 100644 index 000000000..994ade06b --- /dev/null +++ b/apps/common/mixins/api/action.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# +from typing import Callable + +from django.utils.translation import ugettext as _ +from rest_framework.response import Response +from rest_framework.decorators import action +from rest_framework.request import Request + +from common.const.http import POST +from common.permissions import IsValidUser + + +__all__ = ['SuggestionMixin', 'RenderToJsonMixin'] + + +class SuggestionMixin: + suggestion_limit = 10 + + filter_queryset: Callable + get_queryset: Callable + paginate_queryset: Callable + get_serializer: Callable + get_paginated_response: Callable + + @action(methods=['get'], detail=False, permission_classes=(IsValidUser,)) + def suggestions(self, request, *args, **kwargs): + queryset = self.filter_queryset(self.get_queryset()) + queryset = queryset[:self.suggestion_limit] + page = self.paginate_queryset(queryset) + + if page is not None: + serializer = self.get_serializer(page, many=True) + return self.get_paginated_response(serializer.data) + + serializer = self.get_serializer(queryset, many=True) + return Response(serializer.data) + + +class RenderToJsonMixin: + @action(methods=[POST], detail=False, url_path='render-to-json') + def render_to_json(self, request: Request): + data = { + 'title': (), + 'data': request.data, + } + + jms_context = getattr(request, 'jms_context', {}) + column_title_field_pairs = jms_context.get('column_title_field_pairs', ()) + data['title'] = column_title_field_pairs + + if isinstance(request.data, (list, tuple)) and not any(request.data): + error = _("Request file format may be wrong") + return Response(data={"error": error}, status=400) + return Response(data=data) diff --git a/apps/common/mixins/api/common.py b/apps/common/mixins/api/common.py new file mode 100644 index 000000000..ba3895356 --- /dev/null +++ b/apps/common/mixins/api/common.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# +from rest_framework.response import Response + +from .serializer import SerializerMixin +from .filter import ExtraFilterFieldsMixin +from .action import RenderToJsonMixin + +__all__ = [ + 'CommonApiMixin', 'PaginatedResponseMixin', +] + + +class PaginatedResponseMixin: + def get_paginated_response_with_query_set(self, queryset): + page = self.paginate_queryset(queryset) + if page is not None: + serializer = self.get_serializer(page, many=True) + return self.get_paginated_response(serializer.data) + + serializer = self.get_serializer(queryset, many=True) + return Response(serializer.data) + + +class CommonApiMixin(SerializerMixin, ExtraFilterFieldsMixin, RenderToJsonMixin): + pass + + + + diff --git a/apps/common/mixins/api/filter.py b/apps/common/mixins/api/filter.py new file mode 100644 index 000000000..1d1451b66 --- /dev/null +++ b/apps/common/mixins/api/filter.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# +from itertools import chain + +from rest_framework.settings import api_settings + +from common.drf.filters import IDSpmFilter, CustomFilter, IDInFilter + + +__all__ = ['ExtraFilterFieldsMixin'] + + +class ExtraFilterFieldsMixin: + """ + 额外的 api filter + """ + default_added_filters = [CustomFilter, IDSpmFilter, IDInFilter] + filter_backends = api_settings.DEFAULT_FILTER_BACKENDS + extra_filter_fields = [] + extra_filter_backends = [] + + def get_filter_backends(self): + if self.filter_backends != self.__class__.filter_backends: + return self.filter_backends + backends = list(chain( + self.filter_backends, + self.default_added_filters, + self.extra_filter_backends + )) + return backends + + def filter_queryset(self, queryset): + for backend in self.get_filter_backends(): + queryset = backend().filter_queryset(self.request, queryset, self) + return queryset diff --git a/apps/common/mixins/api/patch.py b/apps/common/mixins/api/patch.py new file mode 100644 index 000000000..f79957546 --- /dev/null +++ b/apps/common/mixins/api/patch.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +# +import time +from hashlib import md5 +from threading import Thread + +from django.core.cache import cache +from rest_framework.response import Response + +from common.utils import lazyproperty + + +__all__ = ['InterceptMixin', 'AsyncApiMixin'] + + +class InterceptMixin: + """ + Hack默认的dispatch, 让用户可以实现 self.do + """ + def dispatch(self, request, *args, **kwargs): + self.args = args + self.kwargs = kwargs + request = self.initialize_request(request, *args, **kwargs) + self.request = request + self.headers = self.default_response_headers # deprecate? + + try: + self.initial(request, *args, **kwargs) + + # Get the appropriate handler method + if request.method.lower() in self.http_method_names: + handler = getattr(self, request.method.lower(), + self.http_method_not_allowed) + else: + handler = self.http_method_not_allowed + + response = self.do(handler, request, *args, **kwargs) + + except Exception as exc: + response = self.handle_exception(exc) + + self.response = self.finalize_response(request, response, *args, **kwargs) + return self.response + + +class AsyncApiMixin(InterceptMixin): + def get_request_user_id(self): + user = self.request.user + if hasattr(user, 'id'): + return str(user.id) + return '' + + @lazyproperty + def async_cache_key(self): + method = self.request.method + path = self.get_request_md5() + user = self.get_request_user_id() + key = '{}_{}_{}'.format(method, path, user) + return key + + def get_request_md5(self): + path = self.request.path + query = {k: v for k, v in self.request.GET.items()} + query.pop("_", None) + query.pop('refresh', None) + query = "&".join(["{}={}".format(k, v) for k, v in query.items()]) + full_path = "{}?{}".format(path, query) + return md5(full_path.encode()).hexdigest() + + @lazyproperty + def initial_data(self): + data = { + "status": "running", + "start_time": time.time(), + "key": self.async_cache_key, + } + return data + + def get_cache_data(self): + key = self.async_cache_key + if self.is_need_refresh(): + cache.delete(key) + return None + data = cache.get(key) + return data + + def do(self, handler, *args, **kwargs): + if not self.is_need_async(): + return handler(*args, **kwargs) + resp = self.do_async(handler, *args, **kwargs) + return resp + + def is_need_refresh(self): + if self.request.GET.get("refresh"): + return True + return False + + def is_need_async(self): + return False + + def do_async(self, handler, *args, **kwargs): + data = self.get_cache_data() + if not data: + t = Thread( + target=self.do_in_thread, + args=(handler, *args), + kwargs=kwargs + ) + t.start() + resp = Response(self.initial_data) + return resp + status = data.get("status") + resp = data.get("resp") + if status == "ok" and resp: + resp = Response(**resp) + else: + resp = Response(data) + return resp + + def do_in_thread(self, handler, *args, **kwargs): + key = self.async_cache_key + data = self.initial_data + cache.set(key, data, 600) + try: + response = handler(*args, **kwargs) + data["status"] = "ok" + data["resp"] = { + "data": response.data, + "status": response.status_code + } + cache.set(key, data, 600) + except Exception as e: + data["error"] = str(e) + data["status"] = "error" + cache.set(key, data, 600) + diff --git a/apps/common/mixins/api/permission.py b/apps/common/mixins/api/permission.py new file mode 100644 index 000000000..6ffa15e53 --- /dev/null +++ b/apps/common/mixins/api/permission.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# +from django.contrib.auth import get_user_model +from rest_framework.request import Request + +from common.utils import lazyproperty + + +__all__ = ['AllowBulkDestroyMixin', 'RoleAdminMixin', 'RoleUserMixin'] + + +class AllowBulkDestroyMixin: + def allow_bulk_destroy(self, qs, filtered): + """ + 我们规定,批量删除的情况必须用 `id` 指定要删除的数据。 + """ + query = str(filtered.query) + return '`id` IN (' in query or '`id` =' in query + + +class RoleAdminMixin: + kwargs: dict + user_id_url_kwarg = 'pk' + + @lazyproperty + def user(self): + user_id = self.kwargs.get(self.user_id_url_kwarg) + user_model = get_user_model() + return user_model.objects.get(id=user_id) + + +class RoleUserMixin: + request: Request + + @lazyproperty + def user(self): + return self.request.user \ No newline at end of file diff --git a/apps/common/mixins/api/queryset.py b/apps/common/mixins/api/queryset.py new file mode 100644 index 000000000..4f56e8a51 --- /dev/null +++ b/apps/common/mixins/api/queryset.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# + +__all__ = ['QuerySetMixin'] + + +class QuerySetMixin: + def get_queryset(self): + queryset = super().get_queryset() + serializer_class = self.get_serializer_class() + + if serializer_class and hasattr(serializer_class, 'setup_eager_loading'): + queryset = serializer_class.setup_eager_loading(queryset) + return queryset diff --git a/apps/common/mixins/api/serializer.py b/apps/common/mixins/api/serializer.py new file mode 100644 index 000000000..c5c9b4737 --- /dev/null +++ b/apps/common/mixins/api/serializer.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +# +from collections import defaultdict + +from django.db.models.signals import m2m_changed +from rest_framework.request import Request + +__all__ = ['SerializerMixin', 'RelationMixin'] + + +class SerializerMixin: + """ 根据用户请求动作的不同,获取不同的 `serializer_class `""" + + action: str + request: Request + + serializer_classes = None + single_actions = ['put', 'retrieve', 'patch'] + + def get_serializer_class_by_view_action(self): + if not hasattr(self, 'serializer_classes'): + return None + if not isinstance(self.serializer_classes, dict): + return None + + view_action = self.request.query_params.get('action') or self.action or 'list' + serializer_class = self.serializer_classes.get(view_action) + + if serializer_class is None: + view_method = self.request.method.lower() + serializer_class = self.serializer_classes.get(view_method) + + if serializer_class is None and view_action in self.single_actions: + serializer_class = self.serializer_classes.get('single') + if serializer_class is None: + serializer_class = self.serializer_classes.get('display') + if serializer_class is None: + serializer_class = self.serializer_classes.get('default') + return serializer_class + + def get_serializer_class(self): + serializer_class = self.get_serializer_class_by_view_action() + if serializer_class is None: + serializer_class = super().get_serializer_class() + return serializer_class + + +class RelationMixin: + m2m_field = None + from_field = None + to_field = None + to_model = None + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + assert self.m2m_field is not None, ''' + `m2m_field` should not be `None` + ''' + + self.from_field = self.m2m_field.m2m_field_name() + self.to_field = self.m2m_field.m2m_reverse_field_name() + self.to_model = self.m2m_field.related_model + self.through = getattr(self.m2m_field.model, self.m2m_field.attname).through + + def get_queryset(self): + # 注意,此处拦截了 `get_queryset` 没有 `super` + queryset = self.through.objects.all() + return queryset + + def send_m2m_changed_signal(self, instances, action): + if not isinstance(instances, list): + instances = [instances] + + from_to_mapper = defaultdict(list) + + for i in instances: + to_id = getattr(i, self.to_field).id + # TODO 优化,不应该每次都查询数据库 + from_obj = getattr(i, self.from_field) + from_to_mapper[from_obj].append(to_id) + + for from_obj, to_ids in from_to_mapper.items(): + m2m_changed.send( + sender=self.through, instance=from_obj, action=action, + reverse=False, model=self.to_model, pk_set=to_ids + ) + + def perform_create(self, serializer): + instance = serializer.save() + self.send_m2m_changed_signal(instance, 'post_add') + + def perform_destroy(self, instance): + instance.delete() + self.send_m2m_changed_signal(instance, 'post_remove') diff --git a/apps/common/mixins/views.py b/apps/common/mixins/views.py index a4bc32b76..f167d2001 100644 --- a/apps/common/mixins/views.py +++ b/apps/common/mixins/views.py @@ -1,49 +1,16 @@ # -*- coding: utf-8 -*- # -# coding: utf-8 from django.contrib.auth.mixins import UserPassesTestMixin -from django.utils import timezone -from rest_framework.decorators import action from rest_framework import permissions -from rest_framework.response import Response - -from common.permissions import IsValidUser - -__all__ = ["DatetimeSearchMixin", "PermissionsMixin"] +from rest_framework.request import Request -class DatetimeSearchMixin: - date_format = '%Y-%m-%d' - date_from = date_to = None - - def get_date_range(self): - date_from_s = self.request.GET.get('date_from') - date_to_s = self.request.GET.get('date_to') - - if date_from_s: - date_from = timezone.datetime.strptime(date_from_s, self.date_format) - tz = timezone.get_current_timezone() - self.date_from = tz.localize(date_from) - else: - self.date_from = timezone.now() - timezone.timedelta(7) - - if date_to_s: - date_to = timezone.datetime.strptime( - date_to_s + ' 23:59:59', self.date_format + ' %H:%M:%S' - ) - self.date_to = date_to.replace( - tzinfo=timezone.get_current_timezone() - ) - else: - self.date_to = timezone.now() - - def get(self, request, *args, **kwargs): - self.get_date_range() - return super().get(request, *args, **kwargs) +__all__ = ["PermissionsMixin"] class PermissionsMixin(UserPassesTestMixin): permission_classes = [permissions.IsAuthenticated] + request: Request def get_permissions(self): return self.permission_classes @@ -56,17 +23,3 @@ class PermissionsMixin(UserPassesTestMixin): return True -class SuggestionMixin: - suggestion_mini_count = 10 - - @action(methods=['get'], detail=False, permission_classes=(IsValidUser,)) - def suggestions(self, request, *args, **kwargs): - queryset = self.filter_queryset(self.get_queryset()) - queryset = queryset[:self.suggestion_mini_count] - page = self.paginate_queryset(queryset) - if page is not None: - serializer = self.get_serializer(page, many=True) - return self.get_paginated_response(serializer.data) - - serializer = self.get_serializer(queryset, many=True) - return Response(serializer.data) diff --git a/apps/users/api/user.py b/apps/users/api/user.py index d72f04f73..a1b808a22 100644 --- a/apps/users/api/user.py +++ b/apps/users/api/user.py @@ -8,7 +8,6 @@ from rest_framework.response import Response from rest_framework_bulk import BulkModelViewSet from django.db.models import Prefetch -from users.notifications import ResetMFAMsg from common.permissions import ( IsOrgAdmin, IsOrgAdminOrAppUser, CanUpdateDeleteUser, IsSuperUser @@ -18,9 +17,10 @@ from common.utils import get_logger from orgs.utils import current_org from orgs.models import ROLE as ORG_ROLE, OrganizationMember from users.utils import LoginBlockUtil, MFABlockUtils +from .mixins import UserQuerysetMixin +from ..notifications import ResetMFAMsg from .. import serializers from ..serializers import UserSerializer, MiniUserSerializer, InviteSerializer -from .mixins import UserQuerysetMixin from ..models import User from ..signals import post_user_create from ..filters import OrgRoleUserFilterBackend, UserFilter @@ -128,9 +128,9 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet): return super().perform_bulk_update(serializer) @action(methods=['get'], detail=False, permission_classes=(IsOrgAdmin,)) - def suggestion(self, request): + def suggestion(self, *args, **kwargs): queryset = User.objects.exclude(role=User.ROLE.APP) - queryset = self.filter_queryset(queryset)[:3] + queryset = self.filter_queryset(queryset)[:6] serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @@ -206,6 +206,7 @@ class UserResetOTPApi(UserQuerysetMixin, generics.RetrieveAPIView): if user == request.user: msg = _("Could not reset self otp, use profile reset instead") return Response({"error": msg}, status=401) + if user.mfa_enabled: user.reset_mfa() user.save() From d5c9ec1c3d0258413e1324bb26743db9c52d34dc Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 21 Oct 2021 16:39:52 +0800 Subject: [PATCH 08/24] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E4=BD=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/drf/api.py | 11 +---- apps/common/mixins/api/common.py | 70 +++++++++++++++++++++++++++- apps/common/mixins/api/serializer.py | 54 +-------------------- 3 files changed, 70 insertions(+), 65 deletions(-) diff --git a/apps/common/drf/api.py b/apps/common/drf/api.py index fddd37939..073e9fd7e 100644 --- a/apps/common/drf/api.py +++ b/apps/common/drf/api.py @@ -2,19 +2,10 @@ from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelV from rest_framework_bulk import BulkModelViewSet from ..mixins.api import ( - SerializerMixin, QuerySetMixin, ExtraFilterFieldsMixin, PaginatedResponseMixin, - RelationMixin, AllowBulkDestroyMixin, RenderToJsonMixin, + RelationMixin, AllowBulkDestroyMixin, CommonMixin ) -class CommonMixin(SerializerMixin, - QuerySetMixin, - ExtraFilterFieldsMixin, - PaginatedResponseMixin, - RenderToJsonMixin): - pass - - class JMSGenericViewSet(CommonMixin, GenericViewSet): pass diff --git a/apps/common/mixins/api/common.py b/apps/common/mixins/api/common.py index ba3895356..8dbf4fb1e 100644 --- a/apps/common/mixins/api/common.py +++ b/apps/common/mixins/api/common.py @@ -1,18 +1,28 @@ # -*- coding: utf-8 -*- # +from typing import Callable from rest_framework.response import Response +from collections import defaultdict + +from django.db.models.signals import m2m_changed from .serializer import SerializerMixin from .filter import ExtraFilterFieldsMixin from .action import RenderToJsonMixin +from .queryset import QuerySetMixin + __all__ = [ - 'CommonApiMixin', 'PaginatedResponseMixin', + 'CommonApiMixin', 'PaginatedResponseMixin', 'RelationMixin', 'CommonMixin' ] class PaginatedResponseMixin: - def get_paginated_response_with_query_set(self, queryset): + paginate_queryset: Callable + get_serializer: Callable + get_paginated_response: Callable + + def get_paginated_response_from_queryset(self, queryset): page = self.paginate_queryset(queryset) if page is not None: serializer = self.get_serializer(page, many=True) @@ -22,9 +32,65 @@ class PaginatedResponseMixin: return Response(serializer.data) +class RelationMixin: + m2m_field = None + from_field = None + to_field = None + to_model = None + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + assert self.m2m_field is not None, ''' + `m2m_field` should not be `None` + ''' + + self.from_field = self.m2m_field.m2m_field_name() + self.to_field = self.m2m_field.m2m_reverse_field_name() + self.to_model = self.m2m_field.related_model + self.through = getattr(self.m2m_field.model, self.m2m_field.attname).through + + def get_queryset(self): + # 注意,此处拦截了 `get_queryset` 没有 `super` + queryset = self.through.objects.all() + return queryset + + def send_m2m_changed_signal(self, instances, action): + if not isinstance(instances, list): + instances = [instances] + + from_to_mapper = defaultdict(list) + + for i in instances: + to_id = getattr(i, self.to_field).id + # TODO 优化,不应该每次都查询数据库 + from_obj = getattr(i, self.from_field) + from_to_mapper[from_obj].append(to_id) + + for from_obj, to_ids in from_to_mapper.items(): + m2m_changed.send( + sender=self.through, instance=from_obj, action=action, + reverse=False, model=self.to_model, pk_set=to_ids + ) + + def perform_create(self, serializer): + instance = serializer.save() + self.send_m2m_changed_signal(instance, 'post_add') + + def perform_destroy(self, instance): + instance.delete() + self.send_m2m_changed_signal(instance, 'post_remove') + + class CommonApiMixin(SerializerMixin, ExtraFilterFieldsMixin, RenderToJsonMixin): pass +class CommonMixin(SerializerMixin, + QuerySetMixin, + ExtraFilterFieldsMixin, + RenderToJsonMixin): + pass + diff --git a/apps/common/mixins/api/serializer.py b/apps/common/mixins/api/serializer.py index c5c9b4737..52b0637df 100644 --- a/apps/common/mixins/api/serializer.py +++ b/apps/common/mixins/api/serializer.py @@ -1,11 +1,9 @@ # -*- coding: utf-8 -*- # -from collections import defaultdict -from django.db.models.signals import m2m_changed from rest_framework.request import Request -__all__ = ['SerializerMixin', 'RelationMixin'] +__all__ = ['SerializerMixin'] class SerializerMixin: @@ -43,53 +41,3 @@ class SerializerMixin: if serializer_class is None: serializer_class = super().get_serializer_class() return serializer_class - - -class RelationMixin: - m2m_field = None - from_field = None - to_field = None - to_model = None - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - assert self.m2m_field is not None, ''' - `m2m_field` should not be `None` - ''' - - self.from_field = self.m2m_field.m2m_field_name() - self.to_field = self.m2m_field.m2m_reverse_field_name() - self.to_model = self.m2m_field.related_model - self.through = getattr(self.m2m_field.model, self.m2m_field.attname).through - - def get_queryset(self): - # 注意,此处拦截了 `get_queryset` 没有 `super` - queryset = self.through.objects.all() - return queryset - - def send_m2m_changed_signal(self, instances, action): - if not isinstance(instances, list): - instances = [instances] - - from_to_mapper = defaultdict(list) - - for i in instances: - to_id = getattr(i, self.to_field).id - # TODO 优化,不应该每次都查询数据库 - from_obj = getattr(i, self.from_field) - from_to_mapper[from_obj].append(to_id) - - for from_obj, to_ids in from_to_mapper.items(): - m2m_changed.send( - sender=self.through, instance=from_obj, action=action, - reverse=False, model=self.to_model, pk_set=to_ids - ) - - def perform_create(self, serializer): - instance = serializer.save() - self.send_m2m_changed_signal(instance, 'post_add') - - def perform_destroy(self, instance): - instance.delete() - self.send_m2m_changed_signal(instance, 'post_remove') From 072865f3e5eb79a7515e64290bb4144d547207fb Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 21 Oct 2021 16:50:11 +0800 Subject: [PATCH 09/24] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=20sdk=20=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/sms_verify_code.py | 2 +- apps/authentication/views/dingtalk.py | 4 ++-- apps/authentication/views/feishu.py | 2 +- apps/authentication/views/wecom.py | 4 ++-- apps/common/{message => sdk}/__init__.py | 0 apps/common/{message/backends => sdk/im}/__init__.py | 0 apps/common/{message/backends => sdk/im}/dingtalk/__init__.py | 4 ++-- apps/common/{message/backends => sdk/im}/exceptions.py | 0 apps/common/{message/backends => sdk/im}/feishu/__init__.py | 4 ++-- apps/common/{message/backends => sdk/im}/mixin.py | 2 +- apps/common/{message/backends => sdk/im}/sms/__init__.py | 0 apps/common/{message/backends => sdk/im}/sms/alibaba.py | 0 apps/common/{message/backends => sdk/im}/sms/tencent.py | 0 apps/common/{message/backends => sdk/im}/utils.py | 2 +- apps/common/{message/backends => sdk/im}/wecom/__init__.py | 4 ++-- apps/notifications/backends/dingtalk.py | 2 +- apps/notifications/backends/feishu.py | 2 +- apps/notifications/backends/sms.py | 2 +- apps/notifications/backends/wecom.py | 2 +- apps/settings/api/alibaba_sms.py | 2 +- apps/settings/api/dingtalk.py | 2 +- apps/settings/api/feishu.py | 2 +- apps/settings/api/sms.py | 2 +- apps/settings/api/tencent_sms.py | 2 +- apps/settings/api/wecom.py | 2 +- apps/settings/serializers/auth/sms.py | 2 +- 26 files changed, 25 insertions(+), 25 deletions(-) rename apps/common/{message => sdk}/__init__.py (100%) rename apps/common/{message/backends => sdk/im}/__init__.py (100%) rename apps/common/{message/backends => sdk/im}/dingtalk/__init__.py (98%) rename apps/common/{message/backends => sdk/im}/exceptions.py (100%) rename apps/common/{message/backends => sdk/im}/feishu/__init__.py (96%) rename apps/common/{message/backends => sdk/im}/mixin.py (98%) rename apps/common/{message/backends => sdk/im}/sms/__init__.py (100%) rename apps/common/{message/backends => sdk/im}/sms/alibaba.py (100%) rename apps/common/{message/backends => sdk/im}/sms/tencent.py (100%) rename apps/common/{message/backends => sdk/im}/utils.py (97%) rename apps/common/{message/backends => sdk/im}/wecom/__init__.py (97%) diff --git a/apps/authentication/sms_verify_code.py b/apps/authentication/sms_verify_code.py index 4236d9c0d..4fd5c7fa2 100644 --- a/apps/authentication/sms_verify_code.py +++ b/apps/authentication/sms_verify_code.py @@ -3,7 +3,7 @@ import random from django.core.cache import cache from django.utils.translation import gettext_lazy as _ -from common.message.backends.sms import SMS +from common.sdk.im.sms import SMS from common.utils import get_logger from common.exceptions import JMSException diff --git a/apps/authentication/views/dingtalk.py b/apps/authentication/views/dingtalk.py index 630125151..70bb9211d 100644 --- a/apps/authentication/views/dingtalk.py +++ b/apps/authentication/views/dingtalk.py @@ -14,11 +14,11 @@ from users.models import User from common.utils import get_logger, FlashMessageUtil from common.utils.random import random_string from common.utils.django import reverse, get_object_or_none -from common.message.backends.dingtalk import URL +from common.sdk.im.dingtalk import URL from common.mixins.views import PermissionsMixin from authentication import errors from authentication.mixins import AuthMixin -from common.message.backends.dingtalk import DingTalk +from common.sdk.im.dingtalk import DingTalk logger = get_logger(__file__) diff --git a/apps/authentication/views/feishu.py b/apps/authentication/views/feishu.py index 19f9f0bb3..597b83e46 100644 --- a/apps/authentication/views/feishu.py +++ b/apps/authentication/views/feishu.py @@ -15,7 +15,7 @@ from common.utils import get_logger, FlashMessageUtil from common.utils.random import random_string from common.utils.django import reverse, get_object_or_none from common.mixins.views import PermissionsMixin -from common.message.backends.feishu import FeiShu, URL +from common.sdk.im.feishu import FeiShu, URL from authentication import errors from authentication.mixins import AuthMixin diff --git a/apps/authentication/views/wecom.py b/apps/authentication/views/wecom.py index c3e9068e5..793479ccc 100644 --- a/apps/authentication/views/wecom.py +++ b/apps/authentication/views/wecom.py @@ -14,8 +14,8 @@ from users.models import User from common.utils import get_logger, FlashMessageUtil from common.utils.random import random_string from common.utils.django import reverse, get_object_or_none -from common.message.backends.wecom import URL -from common.message.backends.wecom import WeCom +from common.sdk.im.wecom import URL +from common.sdk.im.wecom import WeCom from common.mixins.views import PermissionsMixin from authentication import errors from authentication.mixins import AuthMixin diff --git a/apps/common/message/__init__.py b/apps/common/sdk/__init__.py similarity index 100% rename from apps/common/message/__init__.py rename to apps/common/sdk/__init__.py diff --git a/apps/common/message/backends/__init__.py b/apps/common/sdk/im/__init__.py similarity index 100% rename from apps/common/message/backends/__init__.py rename to apps/common/sdk/im/__init__.py diff --git a/apps/common/message/backends/dingtalk/__init__.py b/apps/common/sdk/im/dingtalk/__init__.py similarity index 98% rename from apps/common/message/backends/dingtalk/__init__.py rename to apps/common/sdk/im/dingtalk/__init__.py index 3f07ba2a0..bd802a8d5 100644 --- a/apps/common/message/backends/dingtalk/__init__.py +++ b/apps/common/sdk/im/dingtalk/__init__.py @@ -3,8 +3,8 @@ import hmac import base64 from common.utils import get_logger -from common.message.backends.utils import digest, as_request -from common.message.backends.mixin import BaseRequest +from common.sdk.im.utils import digest, as_request +from common.sdk.im.mixin import BaseRequest logger = get_logger(__file__) diff --git a/apps/common/message/backends/exceptions.py b/apps/common/sdk/im/exceptions.py similarity index 100% rename from apps/common/message/backends/exceptions.py rename to apps/common/sdk/im/exceptions.py diff --git a/apps/common/message/backends/feishu/__init__.py b/apps/common/sdk/im/feishu/__init__.py similarity index 96% rename from apps/common/message/backends/feishu/__init__.py rename to apps/common/sdk/im/feishu/__init__.py index f9f1ee0e4..1cfee9cdd 100644 --- a/apps/common/message/backends/feishu/__init__.py +++ b/apps/common/sdk/im/feishu/__init__.py @@ -4,8 +4,8 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework.exceptions import APIException from common.utils.common import get_logger -from common.message.backends.utils import digest -from common.message.backends.mixin import RequestMixin, BaseRequest +from common.sdk.im.utils import digest +from common.sdk.im.mixin import RequestMixin, BaseRequest logger = get_logger(__name__) diff --git a/apps/common/message/backends/mixin.py b/apps/common/sdk/im/mixin.py similarity index 98% rename from apps/common/message/backends/mixin.py rename to apps/common/sdk/im/mixin.py index af151a536..94e4ca73c 100644 --- a/apps/common/message/backends/mixin.py +++ b/apps/common/sdk/im/mixin.py @@ -6,7 +6,7 @@ from django.core.cache import cache from .utils import DictWrapper from common.utils.common import get_logger from common.utils import lazyproperty -from common.message.backends.utils import set_default, as_request +from common.sdk.im.utils import set_default, as_request from . import exceptions as exce diff --git a/apps/common/message/backends/sms/__init__.py b/apps/common/sdk/im/sms/__init__.py similarity index 100% rename from apps/common/message/backends/sms/__init__.py rename to apps/common/sdk/im/sms/__init__.py diff --git a/apps/common/message/backends/sms/alibaba.py b/apps/common/sdk/im/sms/alibaba.py similarity index 100% rename from apps/common/message/backends/sms/alibaba.py rename to apps/common/sdk/im/sms/alibaba.py diff --git a/apps/common/message/backends/sms/tencent.py b/apps/common/sdk/im/sms/tencent.py similarity index 100% rename from apps/common/message/backends/sms/tencent.py rename to apps/common/sdk/im/sms/tencent.py diff --git a/apps/common/message/backends/utils.py b/apps/common/sdk/im/utils.py similarity index 97% rename from apps/common/message/backends/utils.py rename to apps/common/sdk/im/utils.py index 45bea6d94..2386d0eff 100644 --- a/apps/common/message/backends/utils.py +++ b/apps/common/sdk/im/utils.py @@ -3,7 +3,7 @@ import inspect from inspect import Parameter from common.utils.common import get_logger -from common.message.backends import exceptions as exce +from common.sdk.im import exceptions as exce logger = get_logger(__name__) diff --git a/apps/common/message/backends/wecom/__init__.py b/apps/common/sdk/im/wecom/__init__.py similarity index 97% rename from apps/common/message/backends/wecom/__init__.py rename to apps/common/sdk/im/wecom/__init__.py index 730ebcbf6..7640228e9 100644 --- a/apps/common/message/backends/wecom/__init__.py +++ b/apps/common/sdk/im/wecom/__init__.py @@ -4,8 +4,8 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework.exceptions import APIException from common.utils.common import get_logger -from common.message.backends.utils import digest, DictWrapper, update_values, set_default -from common.message.backends.mixin import RequestMixin, BaseRequest +from common.sdk.im.utils import digest, DictWrapper, update_values, set_default +from common.sdk.im.mixin import RequestMixin, BaseRequest logger = get_logger(__name__) diff --git a/apps/notifications/backends/dingtalk.py b/apps/notifications/backends/dingtalk.py index ba72091a4..426a05fc0 100644 --- a/apps/notifications/backends/dingtalk.py +++ b/apps/notifications/backends/dingtalk.py @@ -1,5 +1,5 @@ from django.conf import settings -from common.message.backends.dingtalk import DingTalk as Client +from common.sdk.im.dingtalk import DingTalk as Client from .base import BackendBase diff --git a/apps/notifications/backends/feishu.py b/apps/notifications/backends/feishu.py index 434898c8a..659c77591 100644 --- a/apps/notifications/backends/feishu.py +++ b/apps/notifications/backends/feishu.py @@ -1,6 +1,6 @@ from django.conf import settings -from common.message.backends.feishu import FeiShu as Client +from common.sdk.im.feishu import FeiShu as Client from .base import BackendBase diff --git a/apps/notifications/backends/sms.py b/apps/notifications/backends/sms.py index 77a720a04..798711f77 100644 --- a/apps/notifications/backends/sms.py +++ b/apps/notifications/backends/sms.py @@ -1,6 +1,6 @@ from django.conf import settings -from common.message.backends.sms.alibaba import AlibabaSMS as Client +from common.sdk.im.sms.alibaba import AlibabaSMS as Client from .base import BackendBase diff --git a/apps/notifications/backends/wecom.py b/apps/notifications/backends/wecom.py index 988c904c2..c2aadd4a9 100644 --- a/apps/notifications/backends/wecom.py +++ b/apps/notifications/backends/wecom.py @@ -1,6 +1,6 @@ from django.conf import settings -from common.message.backends.wecom import WeCom as Client +from common.sdk.im.wecom import WeCom as Client from .base import BackendBase diff --git a/apps/settings/api/alibaba_sms.py b/apps/settings/api/alibaba_sms.py index 32460778e..cf905d68f 100644 --- a/apps/settings/api/alibaba_sms.py +++ b/apps/settings/api/alibaba_sms.py @@ -4,7 +4,7 @@ from rest_framework.exceptions import APIException from rest_framework import status from django.utils.translation import gettext_lazy as _ -from common.message.backends.sms.alibaba import AlibabaSMS +from common.sdk.im.sms.alibaba import AlibabaSMS from settings.models import Setting from common.permissions import IsSuperUser from common.exceptions import JMSException diff --git a/apps/settings/api/dingtalk.py b/apps/settings/api/dingtalk.py index abb28e9da..8164198de 100644 --- a/apps/settings/api/dingtalk.py +++ b/apps/settings/api/dingtalk.py @@ -6,7 +6,7 @@ from django.utils.translation import gettext_lazy as _ from django.conf import settings from common.permissions import IsSuperUser -from common.message.backends.dingtalk import DingTalk +from common.sdk.im.dingtalk import DingTalk from .. import serializers diff --git a/apps/settings/api/feishu.py b/apps/settings/api/feishu.py index 3e3d720b1..e9b3a391a 100644 --- a/apps/settings/api/feishu.py +++ b/apps/settings/api/feishu.py @@ -6,7 +6,7 @@ from django.utils.translation import gettext_lazy as _ from settings.models import Setting from common.permissions import IsSuperUser -from common.message.backends.feishu import FeiShu +from common.sdk.im.feishu import FeiShu from .. import serializers diff --git a/apps/settings/api/sms.py b/apps/settings/api/sms.py index 194dbc608..4809e9ddc 100644 --- a/apps/settings/api/sms.py +++ b/apps/settings/api/sms.py @@ -2,7 +2,7 @@ from rest_framework.generics import ListAPIView from rest_framework.response import Response from common.permissions import IsSuperUser -from common.message.backends.sms import BACKENDS +from common.sdk.im.sms import BACKENDS from settings.serializers.sms import SMSBackendSerializer diff --git a/apps/settings/api/tencent_sms.py b/apps/settings/api/tencent_sms.py index 5395a1305..0b4cdd69e 100644 --- a/apps/settings/api/tencent_sms.py +++ b/apps/settings/api/tencent_sms.py @@ -6,7 +6,7 @@ from rest_framework.exceptions import APIException from rest_framework import status from django.utils.translation import gettext_lazy as _ -from common.message.backends.sms.tencent import TencentSMS +from common.sdk.im.sms.tencent import TencentSMS from settings.models import Setting from common.permissions import IsSuperUser from common.exceptions import JMSException diff --git a/apps/settings/api/wecom.py b/apps/settings/api/wecom.py index 39d1576b5..fce5ddad5 100644 --- a/apps/settings/api/wecom.py +++ b/apps/settings/api/wecom.py @@ -6,7 +6,7 @@ from django.utils.translation import gettext_lazy as _ from settings.models import Setting from common.permissions import IsSuperUser -from common.message.backends.wecom import WeCom +from common.sdk.im.wecom import WeCom from .. import serializers diff --git a/apps/settings/serializers/auth/sms.py b/apps/settings/serializers/auth/sms.py index 8320b11eb..808141726 100644 --- a/apps/settings/serializers/auth/sms.py +++ b/apps/settings/serializers/auth/sms.py @@ -1,7 +1,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from common.message.backends.sms import BACKENDS +from common.sdk.im.sms import BACKENDS __all__ = ['SMSSettingSerializer', 'AlibabaSMSSettingSerializer', 'TencentSMSSettingSerializer'] From 25ea3ba01d29758adda232d825bab2d41fc9c751 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 21 Oct 2021 17:08:17 +0800 Subject: [PATCH 10/24] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9sdk=E4=BD=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/sms_verify_code.py | 2 +- apps/common/sdk/{im => }/sms/__init__.py | 0 apps/common/sdk/{im => }/sms/alibaba.py | 0 apps/common/sdk/{im => }/sms/tencent.py | 0 apps/notifications/backends/sms.py | 2 +- apps/settings/api/alibaba_sms.py | 2 +- apps/settings/api/sms.py | 2 +- apps/settings/api/tencent_sms.py | 2 +- apps/settings/serializers/auth/sms.py | 2 +- 9 files changed, 6 insertions(+), 6 deletions(-) rename apps/common/sdk/{im => }/sms/__init__.py (100%) rename apps/common/sdk/{im => }/sms/alibaba.py (100%) rename apps/common/sdk/{im => }/sms/tencent.py (100%) diff --git a/apps/authentication/sms_verify_code.py b/apps/authentication/sms_verify_code.py index 4fd5c7fa2..56bc6ac78 100644 --- a/apps/authentication/sms_verify_code.py +++ b/apps/authentication/sms_verify_code.py @@ -3,7 +3,7 @@ import random from django.core.cache import cache from django.utils.translation import gettext_lazy as _ -from common.sdk.im.sms import SMS +from common.sdk.sms import SMS from common.utils import get_logger from common.exceptions import JMSException diff --git a/apps/common/sdk/im/sms/__init__.py b/apps/common/sdk/sms/__init__.py similarity index 100% rename from apps/common/sdk/im/sms/__init__.py rename to apps/common/sdk/sms/__init__.py diff --git a/apps/common/sdk/im/sms/alibaba.py b/apps/common/sdk/sms/alibaba.py similarity index 100% rename from apps/common/sdk/im/sms/alibaba.py rename to apps/common/sdk/sms/alibaba.py diff --git a/apps/common/sdk/im/sms/tencent.py b/apps/common/sdk/sms/tencent.py similarity index 100% rename from apps/common/sdk/im/sms/tencent.py rename to apps/common/sdk/sms/tencent.py diff --git a/apps/notifications/backends/sms.py b/apps/notifications/backends/sms.py index 798711f77..860db437d 100644 --- a/apps/notifications/backends/sms.py +++ b/apps/notifications/backends/sms.py @@ -1,6 +1,6 @@ from django.conf import settings -from common.sdk.im.sms.alibaba import AlibabaSMS as Client +from common.sdk.sms.alibaba import AlibabaSMS as Client from .base import BackendBase diff --git a/apps/settings/api/alibaba_sms.py b/apps/settings/api/alibaba_sms.py index cf905d68f..ab79d58bc 100644 --- a/apps/settings/api/alibaba_sms.py +++ b/apps/settings/api/alibaba_sms.py @@ -4,7 +4,7 @@ from rest_framework.exceptions import APIException from rest_framework import status from django.utils.translation import gettext_lazy as _ -from common.sdk.im.sms.alibaba import AlibabaSMS +from common.sdk.sms.alibaba import AlibabaSMS from settings.models import Setting from common.permissions import IsSuperUser from common.exceptions import JMSException diff --git a/apps/settings/api/sms.py b/apps/settings/api/sms.py index 4809e9ddc..de36b0317 100644 --- a/apps/settings/api/sms.py +++ b/apps/settings/api/sms.py @@ -2,7 +2,7 @@ from rest_framework.generics import ListAPIView from rest_framework.response import Response from common.permissions import IsSuperUser -from common.sdk.im.sms import BACKENDS +from common.sdk.sms import BACKENDS from settings.serializers.sms import SMSBackendSerializer diff --git a/apps/settings/api/tencent_sms.py b/apps/settings/api/tencent_sms.py index 0b4cdd69e..e20021005 100644 --- a/apps/settings/api/tencent_sms.py +++ b/apps/settings/api/tencent_sms.py @@ -6,7 +6,7 @@ from rest_framework.exceptions import APIException from rest_framework import status from django.utils.translation import gettext_lazy as _ -from common.sdk.im.sms.tencent import TencentSMS +from common.sdk.sms.tencent import TencentSMS from settings.models import Setting from common.permissions import IsSuperUser from common.exceptions import JMSException diff --git a/apps/settings/serializers/auth/sms.py b/apps/settings/serializers/auth/sms.py index 808141726..246c11da6 100644 --- a/apps/settings/serializers/auth/sms.py +++ b/apps/settings/serializers/auth/sms.py @@ -1,7 +1,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from common.sdk.im.sms import BACKENDS +from common.sdk.sms import BACKENDS __all__ = ['SMSSettingSerializer', 'AlibabaSMSSettingSerializer', 'TencentSMSSettingSerializer'] From 10f4ff4eecdf4ff4ecf33b47208713766d7392fd Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 21 Oct 2021 18:34:12 +0800 Subject: [PATCH 11/24] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=B7=A5?= =?UTF-8?q?=E5=8D=95=E9=80=9A=E7=9F=A5=E5=86=85=E5=AE=B9bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/connection_token.py | 2 +- apps/tickets/templates/tickets/_msg_ticket.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index aa6d2c08d..e496afbf2 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -19,7 +19,7 @@ from rest_framework import serializers from authentication.signals import post_auth_failed, post_auth_success from common.utils import get_logger, random_string -from common.drf.api import SerializerMixin +from common.mixins.api import SerializerMixin from common.permissions import IsSuperUserOrAppUser, IsValidUser, IsSuperUser from orgs.mixins.api import RootOrgViewMixin from common.http import is_true diff --git a/apps/tickets/templates/tickets/_msg_ticket.html b/apps/tickets/templates/tickets/_msg_ticket.html index 8fc5afb81..f789d2e9a 100644 --- a/apps/tickets/templates/tickets/_msg_ticket.html +++ b/apps/tickets/templates/tickets/_msg_ticket.html @@ -1,10 +1,10 @@ {% load i18n %}

- {{ title}} + {{ title | safe }}

- {{ body}} + {{ body | safe }}
From d1a3d31d3f283da8a149d380ffe57d869e25cdbb Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Thu, 21 Oct 2021 19:12:02 +0800 Subject: [PATCH 12/24] =?UTF-8?q?feat:=20=E5=AE=A2=E6=88=B7=E7=AB=AF?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE=E5=86=85=E5=AE=B9=E6=B7=BB=E5=8A=A0rdp?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/connection_token.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index e496afbf2..842011a94 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -150,15 +150,19 @@ class ClientProtocolMixin: def get_client_protocol_data(self, serializer): asset, application, system_user, user = self.get_request_resource(serializer) protocol = system_user.protocol + username = user.username + name = '' if protocol == 'rdp': name, config = self.get_rdp_file_content(serializer) elif protocol == 'vnc': raise HttpResponse(status=404, data={"error": "VNC not support"}) else: config = 'ssh://system_user@asset@user@jumpserver-ssh' + filename = "{}-{}-jumpserver".format(username, name) data = { + "filename": filename, "protocol": system_user.protocol, - "username": user.username, + "username": username, "config": config } return data From c341d01e5a96191da7abef6b76706fbd4f6d6982 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 21 Oct 2021 20:03:05 +0800 Subject: [PATCH 13/24] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E5=B7=A5?= =?UTF-8?q?=E5=8D=95=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 410 +++++++++--------- apps/perms/notifications.py | 14 +- apps/tickets/handler/apply_application.py | 10 +- apps/tickets/handler/apply_asset.py | 8 +- apps/tickets/handler/base.py | 15 +- apps/tickets/handler/command_confirm.py | 13 +- apps/tickets/handler/login_asset_confirm.py | 5 +- apps/tickets/handler/login_confirm.py | 5 +- apps/tickets/notifications.py | 32 +- .../templates/tickets/_base_ticket_body.html | 20 + .../templates/tickets/_msg_ticket.html | 1 + 12 files changed, 287 insertions(+), 250 deletions(-) create mode 100644 apps/tickets/templates/tickets/_base_ticket_body.html diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index c16f2930a..4944330cb 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:39e9d8c61c6986a067d9c0c82d2a459e07bcc78d07d9bdd1c5934154ea3a198d -size 91321 +oid sha256:b5a90a96a51ef2f38d4530f3e5b79cd59b07dbca39f60bc35cde5ee0e364b210 +size 91089 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 2987511f3..ae72cdc87 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-10-20 18:58+0800\n" +"POT-Creation-Date: 2021-10-21 18:50+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -25,7 +25,7 @@ msgstr "" #: orgs/models.py:24 perms/models/base.py:44 settings/models.py:29 #: settings/serializers/sms.py:6 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:604 +#: users/forms/profile.py:32 users/models/group.py:15 users/models/user.py:597 #: users/templates/users/_select_user_modal.html:13 #: users/templates/users/user_asset_permission.html:37 #: users/templates/users/user_asset_permission.html:154 @@ -44,7 +44,7 @@ msgstr "优先级" msgid "1-100, the lower the value will be match first" msgstr "优先级可选范围为 1-100 (数值越小越优先)" -#: acls/models/base.py:31 authentication/models.py:20 +#: acls/models/base.py:31 authentication/models.py:17 #: authentication/templates/authentication/_access_key_modal.html:32 #: perms/models/base.py:48 terminal/models/sharing.py:24 #: users/templates/users/_select_user_modal.html:18 @@ -60,26 +60,49 @@ msgstr "激活中" #: orgs/models.py:27 perms/models/base.py:53 settings/models.py:34 #: terminal/models/storage.py:26 terminal/models/terminal.py:114 #: tickets/models/ticket.py:71 users/models/group.py:16 -#: users/models/user.py:637 xpack/plugins/change_auth_plan/models/base.py:41 +#: users/models/user.py:630 xpack/plugins/change_auth_plan/models/base.py:41 #: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:113 #: xpack/plugins/gathered_user/models.py:26 msgid "Comment" msgstr "备注" -#: acls/models/login_acl.py:16 tickets/const.py:38 +#: acls/models/login_acl.py:19 tickets/const.py:38 msgid "Reject" msgstr "拒绝" -#: acls/models/login_acl.py:17 assets/models/cmd_filter.py:48 +#: acls/models/login_acl.py:20 assets/models/cmd_filter.py:48 msgid "Allow" msgstr "允许" -#: acls/models/login_acl.py:20 -msgid "Login IP" -msgstr "登录IP" +#: acls/models/login_acl.py:21 acls/models/login_acl.py:113 +#: acls/models/login_asset_acl.py:17 tickets/const.py:9 +msgid "Login confirm" +msgstr "登录复核" -#: acls/models/login_acl.py:24 acls/models/login_asset_acl.py:26 -#: acls/serializers/login_acl.py:34 acls/serializers/login_asset_acl.py:75 +#: acls/models/login_acl.py:25 acls/models/login_asset_acl.py:20 +#: assets/models/label.py:15 audits/models.py:36 audits/models.py:56 +#: audits/models.py:74 audits/serializers.py:94 authentication/models.py:47 +#: orgs/models.py:19 orgs/models.py:433 perms/models/base.py:45 +#: templates/index.html:78 terminal/backends/command/models.py:18 +#: terminal/backends/command/serializers.py:12 terminal/models/session.py:38 +#: terminal/templates/terminal/_msg_command_alert.html:10 +#: terminal/templates/terminal/_msg_command_execute_alert.html:4 +#: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:173 +#: users/models/user.py:801 users/models/user.py:827 +#: users/serializers/group.py:19 +#: users/templates/users/user_asset_permission.html:38 +#: users/templates/users/user_asset_permission.html:64 +#: users/templates/users/user_database_app_permission.html:37 +#: users/templates/users/user_database_app_permission.html:58 +msgid "User" +msgstr "用户" + +#: acls/models/login_acl.py:29 +msgid "Rule" +msgstr "" + +#: acls/models/login_acl.py:32 acls/models/login_asset_acl.py:26 +#: acls/serializers/login_acl.py:16 acls/serializers/login_asset_acl.py:75 #: assets/models/cmd_filter.py:57 audits/models.py:57 #: authentication/templates/authentication/_access_key_modal.html:34 #: users/templates/users/_granted_assets.html:29 @@ -89,30 +112,10 @@ msgstr "登录IP" msgid "Action" msgstr "动作" -#: acls/models/login_acl.py:28 acls/models/login_asset_acl.py:20 -#: acls/serializers/login_acl.py:33 assets/models/label.py:15 -#: audits/models.py:36 audits/models.py:56 audits/models.py:74 -#: audits/serializers.py:93 authentication/models.py:44 -#: authentication/models.py:100 orgs/models.py:19 orgs/models.py:433 -#: perms/models/base.py:45 templates/index.html:78 -#: terminal/backends/command/models.py:18 -#: terminal/backends/command/serializers.py:12 terminal/models/session.py:38 -#: terminal/templates/terminal/_msg_command_alert.html:10 -#: terminal/templates/terminal/_msg_command_execue_alert.html:4 -#: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:180 -#: users/models/user.py:813 users/models/user.py:839 -#: users/serializers/group.py:19 -#: users/templates/users/user_asset_permission.html:38 -#: users/templates/users/user_asset_permission.html:64 -#: users/templates/users/user_database_app_permission.html:37 -#: users/templates/users/user_database_app_permission.html:58 -msgid "User" -msgstr "用户" - -#: acls/models/login_asset_acl.py:17 authentication/models.py:75 -#: tickets/const.py:9 -msgid "Login confirm" -msgstr "登录复核" +#: acls/models/login_acl.py:36 acls/models/login_asset_acl.py:32 +#: acls/serializers/login_acl.py:15 assets/models/cmd_filter.py:62 +msgid "Reviewers" +msgstr "审批人" #: acls/models/login_asset_acl.py:21 msgid "System User" @@ -133,48 +136,15 @@ msgstr "系统用户" msgid "Asset" msgstr "资产" -#: acls/models/login_asset_acl.py:32 assets/models/cmd_filter.py:62 -#: authentication/models.py:45 -msgid "Reviewers" -msgstr "审批人" - #: acls/models/login_asset_acl.py:89 tickets/const.py:12 msgid "Login asset confirm" msgstr "登录资产复核" -#: acls/serializers/login_acl.py:18 xpack/plugins/cloud/serializers/task.py:23 -msgid "IP address invalid: `{}`" -msgstr "IP 地址无效: `{}`" - -#: acls/serializers/login_acl.py:24 -msgid "" -"Format for comma-delimited string, with * indicating a match all. Such as: " -"192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:" -"db8:1a:1110::/64 " -msgstr "" -"格式为逗号分隔的字符串, * 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, " -"10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64" - -#: acls/serializers/login_acl.py:30 acls/serializers/login_asset_acl.py:31 -#: applications/serializers/attrs/application_type/mysql_workbench.py:18 -#: assets/models/asset.py:180 assets/models/domain.py:61 -#: assets/serializers/account.py:12 -#: authentication/templates/authentication/_msg_rest_password_success.html:9 -#: settings/serializers/terminal.py:8 -#: users/templates/users/_granted_assets.html:26 -#: users/templates/users/user_asset_permission.html:156 -msgid "IP" -msgstr "IP" - -#: acls/serializers/login_acl.py:55 -msgid "The user `{}` is not in the current organization: `{}`" -msgstr "用户 `{}` 不在当前组织: `{}`" - -#: acls/serializers/login_asset_acl.py:12 +#: acls/serializers/login_acl.py:10 acls/serializers/login_asset_acl.py:12 msgid "Format for comma-delimited string, with * indicating a match all. " msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " -#: acls/serializers/login_asset_acl.py:17 +#: acls/serializers/login_acl.py:14 acls/serializers/login_asset_acl.py:17 #: acls/serializers/login_asset_acl.py:51 #: applications/serializers/attrs/application_type/chrome.py:20 #: applications/serializers/attrs/application_type/custom.py:21 @@ -183,7 +153,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " #: assets/models/base.py:176 assets/models/gathered_user.py:15 #: audits/models.py:105 authentication/forms.py:15 authentication/forms.py:17 #: authentication/templates/authentication/_msg_different_city.html:9 -#: ops/models/adhoc.py:148 users/forms/profile.py:31 users/models/user.py:602 +#: ops/models/adhoc.py:148 users/forms/profile.py:31 users/models/user.py:595 #: users/templates/users/_select_user_modal.html:14 #: xpack/plugins/change_auth_plan/models/asset.py:35 #: xpack/plugins/change_auth_plan/models/asset.py:191 @@ -200,6 +170,17 @@ msgstr "" "格式为逗号分隔的字符串, * 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, " "10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 (支持网域)" +#: acls/serializers/login_asset_acl.py:31 acls/serializers/rules/rules.py:32 +#: applications/serializers/attrs/application_type/mysql_workbench.py:18 +#: assets/models/asset.py:180 assets/models/domain.py:61 +#: assets/serializers/account.py:12 +#: authentication/templates/authentication/_msg_rest_password_success.html:9 +#: settings/serializers/terminal.py:8 +#: users/templates/users/_granted_assets.html:26 +#: users/templates/users/user_asset_permission.html:156 +msgid "IP" +msgstr "IP" + #: acls/serializers/login_asset_acl.py:35 assets/models/asset.py:181 #: assets/serializers/account.py:13 assets/serializers/gathered_user.py:23 #: settings/serializers/terminal.py:7 @@ -233,6 +214,24 @@ msgstr "组织 `{}` 不存在" msgid "None of the reviewers belong to Organization `{}`" msgstr "所有复核人都不属于组织 `{}`" +#: acls/serializers/rules/rules.py:20 +#: xpack/plugins/cloud/serializers/task.py:23 +msgid "IP address invalid: `{}`" +msgstr "IP 地址无效: `{}`" + +#: acls/serializers/rules/rules.py:26 +msgid "" +"Format for comma-delimited string, with * indicating a match all. Such as: " +"192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:" +"db8:1a:1110::/64 " +msgstr "" +"格式为逗号分隔的字符串, * 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, " +"10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64" + +#: acls/serializers/rules/rules.py:34 +msgid "Time Period" +msgstr "" + #: applications/api/mixin.py:20 templates/_nav_user.html:10 msgid "My applications" msgstr "我的应用" @@ -456,7 +455,6 @@ msgstr "节点" #: assets/models/asset.py:190 assets/models/cmd_filter.py:22 #: assets/models/domain.py:66 assets/models/label.py:22 -#: authentication/models.py:46 msgid "Is active" msgstr "激活" @@ -537,7 +535,7 @@ msgstr "标签管理" #: 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:25 -#: orgs/models.py:437 perms/models/base.py:51 users/models/user.py:645 +#: orgs/models.py:437 perms/models/base.py:51 users/models/user.py:638 #: users/serializers/group.py:33 #: xpack/plugins/change_auth_plan/models/base.py:45 #: xpack/plugins/cloud/models.py:119 xpack/plugins/gathered_user/models.py:30 @@ -550,7 +548,7 @@ msgstr "创建者" #: 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:26 #: orgs/models.py:435 perms/models/base.py:52 users/models/group.py:18 -#: users/models/user.py:840 xpack/plugins/cloud/models.py:122 +#: users/models/user.py:828 xpack/plugins/cloud/models.py:122 msgid "Date created" msgstr "创建日期" @@ -605,7 +603,7 @@ msgstr "带宽" msgid "Contact" msgstr "联系人" -#: assets/models/cluster.py:22 users/models/user.py:623 +#: assets/models/cluster.py:22 users/models/user.py:616 msgid "Phone" msgstr "手机" @@ -631,7 +629,7 @@ msgid "Default" msgstr "默认" #: assets/models/cluster.py:36 assets/models/label.py:14 -#: users/models/user.py:825 +#: users/models/user.py:813 msgid "System" msgstr "系统" @@ -650,7 +648,7 @@ msgstr "正则表达式" #: assets/models/cmd_filter.py:41 ops/models/command.py:25 #: terminal/backends/command/serializers.py:15 terminal/models/session.py:49 #: terminal/templates/terminal/_msg_command_alert.html:4 -#: terminal/templates/terminal/_msg_command_execue_alert.html:15 +#: terminal/templates/terminal/_msg_command_execute_alert.html:10 msgid "Command" msgstr "命令" @@ -774,7 +772,7 @@ msgstr "用户名与用户相同" #: assets/models/user.py:200 assets/serializers/domain.py:29 #: templates/_nav.html:39 -#: terminal/templates/terminal/_msg_command_execue_alert.html:10 +#: terminal/templates/terminal/_msg_command_execute_alert.html:16 #: xpack/plugins/change_auth_plan/models/asset.py:40 msgid "Assets" msgstr "资产" @@ -808,7 +806,7 @@ msgstr "认证方式" msgid "SFTP Root" msgstr "SFTP根路径" -#: assets/models/user.py:215 authentication/models.py:98 +#: assets/models/user.py:215 authentication/models.py:45 msgid "Token" msgstr "" @@ -1176,12 +1174,12 @@ msgstr "用户代理" #: audits/models.py:110 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 #: authentication/templates/authentication/login_otp.html:6 -#: users/forms/profile.py:64 users/models/user.py:626 +#: users/forms/profile.py:64 users/models/user.py:619 #: users/serializers/profile.py:102 msgid "MFA" msgstr "多因子认证" -#: audits/models.py:111 terminal/models/sharing.py:88 +#: audits/models.py:111 audits/serializers.py:45 terminal/models/sharing.py:88 #: xpack/plugins/change_auth_plan/models/base.py:187 #: xpack/plugins/cloud/models.py:176 msgid "Reason" @@ -1212,33 +1210,33 @@ msgstr "状态名称" msgid "MFA display" msgstr "多因子认证" -#: audits/serializers.py:75 audits/serializers.py:90 ops/models/adhoc.py:248 +#: audits/serializers.py:76 audits/serializers.py:91 ops/models/adhoc.py:248 #: terminal/serializers/session.py:35 msgid "Is success" msgstr "是否成功" -#: audits/serializers.py:77 +#: audits/serializers.py:78 msgid "Hosts display" msgstr "主机名称" -#: audits/serializers.py:89 ops/models/command.py:26 +#: audits/serializers.py:90 ops/models/command.py:26 #: xpack/plugins/cloud/models.py:170 msgid "Result" msgstr "结果" -#: audits/serializers.py:91 terminal/serializers/storage.py:151 +#: audits/serializers.py:92 terminal/serializers/storage.py:151 msgid "Hosts" msgstr "主机" -#: audits/serializers.py:92 +#: audits/serializers.py:93 msgid "Run as" msgstr "运行用户" -#: audits/serializers.py:94 +#: audits/serializers.py:95 msgid "Run as display" msgstr "运行用户名称" -#: audits/serializers.py:95 +#: audits/serializers.py:96 msgid "User display" msgstr "用户名称" @@ -1254,13 +1252,13 @@ msgstr "" msgid "Auth Token" msgstr "认证令牌" -#: audits/signals_handler.py:68 authentication/views/login.py:169 -#: notifications/backends/__init__.py:11 users/models/user.py:659 +#: audits/signals_handler.py:68 authentication/views/login.py:168 +#: notifications/backends/__init__.py:11 users/models/user.py:652 msgid "WeCom" msgstr "企业微信" -#: audits/signals_handler.py:69 authentication/views/login.py:175 -#: notifications/backends/__init__.py:12 users/models/user.py:660 +#: audits/signals_handler.py:69 authentication/views/login.py:174 +#: notifications/backends/__init__.py:12 users/models/user.py:653 msgid "DingTalk" msgstr "钉钉" @@ -1630,30 +1628,31 @@ msgstr "登录复核 {}" msgid "IP is not allowed" msgstr "来源 IP 不被允许登录" +#: authentication/errors.py:268 msgid "Time Period is not allowed" msgstr "该 时间段 不被允许登录" -#: authentication/errors.py:294 +#: authentication/errors.py:301 msgid "SSO auth closed" msgstr "SSO 认证关闭了" -#: authentication/errors.py:299 authentication/mixins.py:338 +#: authentication/errors.py:306 authentication/mixins.py:340 msgid "Your password is too simple, please change it for security" msgstr "你的密码过于简单,为了安全,请修改" -#: authentication/errors.py:308 authentication/mixins.py:345 +#: authentication/errors.py:315 authentication/mixins.py:347 msgid "You should to change your password before login" msgstr "登录完成前,请先修改密码" -#: authentication/errors.py:317 authentication/mixins.py:352 +#: authentication/errors.py:324 authentication/mixins.py:354 msgid "Your password has expired, please reset before logging in" msgstr "您的密码已过期,先修改再登录" -#: authentication/errors.py:351 +#: authentication/errors.py:358 msgid "Your password is invalid" msgstr "您的密码无效" -#: authentication/errors.py:357 +#: authentication/errors.py:364 msgid "No upload or download permission" msgstr "没有上传下载权限" @@ -1677,31 +1676,23 @@ msgstr "多因子认证验证码" msgid "Dynamic code" msgstr "动态码" -#: authentication/mixins.py:328 +#: authentication/mixins.py:330 msgid "Please change your password" msgstr "请修改密码" -#: authentication/mixins.py:514 +#: authentication/mixins.py:515 msgid "SMS" msgstr "短信" -#: authentication/models.py:40 +#: authentication/models.py:37 msgid "Private Token" msgstr "SSH密钥" -#: authentication/models.py:49 settings/serializers/security.py:144 -msgid "Login Confirm" -msgstr "登录复核" - -#: authentication/models.py:90 -msgid "{} need confirm by {}" -msgstr "{} 需要 {} 复核" - -#: authentication/models.py:99 +#: authentication/models.py:46 msgid "Expired" msgstr "过期时间" -#: authentication/notifications.py:19 +#: authentication/notifications.py:20 msgid "Different city login reminder" msgstr "异地登录提醒" @@ -1749,14 +1740,14 @@ msgid "Show" msgstr "显示" #: authentication/templates/authentication/_access_key_modal.html:66 -#: settings/serializers/security.py:25 users/models/user.py:469 +#: settings/serializers/security.py:25 users/models/user.py:462 #: users/serializers/profile.py:99 #: users/templates/users/user_verify_mfa.html:32 msgid "Disable" msgstr "禁用" #: authentication/templates/authentication/_access_key_modal.html:67 -#: users/models/user.py:470 users/serializers/profile.py:100 +#: users/models/user.py:463 users/serializers/profile.py:100 msgid "Enable" msgstr "启用" @@ -2007,12 +1998,12 @@ msgstr "正在跳转到 {} 认证" msgid "Please enable cookies and try again." msgstr "设置你的浏览器支持cookie" -#: authentication/views/login.py:181 notifications/backends/__init__.py:14 -#: users/models/user.py:661 +#: authentication/views/login.py:180 notifications/backends/__init__.py:14 +#: users/models/user.py:654 msgid "FeiShu" msgstr "飞书" -#: authentication/views/login.py:269 +#: authentication/views/login.py:268 msgid "" "Wait for {} confirm, You also can copy link to her/him
\n" " Don't close this page" @@ -2020,15 +2011,15 @@ msgstr "" "等待 {} 确认, 你也可以复制链接发给他/她
\n" " 不要关闭本页面" -#: authentication/views/login.py:274 +#: authentication/views/login.py:273 msgid "No ticket found" msgstr "没有发现工单" -#: authentication/views/login.py:306 +#: authentication/views/login.py:305 msgid "Logout success" msgstr "退出登录成功" -#: authentication/views/login.py:307 +#: authentication/views/login.py:306 msgid "Logout success, return login page" msgstr "退出登录成功,返回到登录页面" @@ -2151,35 +2142,7 @@ msgstr "编码数据为 text" msgid "Encrypt field using Secret Key" msgstr "加密的字段" -#: common/message/backends/exceptions.py:23 -msgid "Network error, please contact system administrator" -msgstr "网络错误,请联系系统管理员" - -#: common/message/backends/sms/__init__.py:15 -msgid "Alibaba cloud" -msgstr "阿里云" - -#: common/message/backends/sms/__init__.py:16 -msgid "Tencent cloud" -msgstr "腾讯云" - -#: common/message/backends/sms/__init__.py:42 -msgid "SMS provider not support: {}" -msgstr "短信服务商不支持:{}" - -#: common/message/backends/sms/__init__.py:63 -msgid "SMS verification code signature or template invalid" -msgstr "短信验证码签名或模版无效" - -#: common/message/backends/sms/alibaba.py:56 -msgid "Signature does not match" -msgstr "签名不匹配" - -#: common/message/backends/wecom/__init__.py:15 -msgid "WeCom error, please contact system administrator" -msgstr "企业微信错误,请联系系统管理员" - -#: common/mixins/api.py:58 +#: common/mixins/api/action.py:53 msgid "Request file format may be wrong" msgstr "上传的文件格式错误 或 其它类型资源的文件" @@ -2191,6 +2154,34 @@ msgstr "忽略的" msgid "discard time" msgstr "忽略时间" +#: common/sdk/im/exceptions.py:23 +msgid "Network error, please contact system administrator" +msgstr "网络错误,请联系系统管理员" + +#: common/sdk/im/wecom/__init__.py:15 +msgid "WeCom error, please contact system administrator" +msgstr "企业微信错误,请联系系统管理员" + +#: common/sdk/sms/__init__.py:15 +msgid "Alibaba cloud" +msgstr "阿里云" + +#: common/sdk/sms/__init__.py:16 +msgid "Tencent cloud" +msgstr "腾讯云" + +#: common/sdk/sms/__init__.py:42 +msgid "SMS provider not support: {}" +msgstr "短信服务商不支持:{}" + +#: common/sdk/sms/__init__.py:63 +msgid "SMS verification code signature or template invalid" +msgstr "短信验证码签名或模版无效" + +#: common/sdk/sms/alibaba.py:56 +msgid "Signature does not match" +msgstr "签名不匹配" + #: common/utils/geoip/utils.py:17 common/utils/geoip/utils.py:30 msgid "Invalid ip" msgstr "无效IP" @@ -2247,7 +2238,7 @@ msgstr "" "div>" #: notifications/backends/__init__.py:10 users/forms/profile.py:101 -#: users/models/user.py:606 +#: users/models/user.py:599 msgid "Email" msgstr "邮件" @@ -2255,7 +2246,7 @@ msgstr "邮件" msgid "Site message" msgstr "站内信" -#: notifications/notifications.py:147 ops/models/adhoc.py:246 +#: notifications/notifications.py:148 ops/models/adhoc.py:246 #: xpack/plugins/change_auth_plan/models/base.py:108 #: xpack/plugins/change_auth_plan/models/base.py:190 #: xpack/plugins/gathered_user/models.py:79 @@ -2458,7 +2449,7 @@ msgstr "组织审计员" msgid "GLOBAL" msgstr "全局组织" -#: orgs/models.py:434 users/models/user.py:614 users/serializers/user.py:37 +#: orgs/models.py:434 users/models/user.py:607 users/serializers/user.py:37 #: users/templates/users/_select_user_modal.html:15 msgid "Role" msgstr "角色" @@ -2471,7 +2462,7 @@ msgstr "管理员正在修改授权,请稍等" msgid "The authorization cannot be revoked for the time being" msgstr "该授权暂时不能撤销" -#: perms/models/application_permission.py:27 users/models/user.py:181 +#: perms/models/application_permission.py:27 users/models/user.py:174 msgid "Application" msgstr "应用程序" @@ -2523,7 +2514,7 @@ msgid "Favorite" msgstr "收藏夹" #: perms/models/base.py:47 templates/_nav.html:21 users/models/group.py:31 -#: users/models/user.py:610 users/templates/users/_select_user_modal.html:16 +#: users/models/user.py:603 users/templates/users/_select_user_modal.html:16 #: users/templates/users/user_asset_permission.html:39 #: users/templates/users/user_asset_permission.html:67 #: users/templates/users/user_database_app_permission.html:38 @@ -2534,7 +2525,7 @@ msgstr "用户组" #: perms/models/base.py:50 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:60 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:50 -#: users/models/user.py:642 +#: users/models/user.py:635 msgid "Date expired" msgstr "失效日期" @@ -2542,35 +2533,35 @@ msgstr "失效日期" msgid "From ticket" msgstr "来自工单" -#: perms/notifications.py:21 +#: perms/notifications.py:19 msgid "You permed assets is about to expire" msgstr "你授权的资产即将到期" -#: perms/notifications.py:25 +#: perms/notifications.py:23 msgid "permed assets" msgstr "授权的资产" -#: perms/notifications.py:63 +#: perms/notifications.py:61 msgid "Asset permissions is about to expire" msgstr "资产授权规则将要过期" -#: perms/notifications.py:67 +#: perms/notifications.py:65 msgid "asset permissions of organization {}" msgstr "组织 ({}) 的资产授权" -#: perms/notifications.py:93 +#: perms/notifications.py:91 msgid "Your permed applications is about to expire" msgstr "你授权的应用即将过期" -#: perms/notifications.py:96 +#: perms/notifications.py:94 msgid "permed applications" msgstr "授权的应用" -#: perms/notifications.py:134 +#: perms/notifications.py:132 msgid "Application permissions is about to expire" msgstr "应用授权规则即将过期" -#: perms/notifications.py:137 +#: perms/notifications.py:135 msgid "application permissions of organization {}" msgstr "组织 ({}) 的应用授权" @@ -2584,7 +2575,7 @@ msgstr "账户是否有效" #: perms/serializers/application/permission.py:19 #: perms/serializers/application/permission.py:37 #: perms/serializers/asset/permission.py:43 -#: perms/serializers/asset/permission.py:67 users/serializers/user.py:29 +#: perms/serializers/asset/permission.py:67 users/serializers/user.py:28 #: users/serializers/user.py:79 msgid "Is expired" msgstr "已过期" @@ -3362,10 +3353,6 @@ msgstr "会话分享" msgid "Enabled, Allows user active session to be shared with other users" msgstr "开启后允许用户分享已连接的资产会话给它人,协同工作" -#: settings/serializers/security.py:145 -msgid "Enabled, please go to the user detail add approver" -msgstr "启用后, 请在用户详情中添加审批人" - #: settings/serializers/sms.py:7 msgid "Label" msgstr "标签" @@ -4284,7 +4271,7 @@ msgstr "是否可加入" msgid "Can terminate" msgstr "是否可中断" -#: terminal/serializers/session.py:50 +#: terminal/serializers/session.py:45 msgid "Command amount" msgstr "命令数量" @@ -4353,12 +4340,16 @@ msgstr "文档类型" msgid "Ignore Certificate Verification" msgstr "忽略证书认证" -#: terminal/serializers/terminal.py:80 terminal/serializers/terminal.py:88 +#: terminal/serializers/terminal.py:44 +msgid "Load status" +msgstr "" + +#: terminal/serializers/terminal.py:83 terminal/serializers/terminal.py:91 msgid "Not found" msgstr "没有发现" #: terminal/templates/terminal/_msg_command_alert.html:13 -#: terminal/templates/terminal/_msg_command_execue_alert.html:7 +#: terminal/templates/terminal/_msg_command_execute_alert.html:7 msgid "Level" msgstr "级别" @@ -4618,22 +4609,22 @@ msgstr "流程" msgid "TicketFlow" msgstr "工单流程" -#: tickets/notifications.py:46 -msgid "New Ticket - {} ({})" -msgstr "新工单 - {} ({})" - -#: tickets/notifications.py:54 +#: tickets/notifications.py:56 msgid "Your has a new ticket, applicant - {}" msgstr "你有一个新的工单, 申请人 - {}" -#: tickets/notifications.py:68 -msgid "Ticket has processed - {} ({})" -msgstr "你的工单已被处理, 处理人 - {} ({})" +#: tickets/notifications.py:62 +msgid "New Ticket - {} ({})" +msgstr "新工单 - {} ({})" -#: tickets/notifications.py:77 +#: tickets/notifications.py:84 msgid "Your ticket has been processed, processor - {}" msgstr "你的工单已被处理, 处理人 - {}" +#: tickets/notifications.py:88 +msgid "Ticket has processed - {} ({})" +msgstr "你的工单已被处理, 处理人 - {} ({})" + #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:20 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:18 msgid "Apply name" @@ -4751,20 +4742,18 @@ msgid "The current organization type already exists" msgstr "当前组织已存在该类型" #: tickets/templates/tickets/_msg_ticket.html:11 -#, fuzzy -#| msgid "click here to review" msgid "Click here to review" msgstr "点击查看" -#: users/api/user.py:208 +#: users/api/user.py:207 msgid "Could not reset self otp, use profile reset instead" msgstr "不能在该页面重置多因子认证, 请去个人信息页面重置" -#: users/const.py:10 users/models/user.py:178 +#: users/const.py:10 users/models/user.py:171 msgid "System administrator" msgstr "系统管理员" -#: users/const.py:11 users/models/user.py:179 +#: users/const.py:11 users/models/user.py:172 msgid "System auditor" msgstr "系统审计员" @@ -4859,7 +4848,7 @@ msgstr "不能和原来的密钥相同" msgid "Not a valid ssh public key" msgstr "SSH密钥不合法" -#: users/forms/profile.py:160 users/models/user.py:634 +#: users/forms/profile.py:160 users/models/user.py:627 #: users/templates/users/user_password_update.html:48 msgid "Public key" msgstr "SSH公钥" @@ -4872,43 +4861,43 @@ msgstr "一次性密码" msgid "SMS verify code" msgstr "短信验证码" -#: users/models/user.py:471 +#: users/models/user.py:464 msgid "Force enable" msgstr "强制启用" -#: users/models/user.py:583 +#: users/models/user.py:576 msgid "Local" msgstr "数据库" -#: users/models/user.py:617 +#: users/models/user.py:610 msgid "Avatar" msgstr "头像" -#: users/models/user.py:620 +#: users/models/user.py:613 msgid "Wechat" msgstr "微信" -#: users/models/user.py:631 +#: users/models/user.py:624 msgid "Private key" msgstr "ssh私钥" -#: users/models/user.py:650 +#: users/models/user.py:643 msgid "Source" msgstr "来源" -#: users/models/user.py:654 +#: users/models/user.py:647 msgid "Date password last updated" msgstr "最后更新密码日期" -#: users/models/user.py:657 +#: users/models/user.py:650 msgid "Need update password" msgstr "需要更新密码" -#: users/models/user.py:821 +#: users/models/user.py:809 msgid "Administrator" msgstr "管理员" -#: users/models/user.py:824 +#: users/models/user.py:812 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" @@ -4959,33 +4948,33 @@ msgstr "两次密码不一致" msgid "Is first login" msgstr "首次登录" -#: users/serializers/user.py:23 +#: users/serializers/user.py:22 #: xpack/plugins/change_auth_plan/models/base.py:32 #: xpack/plugins/change_auth_plan/serializers/base.py:24 msgid "Password strategy" msgstr "密码策略" -#: users/serializers/user.py:25 +#: users/serializers/user.py:24 msgid "MFA enabled" msgstr "MFA" -#: users/serializers/user.py:26 +#: users/serializers/user.py:25 msgid "MFA force enabled" msgstr "强制 MFA" -#: users/serializers/user.py:27 +#: users/serializers/user.py:26 msgid "MFA level display" msgstr "MFA 等级名称" -#: users/serializers/user.py:28 +#: users/serializers/user.py:27 msgid "Login blocked" msgstr "登录被阻塞" -#: users/serializers/user.py:30 +#: users/serializers/user.py:29 msgid "Can update" msgstr "是否可更新" -#: users/serializers/user.py:31 +#: users/serializers/user.py:30 msgid "Can delete" msgstr "是否可删除" @@ -5037,7 +5026,7 @@ msgstr "是否绑定了虚拟MFA" msgid "Role limit to {}" msgstr "角色只能为 {}" -#: users/serializers/user.py:235 +#: users/serializers/user.py:227 msgid "name not unique" msgstr "名称重复" @@ -5994,6 +5983,21 @@ msgstr "旗舰版" msgid "Community edition" msgstr "社区版" +#~ msgid "Login IP" +#~ msgstr "登录IP" + +#~ msgid "The user `{}` is not in the current organization: `{}`" +#~ msgstr "用户 `{}` 不在当前组织: `{}`" + +#~ msgid "Login Confirm" +#~ msgstr "登录复核" + +#~ msgid "{} need confirm by {}" +#~ msgstr "{} 需要 {} 复核" + +#~ msgid "Enabled, please go to the user detail add approver" +#~ msgstr "启用后, 请在用户详情中添加审批人" + #~ msgid "" #~ "\n" #~ "Time: {}" diff --git a/apps/perms/notifications.py b/apps/perms/notifications.py index a8319b92f..4971e7134 100644 --- a/apps/perms/notifications.py +++ b/apps/perms/notifications.py @@ -6,13 +6,7 @@ from common.utils import reverse as js_reverse from notifications.notifications import UserMessage -class BasePermMsg(UserMessage): - @classmethod - def gen_test_msg(cls): - return - - -class PermedWillExpireUserMsg(BasePermMsg): +class PermedWillExpireUserMsg(UserMessage): def __init__(self, user, assets): super().__init__(user) self.assets = assets @@ -40,7 +34,7 @@ class PermedWillExpireUserMsg(BasePermMsg): return cls(user, assets) -class AssetPermsWillExpireForOrgAdminMsg(BasePermMsg): +class AssetPermsWillExpireForOrgAdminMsg(UserMessage): def __init__(self, user, perms, org): super().__init__(user) @@ -84,7 +78,7 @@ class AssetPermsWillExpireForOrgAdminMsg(BasePermMsg): return cls(user, perms, org) -class PermedAppsWillExpireUserMsg(BasePermMsg): +class PermedAppsWillExpireUserMsg(UserMessage): def __init__(self, user, apps): super().__init__(user) self.apps = apps @@ -112,7 +106,7 @@ class PermedAppsWillExpireUserMsg(BasePermMsg): return cls(user, apps) -class AppPermsWillExpireForOrgAdminMsg(BasePermMsg): +class AppPermsWillExpireForOrgAdminMsg(UserMessage): def __init__(self, user, perms, org): super().__init__(user) self.perms = perms diff --git a/apps/tickets/handler/apply_application.py b/apps/tickets/handler/apply_application.py index 61ef64258..9a504f39f 100644 --- a/apps/tickets/handler/apply_application.py +++ b/apps/tickets/handler/apply_application.py @@ -43,11 +43,11 @@ class Handler(BaseHandler): apply_date_start = self.ticket.meta.get('apply_date_start') apply_date_expired = self.ticket.meta.get('apply_date_expired') applied_body = '''{}: {}, - {}: {}, - {}: {}, - {}: {}, - {}: {}, - {}: {}, + {}: {} + {}: {} + {}: {} + {}: {} + {}: {} '''.format( _('Applied category'), apply_category_display, _('Applied type'), apply_type_display, diff --git a/apps/tickets/handler/apply_asset.py b/apps/tickets/handler/apply_asset.py index 584f3a12b..883c7f45b 100644 --- a/apps/tickets/handler/apply_asset.py +++ b/apps/tickets/handler/apply_asset.py @@ -69,10 +69,10 @@ class Handler(BaseHandler): str(self.ticket.__class__.__name__), str(self.ticket.id) ) permission_comment = _( - 'Created by the ticket, ' - 'ticket title: {}, ' - 'ticket applicant: {}, ' - 'ticket processor: {}, ' + 'Created by the ticket ' + 'ticket title: {} ' + 'ticket applicant: {} ' + 'ticket processor: {} ' 'ticket ID: {}' ).format( self.ticket.title, diff --git a/apps/tickets/handler/base.py b/apps/tickets/handler/base.py index e308d7255..a5b2efdc6 100644 --- a/apps/tickets/handler/base.py +++ b/apps/tickets/handler/base.py @@ -105,28 +105,29 @@ class BaseHandler(object): return basic_body + meta_body def _construct_basic_body(self): - basic_body = '''{}: {}, - {}: {}, - {}: {}, - {}: {}, + basic_body = ''' + {}: {} + {}: {} + {}: {} + {}: {} '''.format( _('Ticket title'), self.ticket.title, _('Ticket type'), self.ticket.get_type_display(), _('Ticket status'), self.ticket.get_status_display(), _('Ticket applicant'), self.ticket.applicant_display, - ) + ).strip() body = self.body_html_format.format(_("Ticket basic info"), basic_body) return body def _construct_meta_body(self): body = '' - open_body = self._base_construct_meta_body_of_open() + open_body = self._base_construct_meta_body_of_open().strip() body += open_body return body def _base_construct_meta_body_of_open(self): meta_body_of_open = getattr( self, '_construct_meta_body_of_open', lambda: _('No content') - )() + )().strip() body = self.body_html_format.format(_('Ticket applied info'), meta_body_of_open) return body diff --git a/apps/tickets/handler/command_confirm.py b/apps/tickets/handler/command_confirm.py index 2d66db2d8..6fb27dcfe 100644 --- a/apps/tickets/handler/command_confirm.py +++ b/apps/tickets/handler/command_confirm.py @@ -14,12 +14,13 @@ class Handler(BaseHandler): apply_from_cmd_filter_rule_id = self.ticket.meta.get('apply_from_cmd_filter_rule_id') apply_from_cmd_filter_id = self.ticket.meta.get('apply_from_cmd_filter_id') - applied_body = '''{}: {}, - {}: {}, - {}: {}, - {}: {}, - {}: {}, - {}: {}, + applied_body = ''' + {}: {} + {}: {} + {}: {} + {}: {} + {}: {} + {}: {} '''.format( _("Applied run user"), apply_run_user, _("Applied run asset"), apply_run_asset, diff --git a/apps/tickets/handler/login_asset_confirm.py b/apps/tickets/handler/login_asset_confirm.py index b967af29d..039f7ce50 100644 --- a/apps/tickets/handler/login_asset_confirm.py +++ b/apps/tickets/handler/login_asset_confirm.py @@ -9,8 +9,9 @@ class Handler(BaseHandler): apply_login_user = self.ticket.meta.get('apply_login_user') apply_login_asset = self.ticket.meta.get('apply_login_asset') apply_login_system_user = self.ticket.meta.get('apply_login_system_user') - applied_body = '''{}: {}, - {}: {}, + applied_body = ''' + {}: {} + {}: {} {}: {} '''.format( _("Applied login user"), apply_login_user, diff --git a/apps/tickets/handler/login_confirm.py b/apps/tickets/handler/login_confirm.py index b1d491cfc..65c46fb56 100644 --- a/apps/tickets/handler/login_confirm.py +++ b/apps/tickets/handler/login_confirm.py @@ -9,8 +9,9 @@ class Handler(BaseHandler): apply_login_ip = self.ticket.meta.get('apply_login_ip') apply_login_city = self.ticket.meta.get('apply_login_city') apply_login_datetime = self.ticket.meta.get('apply_login_datetime') - applied_body = '''{}: {}, - {}: {}, + applied_body = ''' + {}: {} + {}: {} {}: {} '''.format( _("Applied login IP"), apply_login_ip, diff --git a/apps/tickets/notifications.py b/apps/tickets/notifications.py index a8f3a5417..2df08845f 100644 --- a/apps/tickets/notifications.py +++ b/apps/tickets/notifications.py @@ -2,7 +2,7 @@ from urllib.parse import urljoin from django.conf import settings from django.template.loader import render_to_string -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext_lazy as _ from . import const from notifications.notifications import UserMessage @@ -17,14 +17,18 @@ class BaseTicketMessage(UserMessage): ticket: Ticket content_title: str - @property - def subject(self): - return self.title.format(self.ticket.title, self.ticket.get_type_display()) - @property def ticket_detail_url(self): return urljoin(settings.SITE_URL, const.TICKET_DETAIL_URL.format(id=str(self.ticket.id))) + @property + def content_title(self): + raise NotImplementedError + + @property + def subject(self): + raise NotImplementedError + def get_html_msg(self) -> dict: context = dict( title=self.content_title, @@ -43,8 +47,6 @@ class BaseTicketMessage(UserMessage): class TicketAppliedToAssignee(BaseTicketMessage): - title = _('New Ticket - {} ({})') - def __init__(self, user, ticket): self.ticket = ticket super().__init__(user) @@ -55,6 +57,13 @@ class TicketAppliedToAssignee(BaseTicketMessage): str(self.ticket.applicant_display) ) + @property + def subject(self): + title = _('New Ticket - {} ({})').format( + self.ticket.title, self.ticket.get_type_display() + ) + return title + @classmethod def gen_test_msg(cls): from .models import Ticket @@ -65,8 +74,6 @@ class TicketAppliedToAssignee(BaseTicketMessage): class TicketProcessedToApplicant(BaseTicketMessage): - title = _('Ticket has processed - {} ({})') - def __init__(self, user, ticket, processor): self.ticket = ticket self.processor = processor @@ -76,6 +83,13 @@ class TicketProcessedToApplicant(BaseTicketMessage): def content_title(self): return _('Your ticket has been processed, processor - {}').format(str(self.processor)) + @property + def subject(self): + title = _('Ticket has processed - {} ({})').format( + self.ticket.title, self.ticket.get_type_display() + ) + return title + @classmethod def gen_test_msg(cls): from .models import Ticket diff --git a/apps/tickets/templates/tickets/_base_ticket_body.html b/apps/tickets/templates/tickets/_base_ticket_body.html new file mode 100644 index 000000000..593a1d35e --- /dev/null +++ b/apps/tickets/templates/tickets/_base_ticket_body.html @@ -0,0 +1,20 @@ +{% load i18n %} +
\ No newline at end of file diff --git a/apps/tickets/templates/tickets/_msg_ticket.html b/apps/tickets/templates/tickets/_msg_ticket.html index f789d2e9a..deba0d72a 100644 --- a/apps/tickets/templates/tickets/_msg_ticket.html +++ b/apps/tickets/templates/tickets/_msg_ticket.html @@ -6,6 +6,7 @@
{{ body | safe }}
+
{% trans 'Click here to review' %} From 06de6c35750768cbfd73c283b2cf3f42fd421b1f Mon Sep 17 00:00:00 2001 From: Michael Bai Date: Fri, 22 Oct 2021 14:27:54 +0800 Subject: [PATCH 14/24] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E7=B3=BB=E7=BB=9F=E7=94=A8=E6=88=B7auth-info=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E6=B5=81=E7=A8=8B=E5=8F=8A=E5=88=9B=E5=BB=BA=E6=97=B6?= =?UTF-8?q?=E6=89=8B=E5=8A=A8=E7=99=BB=E5=BD=95=E6=96=B9=E5=BC=8F=E7=9A=84?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/admin_user.py | 2 + apps/assets/api/asset.py | 1 + apps/assets/api/domain.py | 2 + apps/assets/api/system_user.py | 4 +- apps/assets/models/user.py | 56 ++++++++----------- apps/assets/serializers/system_user.py | 49 ++++++++++------ apps/orgs/api.py | 2 + .../api/application/application_permission.py | 2 + apps/perms/api/asset/asset_permission.py | 2 + apps/terminal/api/task.py | 1 + apps/tickets/api/ticket.py | 2 + apps/users/api/group.py | 2 + apps/users/api/user.py | 2 + 13 files changed, 77 insertions(+), 50 deletions(-) diff --git a/apps/assets/api/admin_user.py b/apps/assets/api/admin_user.py index 043a30a1b..e7f1598cd 100644 --- a/apps/assets/api/admin_user.py +++ b/apps/assets/api/admin_user.py @@ -21,6 +21,8 @@ class AdminUserViewSet(OrgBulkModelViewSet): search_fields = filterset_fields serializer_class = serializers.AdminUserSerializer permission_classes = (IsOrgAdmin,) + ordering_fields = ('name',) + ordering = ('name', ) def get_queryset(self): queryset = super().get_queryset().filter(type=SystemUser.Type.admin) diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index 410020d39..321fa5af7 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -50,6 +50,7 @@ class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet) } search_fields = ("hostname", "ip") ordering_fields = ("hostname", "ip", "port", "cpu_cores") + ordering = ('hostname', ) serializer_classes = { 'default': serializers.AssetSerializer, 'suggestion': serializers.MiniAssetSerializer diff --git a/apps/assets/api/domain.py b/apps/assets/api/domain.py index dbf7fa277..fe3255103 100644 --- a/apps/assets/api/domain.py +++ b/apps/assets/api/domain.py @@ -22,6 +22,8 @@ class DomainViewSet(OrgBulkModelViewSet): search_fields = filterset_fields permission_classes = (IsOrgAdminOrAppUser,) serializer_class = serializers.DomainSerializer + ordering_fields = ('name',) + ordering = ('name', ) def get_serializer_class(self): if self.request.query_params.get('gateway'): diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py index f03f2e7d4..01baf000b 100644 --- a/apps/assets/api/system_user.py +++ b/apps/assets/api/system_user.py @@ -41,6 +41,8 @@ class SystemUserViewSet(SuggestionMixin, OrgBulkModelViewSet): 'default': serializers.SystemUserSerializer, 'suggestion': serializers.MiniSystemUserSerializer } + ordering_fields = ('name', 'protocol') + ordering = ('name', ) permission_classes = (IsOrgAdminOrAppUser,) @@ -73,7 +75,7 @@ class SystemUserTempAuthInfoApi(generics.CreateAPIView): with tmp_to_root_org(): instance = get_object_or_404(SystemUser, pk=pk) - instance.set_temp_auth(instance_id, user, data) + instance.set_temp_auth(instance_id, user.id, data) return Response(serializer.data, status=201) diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index 3677144c2..52e3c2af8 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -103,16 +103,23 @@ class AuthMixin: password = cache.get(key) return password - def load_tmp_auth_if_has(self, asset_or_app_id, user): - if not asset_or_app_id or not user: - return + def _clean_auth_info_if_manual_login_mode(self): + if self.login_mode == self.LOGIN_MANUAL: + self.password = '' + self.private_key = '' + self.public_key = '' + def _load_tmp_auth_if_has(self, asset_or_app_id, user_id): if self.login_mode != self.LOGIN_MANUAL: return - auth = self.get_temp_auth(asset_or_app_id, user) + if not asset_or_app_id or not user_id: + return + + auth = self.get_temp_auth(asset_or_app_id, user_id) if not auth: return + username = auth.get('username') password = auth.get('password') @@ -122,17 +129,11 @@ class AuthMixin: self.password = password def load_app_more_auth(self, app_id=None, user_id=None): - from users.models import User - + self._clean_auth_info_if_manual_login_mode() + # 加载临时认证信息 if self.login_mode == self.LOGIN_MANUAL: - self.password = '' - self.private_key = '' - if not user_id: + self._load_tmp_auth_if_has(app_id, user_id) return - user = get_object_or_none(User, pk=user_id) - if not user: - return - self.load_tmp_auth_if_has(app_id, user) def load_asset_special_auth(self, asset, username=''): """ @@ -152,34 +153,25 @@ class AuthMixin: def load_asset_more_auth(self, asset_id=None, username=None, user_id=None): from users.models import User - + self._clean_auth_info_if_manual_login_mode() + # 加载临时认证信息 if self.login_mode == self.LOGIN_MANUAL: - self.password = '' - self.private_key = '' - - asset = None - if asset_id: - asset = get_object_or_none(Asset, pk=asset_id) - # 没有资产就没有必要继续了 - if not asset: - logger.debug('Asset not found, pass') + self._load_tmp_auth_if_has(asset_id, user_id) return - - user = None - if user_id: - user = get_object_or_none(User, pk=user_id) - - _username = self.username + # 更新用户名 + user = get_object_or_none(User, pk=user_id) if user_id else None if self.username_same_with_user: if user and not username: _username = user.username else: _username = username self.username = _username - # 加载某个资产的特殊配置认证信息 - self.load_asset_special_auth(asset, _username) - self.load_tmp_auth_if_has(asset_id, user) + asset = get_object_or_none(Asset, pk=asset_id) if asset_id else None + if not asset: + logger.debug('Asset not found, pass') + return + self.load_asset_special_auth(asset, self.username) class SystemUser(ProtocolMixin, AuthMixin, BaseUser): diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index 16cf9451c..a380c3831 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -123,7 +123,8 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): return '' return home - def validate_sftp_root(self, value): + @staticmethod + def validate_sftp_root(value): if value in ['home', 'tmp']: return value if not value.startswith('/'): @@ -131,19 +132,6 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): raise serializers.ValidationError(error) return value - def validate_admin_user(self, attrs): - if self.instance: - tp = self.instance.type - else: - tp = attrs.get('type') - if tp != SystemUser.Type.admin: - return attrs - attrs['protocol'] = SystemUser.Protocol.ssh - attrs['login_mode'] = SystemUser.LOGIN_AUTO - attrs['username_same_with_user'] = False - attrs['auto_push'] = False - return attrs - def validate_password(self, password): super().validate_password(password) auto_gen_key = self.get_initial_value("auto_generate_key", False) @@ -155,7 +143,20 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): raise serializers.ValidationError(_("Password or private key required")) return password - def validate_gen_key(self, attrs): + def _validate_admin_user(self, attrs): + if self.instance: + tp = self.instance.type + else: + tp = attrs.get('type') + if tp != SystemUser.Type.admin: + return attrs + attrs['protocol'] = SystemUser.Protocol.ssh + attrs['login_mode'] = SystemUser.LOGIN_AUTO + attrs['username_same_with_user'] = False + attrs['auto_push'] = False + return attrs + + def _validate_gen_key(self, attrs): username = attrs.get("username", "manual") auto_gen_key = attrs.pop("auto_generate_key", False) protocol = attrs.get("protocol") @@ -179,9 +180,23 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): attrs["public_key"] = public_key return attrs + def _validate_login_mode(self, attrs): + if 'login_mode' in attrs: + login_mode = attrs['login_mode'] + else: + login_mode = self.instance.login_mode if self.instance else SystemUser.LOGIN_AUTO + + if login_mode == SystemUser.LOGIN_MANUAL: + attrs['password'] = '' + attrs['private_key'] = '' + attrs['public_key'] = '' + + return attrs + def validate(self, attrs): - attrs = self.validate_admin_user(attrs) - attrs = self.validate_gen_key(attrs) + attrs = self._validate_admin_user(attrs) + attrs = self._validate_gen_key(attrs) + attrs = self._validate_login_mode(attrs) return attrs @classmethod diff --git a/apps/orgs/api.py b/apps/orgs/api.py index bc07a6833..283e71469 100644 --- a/apps/orgs/api.py +++ b/apps/orgs/api.py @@ -48,6 +48,8 @@ class OrgViewSet(BulkModelViewSet): queryset = Organization.objects.all() serializer_class = OrgSerializer permission_classes = (IsSuperUserOrAppUser,) + ordering_fields = ('name',) + ordering = ('name', ) def get_serializer_class(self): mapper = { diff --git a/apps/perms/api/application/application_permission.py b/apps/perms/api/application/application_permission.py index 9c3eb18b4..798455053 100644 --- a/apps/perms/api/application/application_permission.py +++ b/apps/perms/api/application/application_permission.py @@ -22,6 +22,8 @@ class ApplicationPermissionViewSet(BasePermissionViewSet): custom_filter_fields = BasePermissionViewSet.custom_filter_fields + [ 'application_id', 'application', 'app', 'app_name' ] + ordering_fields = ('name',) + ordering = ('name', ) def get_queryset(self): queryset = super().get_queryset().prefetch_related( diff --git a/apps/perms/api/asset/asset_permission.py b/apps/perms/api/asset/asset_permission.py index ff4de7d9f..7b3be59c1 100644 --- a/apps/perms/api/asset/asset_permission.py +++ b/apps/perms/api/asset/asset_permission.py @@ -21,3 +21,5 @@ class AssetPermissionViewSet(OrgBulkModelViewSet): serializer_class = serializers.AssetPermissionSerializer filterset_class = AssetPermissionFilter search_fields = ('name',) + ordering_fields = ('name',) + ordering = ('name', ) diff --git a/apps/terminal/api/task.py b/apps/terminal/api/task.py index 474cab65f..137b8481b 100644 --- a/apps/terminal/api/task.py +++ b/apps/terminal/api/task.py @@ -17,6 +17,7 @@ logger = logging.getLogger(__file__) class TaskViewSet(BulkModelViewSet): queryset = Task.objects.all() serializer_class = serializers.TaskSerializer + filterset_fields = ('is_finished',) permission_classes = (IsOrgAdminOrAppUser,) diff --git a/apps/tickets/api/ticket.py b/apps/tickets/api/ticket.py index cbbe72151..86d06f062 100644 --- a/apps/tickets/api/ticket.py +++ b/apps/tickets/api/ticket.py @@ -29,6 +29,8 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet): search_fields = [ 'title', 'action', 'type', 'status', 'applicant_display' ] + ordering_fields = ('title',) + ordering = ('title', ) def create(self, request, *args, **kwargs): raise MethodNotAllowed(self.action) diff --git a/apps/users/api/group.py b/apps/users/api/group.py index 27196c2f3..db8c4109c 100644 --- a/apps/users/api/group.py +++ b/apps/users/api/group.py @@ -16,3 +16,5 @@ class UserGroupViewSet(OrgBulkModelViewSet): search_fields = filterset_fields permission_classes = (IsOrgAdmin,) serializer_class = UserGroupSerializer + ordering_fields = ('name', ) + ordering = ('name', ) diff --git a/apps/users/api/user.py b/apps/users/api/user.py index a1b808a22..2499e7b78 100644 --- a/apps/users/api/user.py +++ b/apps/users/api/user.py @@ -42,6 +42,8 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet): 'invite': InviteSerializer, } extra_filter_backends = [OrgRoleUserFilterBackend] + ordering_fields = ('name',) + ordering = ('name', ) def get_queryset(self): queryset = super().get_queryset().prefetch_related( From dc13134b7be4fd17d9f489e56f0c3621b3240282 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Fri, 22 Oct 2021 16:09:22 +0800 Subject: [PATCH 15/24] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dacl=20=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E6=97=B6=E9=97=B4=E6=AE=B5=E4=B8=BA=E7=A9=BAbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/utils/time_period.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/common/utils/time_period.py b/apps/common/utils/time_period.py index c177e0fb6..c25d63bee 100644 --- a/apps/common/utils/time_period.py +++ b/apps/common/utils/time_period.py @@ -10,6 +10,10 @@ def contains_time_period(time_periods): current_time = local_now().strftime('%H:%M') today_time_period = next(filter(lambda x: str(x['id']) == local_now().strftime("%w"), time_periods)) + today_time_period = today_time_period['value'] + if not today_time_period: + return False + for time in today_time_period['value'].split('、'): start, end = time.split('~') end = "24:00" if end == "00:00" else end From ea485c3070fdd76ff40f42a62992e76a8dd24bf5 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Fri, 22 Oct 2021 16:38:14 +0800 Subject: [PATCH 16/24] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=B7=A5?= =?UTF-8?q?=E5=8D=95=E7=BB=93=E6=9D=9F=E9=87=8D=E5=A4=8D=E5=8F=91=E9=80=81?= =?UTF-8?q?=E5=8F=97=E7=90=86=E4=BA=BA=E6=B6=88=E6=81=AFbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/tickets/handler/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/tickets/handler/base.py b/apps/tickets/handler/base.py index a5b2efdc6..2bd674b0e 100644 --- a/apps/tickets/handler/base.py +++ b/apps/tickets/handler/base.py @@ -25,12 +25,12 @@ class BaseHandler(object): if self.ticket.approval_step != len(self.ticket.process_map): self.ticket.approval_step += 1 self.ticket.create_related_node() + self._send_applied_mail_to_assignees() is_finished = False else: self.ticket.set_state_approve() self.ticket.set_status_closed() is_finished = True - self._send_applied_mail_to_assignees() self.__on_process(self.ticket.processor) return is_finished From c244cf5f430ccd328d09e5660cf49eca84f08af7 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Fri, 22 Oct 2021 20:06:16 +0800 Subject: [PATCH 17/24] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E7=9A=84=E6=B6=88=E6=81=AF=E5=86=85=E5=AE=B9=20(#7061?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: 再次优化通知 * pref: 修改使用的消息内容 * perf: 修复url地址 Co-authored-by: ibuler --- .../authentication/_msg_different_city.html | 4 +- .../authentication/_msg_reset_password.html | 30 +- .../_msg_rest_password_success.html | 16 +- apps/common/utils/timezone.py | 4 + apps/jumpserver/api.py | 1 - apps/jumpserver/context_processor.py | 36 +- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 370 ++++++++++-------- apps/notifications/api/notifications.py | 45 ++- apps/notifications/notifications.py | 71 +++- apps/notifications/urls/api_urls.py | 6 + apps/ops/notifications.py | 87 ++-- .../ops/_msg_terminal_performance.html | 10 + apps/perms/notifications.py | 11 +- apps/perms/tasks.py | 4 +- .../perms/_msg_permed_items_expire.html | 5 +- apps/settings/api/public.py | 29 +- apps/settings/utils/common.py | 13 + apps/terminal/models/terminal.py | 7 +- apps/terminal/notifications.py | 45 ++- .../terminal/_msg_command_alert.html | 29 +- .../terminal/_msg_command_execute_alert.html | 18 +- apps/tickets/notifications.py | 1 + apps/users/notifications.py | 43 +- .../users/_msg_password_expire_reminder.html | 6 +- .../users/templates/users/_msg_reset_mfa.html | 10 +- .../templates/users/_msg_reset_ssh_key.html | 10 +- .../templates/users/_msg_user_created.html | 20 + apps/users/utils.py | 55 +-- 29 files changed, 570 insertions(+), 420 deletions(-) create mode 100644 apps/ops/templates/ops/_msg_terminal_performance.html create mode 100644 apps/users/templates/users/_msg_user_created.html diff --git a/apps/authentication/templates/authentication/_msg_different_city.html b/apps/authentication/templates/authentication/_msg_different_city.html index 16ff90fbc..b12e9aab6 100644 --- a/apps/authentication/templates/authentication/_msg_different_city.html +++ b/apps/authentication/templates/authentication/_msg_different_city.html @@ -12,7 +12,5 @@

- - {% trans 'If you suspect that the login behavior is abnormal, please modify the account password in time.' %} - + {% trans 'If you suspect that the login behavior is abnormal, please modify the account password in time.' %}

\ No newline at end of file diff --git a/apps/authentication/templates/authentication/_msg_reset_password.html b/apps/authentication/templates/authentication/_msg_reset_password.html index 9388f2388..036eea542 100644 --- a/apps/authentication/templates/authentication/_msg_reset_password.html +++ b/apps/authentication/templates/authentication/_msg_reset_password.html @@ -1,15 +1,17 @@ {% load i18n %} -{% trans 'Hello' %} {{ user.name }}, -
-{% trans 'Please click the link below to reset your password, if not your request, concern your account security' %} -
-
-
{% trans 'Click here reset password' %} -
-
-{% trans 'This link is valid for 1 hour. After it expires,' %} {% trans 'request new one' %} -
---- -
-{% trans 'Login direct' %} -
+

+ {% trans 'Hello' %} {{ user.name }}, +

+

+ {% trans 'Please click the link below to reset your password, if not your request, concern your account security' %} +
+
+ {% trans 'Click here reset password' %} +

+ +

+ {% trans 'This link is valid for 1 hour. After it expires' %} + + {% trans 'request new one' %} + +

diff --git a/apps/authentication/templates/authentication/_msg_rest_password_success.html b/apps/authentication/templates/authentication/_msg_rest_password_success.html index 424396473..a12d611f7 100644 --- a/apps/authentication/templates/authentication/_msg_rest_password_success.html +++ b/apps/authentication/templates/authentication/_msg_rest_password_success.html @@ -1,18 +1,14 @@ {% load i18n %} - -

{% trans 'Hello' %}: {{ name }},

+

{% trans 'Hello' %} {{ name }},

- {% trans 'Your password has just been successfully updated.' %} + {% trans 'Your password has just been successfully updated' %}

- {% trans 'IP' %}: {{ ip_address }}
- {% trans 'Browser' %}: {{ browser }} + {% trans 'IP' %}: {{ ip_address }}
+ {% trans 'Browser' %}: {{ browser }}

-

---

- - {% trans 'If the password update was not initiated by you, your account may have security issues.' %}
- {% trans 'If you have any questions, you can contact the administrator.' %} -
+ {% trans 'If the password update was not initiated by you, your account may have security issues' %}
+ {% trans 'If you have any questions, you can contact the administrator' %}

diff --git a/apps/common/utils/timezone.py b/apps/common/utils/timezone.py index 037aad62c..6848e7428 100644 --- a/apps/common/utils/timezone.py +++ b/apps/common/utils/timezone.py @@ -28,6 +28,10 @@ def local_now(): return as_current_tz(utc_now()) +def local_now_display(fmt='%Y-%m-%d %H:%M:%S'): + return local_now().strftime(fmt) + + _rest_dt_field = DateTimeField() dt_parser = _rest_dt_field.to_internal_value dt_formatter = _rest_dt_field.to_representation diff --git a/apps/jumpserver/api.py b/apps/jumpserver/api.py index e67bff1cd..3e7c4f19c 100644 --- a/apps/jumpserver/api.py +++ b/apps/jumpserver/api.py @@ -8,7 +8,6 @@ from django.http.response import JsonResponse, HttpResponse from rest_framework.views import APIView from rest_framework.permissions import AllowAny from collections import Counter -from django.conf import settings from rest_framework.response import Response from users.models import User diff --git a/apps/jumpserver/context_processor.py b/apps/jumpserver/context_processor.py index 93c20c6f3..51a077cca 100644 --- a/apps/jumpserver/context_processor.py +++ b/apps/jumpserver/context_processor.py @@ -4,27 +4,39 @@ from django.templatetags.static import static from django.conf import settings from django.utils.translation import ugettext as _ +default_context = { + 'DEFAULT_PK': '00000000-0000-0000-0000-000000000000', + 'LOGO_URL': static('img/logo.png'), + 'LOGO_TEXT_URL': static('img/logo_text.png'), + 'LOGIN_IMAGE_URL': static('img/login_image.jpg'), + 'FAVICON_URL': static('img/facio.ico'), + 'LOGIN_CAS_LOGO_URL': static('img/login_cas_logo.png'), + 'LOGIN_WECOM_LOGO_URL': static('img/login_wecom_logo.png'), + 'LOGIN_DINGTALK_LOGO_URL': static('img/login_dingtalk_logo.png'), + 'LOGIN_FEISHU_LOGO_URL': static('img/login_feishu_logo.png'), + 'JMS_TITLE': _('JumpServer Open Source Bastion Host'), +} + +default_interface = { + 'login_title': default_context['JMS_TITLE'], + 'logo_logout': default_context['LOGO_URL'], + 'logo_index': default_context['LOGO_TEXT_URL'], + 'login_image': default_context['LOGIN_IMAGE_URL'], + 'favicon': default_context['FAVICON_URL'], +} + def jumpserver_processor(request): # Setting default pk - context = { - 'DEFAULT_PK': '00000000-0000-0000-0000-000000000000', - 'LOGO_URL': static('img/logo.png'), - 'LOGO_TEXT_URL': static('img/logo_text.png'), - 'LOGIN_IMAGE_URL': static('img/login_image.jpg'), - 'FAVICON_URL': static('img/facio.ico'), - 'LOGIN_CAS_LOGO_URL': static('img/login_cas_logo.png'), - 'LOGIN_WECOM_LOGO_URL': static('img/login_wecom_logo.png'), - 'LOGIN_DINGTALK_LOGO_URL': static('img/login_dingtalk_logo.png'), - 'LOGIN_FEISHU_LOGO_URL': static('img/login_feishu_logo.png'), - 'JMS_TITLE': _('JumpServer Open Source Bastion Host'), + context = default_context + context.update({ 'VERSION': settings.VERSION, 'COPYRIGHT': 'FIT2CLOUD 飞致云' + ' © 2014-2021', 'SECURITY_COMMAND_EXECUTION': settings.SECURITY_COMMAND_EXECUTION, 'SECURITY_MFA_VERIFY_TTL': settings.SECURITY_MFA_VERIFY_TTL, 'FORCE_SCRIPT_NAME': settings.FORCE_SCRIPT_NAME, 'SECURITY_VIEW_AUTH_NEED_MFA': settings.SECURITY_VIEW_AUTH_NEED_MFA, - } + }) return context diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 4944330cb..555ebb9f5 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b5a90a96a51ef2f38d4530f3e5b79cd59b07dbca39f60bc35cde5ee0e364b210 -size 91089 +oid sha256:b2663d90ceadd419c40abd472a3b5030b007b1e1db1fcaeda9893ff90cd7cdd6 +size 89955 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index ae72cdc87..c2945ae51 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-10-21 18:50+0800\n" +"POT-Creation-Date: 2021-10-22 19:15+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -85,8 +85,7 @@ msgstr "登录复核" #: orgs/models.py:19 orgs/models.py:433 perms/models/base.py:45 #: templates/index.html:78 terminal/backends/command/models.py:18 #: terminal/backends/command/serializers.py:12 terminal/models/session.py:38 -#: terminal/templates/terminal/_msg_command_alert.html:10 -#: terminal/templates/terminal/_msg_command_execute_alert.html:4 +#: terminal/notifications.py:91 terminal/notifications.py:136 #: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:173 #: users/models/user.py:801 users/models/user.py:827 #: users/serializers/group.py:19 @@ -128,7 +127,7 @@ msgstr "系统用户" #: audits/models.py:38 perms/models/asset_permission.py:99 #: templates/index.html:82 terminal/backends/command/models.py:19 #: terminal/backends/command/serializers.py:13 terminal/models/session.py:40 -#: terminal/templates/terminal/_msg_command_alert.html:7 +#: terminal/notifications.py:90 #: users/templates/users/user_asset_permission.html:40 #: users/templates/users/user_asset_permission.html:70 #: xpack/plugins/change_auth_plan/models/asset.py:195 @@ -154,6 +153,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " #: audits/models.py:105 authentication/forms.py:15 authentication/forms.py:17 #: authentication/templates/authentication/_msg_different_city.html:9 #: ops/models/adhoc.py:148 users/forms/profile.py:31 users/models/user.py:595 +#: users/templates/users/_msg_user_created.html:10 #: users/templates/users/_select_user_modal.html:14 #: xpack/plugins/change_auth_plan/models/asset.py:35 #: xpack/plugins/change_auth_plan/models/asset.py:191 @@ -174,7 +174,7 @@ msgstr "" #: applications/serializers/attrs/application_type/mysql_workbench.py:18 #: assets/models/asset.py:180 assets/models/domain.py:61 #: assets/serializers/account.py:12 -#: authentication/templates/authentication/_msg_rest_password_success.html:9 +#: authentication/templates/authentication/_msg_rest_password_success.html:8 #: settings/serializers/terminal.py:8 #: users/templates/users/_granted_assets.html:26 #: users/templates/users/user_asset_permission.html:156 @@ -384,6 +384,7 @@ msgstr "目标URL" #: authentication/forms.py:22 #: authentication/templates/authentication/login.html:151 #: settings/serializers/auth/ldap.py:44 users/forms/profile.py:21 +#: users/templates/users/_msg_user_created.html:11 #: 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 @@ -647,7 +648,7 @@ msgstr "正则表达式" #: assets/models/cmd_filter.py:41 ops/models/command.py:25 #: terminal/backends/command/serializers.py:15 terminal/models/session.py:49 -#: terminal/templates/terminal/_msg_command_alert.html:4 +#: terminal/templates/terminal/_msg_command_alert.html:12 #: terminal/templates/terminal/_msg_command_execute_alert.html:10 msgid "Command" msgstr "命令" @@ -1445,7 +1446,7 @@ msgstr "{ApplicationPermission} 添加 {SystemUser}" msgid "{ApplicationPermission} REMOVE {SystemUser}" msgstr "{ApplicationPermission} 移除 {SystemUser}" -#: authentication/api/connection_token.py:235 +#: authentication/api/connection_token.py:239 msgid "Invalid token" msgstr "无效的令牌" @@ -1731,6 +1732,7 @@ msgid "Secret" msgstr "密钥" #: authentication/templates/authentication/_access_key_modal.html:33 +#: terminal/notifications.py:93 terminal/notifications.py:138 msgid "Date" msgstr "日期" @@ -1788,8 +1790,8 @@ msgid "Code error" msgstr "代码错误" #: authentication/templates/authentication/_msg_different_city.html:3 -#: authentication/templates/authentication/_msg_reset_password.html:2 -#: authentication/templates/authentication/_msg_rest_password_success.html:3 +#: authentication/templates/authentication/_msg_reset_password.html:3 +#: authentication/templates/authentication/_msg_rest_password_success.html:2 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 #: users/templates/users/_msg_account_expire_reminder.html:4 @@ -1807,53 +1809,48 @@ msgstr "你的账号存在异地登录行为,请关注。" msgid "Login time" msgstr "登录日期" -#: authentication/templates/authentication/_msg_different_city.html:16 +#: authentication/templates/authentication/_msg_different_city.html:15 msgid "" "If you suspect that the login behavior is abnormal, please modify the " "account password in time." msgstr "若怀疑此次登录行为异常,请及时修改账号密码" -#: authentication/templates/authentication/_msg_reset_password.html:4 +#: authentication/templates/authentication/_msg_reset_password.html:6 msgid "" "Please click the link below to reset your password, if not your request, " "concern your account security" msgstr "请点击下面链接重置密码, 如果不是您申请的,请关注账号安全" -#: authentication/templates/authentication/_msg_reset_password.html:7 +#: authentication/templates/authentication/_msg_reset_password.html:9 msgid "Click here reset password" msgstr "点击这里重置密码" -#: authentication/templates/authentication/_msg_reset_password.html:10 -msgid "This link is valid for 1 hour. After it expires," +#: authentication/templates/authentication/_msg_reset_password.html:13 +#: users/templates/users/_msg_user_created.html:17 +msgid "This link is valid for 1 hour. After it expires" msgstr "这个链接有效期1小时, 超过时间您可以" -#: authentication/templates/authentication/_msg_reset_password.html:10 +#: authentication/templates/authentication/_msg_reset_password.html:15 +#: users/templates/users/_msg_user_created.html:18 msgid "request new one" msgstr "重新申请" -#: authentication/templates/authentication/_msg_reset_password.html:14 -#: users/templates/users/_msg_password_expire_reminder.html:22 -#: users/templates/users/_msg_reset_mfa.html:11 -#: users/templates/users/_msg_reset_ssh_key.html:11 -msgid "Login direct" -msgstr "直接登录" +#: authentication/templates/authentication/_msg_rest_password_success.html:5 +msgid "Your password has just been successfully updated" +msgstr "你的密码刚刚成功更新" -#: authentication/templates/authentication/_msg_rest_password_success.html:6 -msgid "Your password has just been successfully updated." -msgstr "你的密码刚刚已经成功更新。" - -#: authentication/templates/authentication/_msg_rest_password_success.html:10 +#: authentication/templates/authentication/_msg_rest_password_success.html:9 msgid "Browser" msgstr "浏览器" -#: authentication/templates/authentication/_msg_rest_password_success.html:15 +#: authentication/templates/authentication/_msg_rest_password_success.html:12 msgid "" "If the password update was not initiated by you, your account may have " -"security issues." -msgstr "如果这次密码更新不是由你发起的,那么你的账号可能存在安全问题。" +"security issues" +msgstr "如果这次密码更新不是由你发起的,那么你的账号可能存在安全问题" -#: authentication/templates/authentication/_msg_rest_password_success.html:16 -msgid "If you have any questions, you can contact the administrator." +#: authentication/templates/authentication/_msg_rest_password_success.html:13 +msgid "If you have any questions, you can contact the administrator" msgstr "如果有疑问或需求,请联系系统管理员" #: authentication/templates/authentication/login.html:143 @@ -2206,7 +2203,7 @@ msgstr "不能包含特殊字符" msgid "The mobile phone number format is incorrect" msgstr "手机号格式不正确" -#: jumpserver/context_processor.py:20 +#: jumpserver/context_processor.py:17 msgid "JumpServer Open Source Bastion Host" msgstr "JumpServer 开源堡垒机" @@ -2246,7 +2243,7 @@ msgstr "邮件" msgid "Site message" msgstr "站内信" -#: notifications/notifications.py:148 ops/models/adhoc.py:246 +#: notifications/notifications.py:167 ops/models/adhoc.py:246 #: xpack/plugins/change_auth_plan/models/base.py:108 #: xpack/plugins/change_auth_plan/models/base.py:190 #: xpack/plugins/gathered_user/models.py:79 @@ -2261,7 +2258,7 @@ msgstr "等待任务开始" msgid "Not has host {} permission" msgstr "没有该主机 {} 权限" -#: ops/apps.py:9 ops/notifications.py:14 +#: ops/apps.py:9 ops/notifications.py:15 msgid "Operations" msgstr "运维" @@ -2383,29 +2380,33 @@ msgstr "命令 `{}` 不允许被执行 ......." msgid "Task end" msgstr "任务结束" -#: ops/notifications.py:15 +#: ops/notifications.py:16 msgid "Server performance" msgstr "监控告警" -#: ops/notifications.py:54 +#: ops/notifications.py:22 +msgid "Terminal health check warning" +msgstr "" + +#: ops/notifications.py:63 #, python-brace-format msgid "The terminal is offline: {name}" msgstr "终端已离线: {name}" -#: ops/notifications.py:60 +#: ops/notifications.py:68 #, python-brace-format -msgid "Disk used more than {max_threshold}%: => {value} ({name})" -msgstr "硬盘使用率超过 {max_threshold}%: => {value} ({name})" +msgid "Disk used more than {max_threshold}%: => {value}" +msgstr "硬盘使用率超过 {max_threshold}%: => {value}" -#: ops/notifications.py:67 +#: ops/notifications.py:73 #, python-brace-format -msgid "Memory used more than {max_threshold}%: => {value} ({name})" -msgstr "内存使用率超过 {max_threshold}%: => {value} ({name})" +msgid "Memory used more than {max_threshold}%: => {value}" +msgstr "内存使用率超过 {max_threshold}%: => {value}" -#: ops/notifications.py:74 +#: ops/notifications.py:78 #, python-brace-format -msgid "CPU load more than {max_threshold}: => {value} ({name})" -msgstr "CPU 使用率超过 {max_threshold}: => {value} ({name})" +msgid "CPU load more than {max_threshold}: => {value}" +msgstr "CPU 使用率超过 {max_threshold}: => {value}" #: ops/tasks.py:71 msgid "Clean task history period" @@ -2533,35 +2534,35 @@ msgstr "失效日期" msgid "From ticket" msgstr "来自工单" -#: perms/notifications.py:19 +#: perms/notifications.py:15 msgid "You permed assets is about to expire" msgstr "你授权的资产即将到期" -#: perms/notifications.py:23 +#: perms/notifications.py:19 msgid "permed assets" msgstr "授权的资产" -#: perms/notifications.py:61 +#: perms/notifications.py:57 msgid "Asset permissions is about to expire" msgstr "资产授权规则将要过期" -#: perms/notifications.py:65 +#: perms/notifications.py:61 msgid "asset permissions of organization {}" msgstr "组织 ({}) 的资产授权" -#: perms/notifications.py:91 +#: perms/notifications.py:87 msgid "Your permed applications is about to expire" msgstr "你授权的应用即将过期" -#: perms/notifications.py:94 +#: perms/notifications.py:90 msgid "permed applications" msgstr "授权的应用" -#: perms/notifications.py:132 +#: perms/notifications.py:128 msgid "Application permissions is about to expire" msgstr "应用授权规则即将过期" -#: perms/notifications.py:135 +#: perms/notifications.py:131 msgid "application permissions of organization {}" msgstr "组织 ({}) 的应用授权" @@ -2633,7 +2634,7 @@ msgstr "" " 以下 %(item_type)s 即将在 3 天后过期\n" " " -#: perms/templates/perms/_msg_permed_items_expire.html:22 +#: perms/templates/perms/_msg_permed_items_expire.html:20 msgid "If you have any question, please contact the administrator" msgstr "如果有疑问或需求,请联系系统管理员" @@ -2671,11 +2672,6 @@ msgstr "获取 LDAP 用户为 None" msgid "Imported {} users successfully (Organization: {})" msgstr "成功导入 {} 个用户 ( 组织: {} )" -#: settings/api/public.py:41 xpack/plugins/interface/api.py:18 -#: xpack/plugins/interface/models.py:36 -msgid "Welcome to the JumpServer open source Bastion Host" -msgstr "欢迎使用JumpServer开源堡垒机" - #: settings/models.py:191 users/templates/users/reset_password.html:29 msgid "Setting" msgstr "设置" @@ -3725,7 +3721,7 @@ msgstr "数据库应用" msgid "Perms" msgstr "权限管理" -#: templates/_nav.html:97 terminal/notifications.py:22 +#: templates/_nav.html:97 terminal/notifications.py:24 msgid "Sessions" msgstr "会话管理" @@ -4068,7 +4064,7 @@ msgstr "输出" #: terminal/backends/command/models.py:23 terminal/models/sharing.py:15 #: terminal/models/sharing.py:58 -#: terminal/templates/terminal/_msg_command_alert.html:16 +#: terminal/templates/terminal/_msg_command_alert.html:10 msgid "Session" msgstr "会话" @@ -4235,11 +4231,15 @@ msgstr "命令存储" msgid "Replay storage" msgstr "录像存储" -#: terminal/notifications.py:68 +#: terminal/notifications.py:70 msgid "Danger command alert" msgstr "危险命令告警" -#: terminal/notifications.py:106 +#: terminal/notifications.py:92 terminal/notifications.py:137 +msgid "Level" +msgstr "级别" + +#: terminal/notifications.py:110 msgid "Batch danger command alert" msgstr "批量危险命令告警" @@ -4348,12 +4348,7 @@ msgstr "" msgid "Not found" msgstr "没有发现" -#: terminal/templates/terminal/_msg_command_alert.html:13 -#: terminal/templates/terminal/_msg_command_execute_alert.html:7 -msgid "Level" -msgstr "级别" - -#: terminal/templates/terminal/_msg_command_alert.html:16 +#: terminal/templates/terminal/_msg_command_alert.html:10 msgid "view" msgstr "查看" @@ -4445,7 +4440,7 @@ msgstr "申请的开始日期" msgid "Applied date expired" msgstr "申请的失效日期" -#: tickets/handler/apply_application.py:79 tickets/handler/apply_asset.py:72 +#: tickets/handler/apply_application.py:79 msgid "" "Created by the ticket, ticket title: {}, ticket applicant: {}, ticket " "processor: {}, ticket ID: {}" @@ -4460,87 +4455,94 @@ msgstr "申请的主机名组" msgid "Applied actions" msgstr "申请的动作" +#: tickets/handler/apply_asset.py:72 +msgid "" +"Created by the ticket ticket title: {} ticket applicant: {} ticket " +"processor: {} ticket ID: {}" +msgstr "" +"通过工单创建, 工单标题: {}, 工单申请人: {}, 工单处理人: {}, 工单 ID: {}" + #: tickets/handler/base.py:86 msgid "{} {} the ticket" -msgstr "{} {}工单" +msgstr "{} {} 工单" -#: tickets/handler/base.py:113 +#: tickets/handler/base.py:114 msgid "Ticket title" msgstr "工单标题" -#: tickets/handler/base.py:114 +#: tickets/handler/base.py:115 msgid "Ticket type" msgstr "工单类型" -#: tickets/handler/base.py:115 +#: tickets/handler/base.py:116 msgid "Ticket status" msgstr "工单状态" -#: tickets/handler/base.py:116 +#: tickets/handler/base.py:117 msgid "Ticket applicant" msgstr "工单申请人" -#: tickets/handler/base.py:118 +#: tickets/handler/base.py:119 msgid "Ticket basic info" msgstr "工单基本信息" -#: tickets/handler/base.py:129 +#: tickets/handler/base.py:130 msgid "No content" msgstr "无内容" -#: tickets/handler/base.py:131 +#: tickets/handler/base.py:132 msgid "Ticket applied info" msgstr "工单申请信息" -#: tickets/handler/command_confirm.py:24 +#: tickets/handler/command_confirm.py:25 msgid "Applied run user" msgstr "申请运行的用户" -#: tickets/handler/command_confirm.py:25 +#: tickets/handler/command_confirm.py:26 msgid "Applied run asset" msgstr "申请运行的资产" -#: tickets/handler/command_confirm.py:26 +#: tickets/handler/command_confirm.py:27 msgid "Applied run system user" msgstr "申请运行的系统用户" -#: tickets/handler/command_confirm.py:27 +#: tickets/handler/command_confirm.py:28 msgid "Applied run command" msgstr "申请运行的命令" -#: tickets/handler/command_confirm.py:28 +#: tickets/handler/command_confirm.py:29 msgid "Applied from session" msgstr "申请来自会话" -#: tickets/handler/command_confirm.py:29 +#: tickets/handler/command_confirm.py:30 msgid "Applied from command filter rules" msgstr "申请来自命令过滤规则" -#: tickets/handler/command_confirm.py:30 +#: tickets/handler/command_confirm.py:31 msgid "Applied from command filter" msgstr "申请来自命令过滤规则" -#: tickets/handler/login_asset_confirm.py:16 +#: tickets/handler/login_asset_confirm.py:17 msgid "Applied login user" msgstr "申请登录的用户" -#: tickets/handler/login_asset_confirm.py:17 +#: tickets/handler/login_asset_confirm.py:18 msgid "Applied login asset" msgstr "申请登录的资产" -#: tickets/handler/login_asset_confirm.py:18 +#: tickets/handler/login_asset_confirm.py:19 msgid "Applied login system user" msgstr "申请登录的系统用户" -#: tickets/handler/login_confirm.py:16 +#: tickets/handler/login_confirm.py:17 msgid "Applied login IP" msgstr "申请登录的IP" -#: tickets/handler/login_confirm.py:17 +#: tickets/handler/login_confirm.py:18 msgid "Applied login city" msgstr "申请登录的城市" -#: tickets/handler/login_confirm.py:18 +#: tickets/handler/login_confirm.py:19 msgid "Applied login datetime" msgstr "申请登录的日期" @@ -4741,7 +4743,8 @@ msgstr "请选择受理人" msgid "The current organization type already exists" msgstr "当前组织已存在该类型" -#: tickets/templates/tickets/_msg_ticket.html:11 +#: tickets/templates/tickets/_base_ticket_body.html:17 +#: tickets/templates/tickets/_msg_ticket.html:12 msgid "Click here to review" msgstr "点击查看" @@ -4901,30 +4904,38 @@ msgstr "管理员" msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" -#: users/notifications.py:20 +#: users/notifications.py:15 +msgid "Create account successfully" +msgstr "创建账户成功" + +#: users/notifications.py:19 +msgid "Hello {}" +msgstr "你好" + +#: users/notifications.py:51 #: users/templates/users/_msg_password_expire_reminder.html:17 #: users/templates/users/reset_password.html:5 #: users/templates/users/reset_password.html:6 msgid "Reset password" msgstr "重置密码" -#: users/notifications.py:50 users/views/profile/reset.py:127 +#: users/notifications.py:81 users/views/profile/reset.py:127 msgid "Reset password success" msgstr "重置密码成功" -#: users/notifications.py:78 +#: users/notifications.py:110 msgid "Password is about expire" msgstr "密码即将过期" -#: users/notifications.py:105 +#: users/notifications.py:137 msgid "Account is about expire" msgstr "账号即将过期" -#: users/notifications.py:127 +#: users/notifications.py:161 msgid "Reset SSH Key" msgstr "重置 SSH 密钥" -#: users/notifications.py:147 +#: users/notifications.py:181 msgid "Reset MFA" msgstr "重置 MFA" @@ -5073,28 +5084,34 @@ msgid "Click here update password" msgstr "点击这里更新密码" #: users/templates/users/_msg_password_expire_reminder.html:16 -msgid "If your password has expired, please click" -msgstr "如果你的密码已过期,先点击" - -#: users/templates/users/_msg_password_expire_reminder.html:18 -msgid "to apply for a password reset email." -msgstr "申请重置" +msgid "If your password has expired, please click the link below to" +msgstr "如果你的密码已过期,请点击" #: users/templates/users/_msg_reset_mfa.html:7 -msgid "Your MFA has been reset by site administrator." -msgstr "你的 MFA 已经被管理员重置。" +msgid "Your MFA has been reset by site administrator" +msgstr "你的 MFA 已经被管理员重置" #: users/templates/users/_msg_reset_mfa.html:8 -msgid "Please login and reset your MFA." -msgstr "请登录并重新设置你的 MFA" +#: users/templates/users/_msg_reset_ssh_key.html:8 +msgid "Please click the link below to set" +msgstr "请点击下面链接设置" + +#: users/templates/users/_msg_reset_mfa.html:11 +#: users/templates/users/_msg_reset_ssh_key.html:11 +msgid "Click here set" +msgstr "点击这里设置" #: users/templates/users/_msg_reset_ssh_key.html:7 -msgid "Your ssh public key has been reset by site administrator." -msgstr "你的 SSH 密钥已经被管理员重置。" +msgid "Your ssh public key has been reset by site administrator" +msgstr "你的 SSH 密钥已经被管理员重置" -#: users/templates/users/_msg_reset_ssh_key.html:8 -msgid "Please login and reset your ssh public key." -msgstr "请登录并重新设置你的密钥" +#: users/templates/users/_msg_user_created.html:8 +msgid "Your account has been created successfully" +msgstr "您的账户已创建成功" + +#: users/templates/users/_msg_user_created.html:13 +msgid "click here to set your password" +msgstr "点击这里设置密码" #: users/templates/users/_select_user_modal.html:5 msgid "Please Select User" @@ -5279,56 +5296,6 @@ msgstr "账号保护已开启,请根据提示完成以下操作" msgid "Open MFA Authenticator and enter the 6-bit dynamic code" msgstr "请打开MFA验证器,输入6位动态码" -# msgid "Update user" -# msgstr "更新用户" -#: users/utils.py:23 -#, python-format -msgid "" -"\n" -"
\n" -"

Your account has been created successfully

\n" -"
\n" -" Username: %(username)s\n" -"
\n" -" Password: \n" -" click here to set your password \n" -" (This link is valid for 1 hour. After it expires, request new one)\n" -"
\n" -"
\n" -"

---

\n" -" Login direct\n" -"
\n" -"
\n" -" " -msgstr "" -"\n" -"
\n" -"

您的账户已创建成功

\n" -"
\n" -" 用户名: %(username)s\n" -"
\n" -" 密码: 请点击这里设置密码 (这个链接有效期1小时, 超过时" -"间您可以 重新申请)\n" -"
\n" -"
\n" -"

---

\n" -" 直接登录\n" -"
\n" -"
\n" -" " - -#: users/utils.py:57 -msgid "Create account successfully" -msgstr "创建账户成功" - -#: users/utils.py:61 -#, python-format -msgid "Hello %(name)s" -msgstr "你好 %(name)s" - #: users/views/profile/otp.py:122 users/views/profile/otp.py:161 #: users/views/profile/otp.py:181 msgid "MFA code invalid, or ntp sync server time" @@ -5923,11 +5890,11 @@ msgstr "资产为空,请更改节点" msgid "Executed times" msgstr "执行次数" -#: xpack/plugins/interface/api.py:68 +#: xpack/plugins/interface/api.py:43 msgid "It is already in the default setting state!" msgstr "当前已经是初始化状态!" -#: xpack/plugins/interface/api.py:72 +#: xpack/plugins/interface/api.py:46 msgid "Restore default successfully." msgstr "恢复默认成功!" @@ -5935,23 +5902,23 @@ msgstr "恢复默认成功!" msgid "Interface settings" msgstr "界面设置" -#: xpack/plugins/interface/models.py:15 +#: xpack/plugins/interface/models.py:16 msgid "Title of login page" msgstr "登录页面标题" -#: xpack/plugins/interface/models.py:19 +#: xpack/plugins/interface/models.py:20 msgid "Image of login page" msgstr "登录页面图片" -#: xpack/plugins/interface/models.py:23 +#: xpack/plugins/interface/models.py:24 msgid "Website icon" msgstr "网站图标" -#: xpack/plugins/interface/models.py:27 +#: xpack/plugins/interface/models.py:28 msgid "Logo of management page" msgstr "管理页面logo" -#: xpack/plugins/interface/models.py:31 +#: xpack/plugins/interface/models.py:32 msgid "Logo of logout page" msgstr "退出页面logo" @@ -5983,6 +5950,69 @@ msgstr "旗舰版" msgid "Community edition" msgstr "社区版" +#~ msgid "Login direct" +#~ msgstr "直接登录" + +#~ msgid "to apply for a password reset email." +#~ msgstr "申请重置" + +#~ msgid "Please login and reset your MFA." +#~ msgstr "请登录并重新设置你的 MFA" + +#~ msgid "Please login and reset your ssh public key." +#~ msgstr "请登录并重新设置你的密钥" + +# msgid "Update user" +# msgstr "更新用户" +#, python-format +#~ msgid "" +#~ "\n" +#~ "
\n" +#~ "

Your account has been created successfully

\n" +#~ "
\n" +#~ " Username: %(username)s\n" +#~ "
\n" +#~ " Password: \n" +#~ " click here to set your password \n" +#~ " (This link is valid for 1 hour. After it expires, request new one)\n" +#~ "
\n" +#~ "
\n" +#~ "

---

\n" +#~ " Login direct\n" +#~ "
\n" +#~ "
\n" +#~ " " +#~ msgstr "" +#~ "\n" +#~ "
\n" +#~ "

您的账户已创建成功

\n" +#~ "
\n" +#~ " 用户名: %(username)s\n" +#~ "
\n" +#~ " 密码: 请点击这里设置密码 (这个链接有效期1小时, 超" +#~ "过时间您可以 重新申请" +#~ ")\n" +#~ "
\n" +#~ "
\n" +#~ "

---

\n" +#~ " 直接登录\n" +#~ "
\n" +#~ "
\n" +#~ " " + +#, python-format +#~ msgid "Hello %(name)s" +#~ msgstr "你好 %(name)s" + +#~ msgid "This link is valid for 1 hour. After it expires," +#~ msgstr "这个链接有效期1小时, 超过时间您可以" + +#~ msgid "Welcome to the JumpServer open source Bastion Host" +#~ msgstr "欢迎使用JumpServer开源堡垒机" + #~ msgid "Login IP" #~ msgstr "登录IP" diff --git a/apps/notifications/api/notifications.py b/apps/notifications/api/notifications.py index 3b58d3edc..e1ad87a43 100644 --- a/apps/notifications/api/notifications.py +++ b/apps/notifications/api/notifications.py @@ -12,7 +12,10 @@ from notifications.serializers import ( UserMsgSubscriptionSerializer, ) -__all__ = ('BackendListView', 'SystemMsgSubscriptionViewSet', 'UserMsgSubscriptionViewSet') +__all__ = ( + 'BackendListView', 'SystemMsgSubscriptionViewSet', + 'UserMsgSubscriptionViewSet', 'get_all_test_messages' +) class BackendListView(APIView): @@ -80,3 +83,43 @@ class UserMsgSubscriptionViewSet(ListModelMixin, queryset = UserMsgSubscription.objects.all() serializer_class = UserMsgSubscriptionSerializer permission_classes = (IsObjectOwner | IsSuperUser, OnlySuperUserCanList) + + +def get_all_test_messages(request): + import textwrap + from ..notifications import Message + from django.shortcuts import HttpResponse + + msgs_cls = Message.get_all_sub_messages() + html_data = '

HTML 格式

' + text_data = '

Text 格式

' + + for msg_cls in msgs_cls: + try: + msg = msg_cls.gen_test_msg() + if not msg: + continue + msg_html = msg.html_msg_with_sign['message'] + msg_text = msg.text_msg_with_sign['message'] + except NotImplementedError: + msg_html = msg_text = '没有实现方法' + except Exception as e: + msg_html = msg_text = 'Error: ' + str(e) + + html_data += """ +

{}

+ {} +
+ """.format(msg_cls.__name__, msg_html) + + text_data += textwrap.dedent(""" +

{}

+
+        {}
+        
+
+
+ """).format(msg_cls.__name__, msg_text) + return HttpResponse(html_data + text_data) + + diff --git a/apps/notifications/notifications.py b/apps/notifications/notifications.py index 3bb8866c2..cb2d5144f 100644 --- a/apps/notifications/notifications.py +++ b/apps/notifications/notifications.py @@ -2,22 +2,23 @@ import traceback from html2text import HTML2Text from typing import Iterable from itertools import chain +import textwrap from celery import shared_task from django.utils.translation import gettext_lazy as _ from common.utils.timezone import local_now from common.utils import lazyproperty +from settings.utils import get_login_title from users.models import User from notifications.backends import BACKEND from .models import SystemMsgSubscription, UserMsgSubscription -__all__ = ('SystemMessage', 'UserMessage', 'system_msgs') +__all__ = ('SystemMessage', 'UserMessage', 'system_msgs', 'Message') system_msgs = [] user_msgs = [] -all_msgs = [] class MessageType(type): @@ -55,7 +56,6 @@ class Message(metaclass=MessageType): - publish 该方法的实现与消息订阅的表结构有关 - send_msg """ - message_type_label: str category: str category_label: str @@ -84,16 +84,13 @@ class Message(metaclass=MessageType): backend = BACKEND(backend) if not backend.is_enable: continue - get_msg_method = getattr(self, f'get_{backend}_msg', self.get_common_msg) - try: - msg = get_msg_method() - except NotImplementedError: - continue - + msg = get_msg_method() client = backend.client() client.send_msg(users, **msg) - except Exception: + except NotImplementedError: + continue + except: traceback.print_exc() @classmethod @@ -111,10 +108,7 @@ class Message(metaclass=MessageType): @staticmethod def get_common_msg() -> dict: - return { - 'subject': '', - 'message': '' - } + return {'subject': '', 'message': ''} def get_html_msg(self) -> dict: return self.get_common_msg() @@ -133,11 +127,39 @@ class Message(metaclass=MessageType): @lazyproperty def text_msg(self) -> dict: - return self.get_text_msg() + msg = self.get_text_msg() + return msg @lazyproperty def html_msg(self) -> dict: - return self.get_html_msg() + msg = self.get_html_msg() + return msg + + @lazyproperty + def html_msg_with_sign(self): + msg = self.get_html_msg() + msg['message'] = textwrap.dedent(""" + {} +
+ — +
+ {} + """).format(msg['message'], self.signature) + return msg + + @lazyproperty + def text_msg_with_sign(self): + msg = self.get_text_msg() + msg['message'] = textwrap.dedent(""" + {} + — + {} + """).format(msg['message'], self.signature) + return msg + + @lazyproperty + def signature(self): + return get_login_title() # -------------------------------------------------------------- # 支持不同发送消息的方式定义自己的消息内容,比如有些支持 html 标签 @@ -159,16 +181,16 @@ class Message(metaclass=MessageType): return self.text_msg def get_email_msg(self) -> dict: - return self.html_msg + return self.html_msg_with_sign def get_site_msg_msg(self) -> dict: return self.html_msg def get_sms_msg(self) -> dict: - return self.text_msg + return self.text_msg_with_sign @classmethod - def test_all_messages(cls): + def get_all_sub_messages(cls): def get_subclasses(cls): """returns all subclasses of argument, cls""" if issubclass(cls, type): @@ -180,6 +202,12 @@ class Message(metaclass=MessageType): return subclasses messages_cls = get_subclasses(cls) + return messages_cls + + @classmethod + def test_all_messages(cls): + messages_cls = cls.get_all_sub_messages() + for _cls in messages_cls: try: msg = _cls.send_test_msg() @@ -225,6 +253,11 @@ class UserMessage(Message): sub = UserMsgSubscription.objects.get(user=self.user) self.send_msg([self.user], sub.receive_backends) + @classmethod + def get_test_user(cls): + from users.models import User + return User.objects.all().first() + @classmethod def gen_test_msg(cls): raise NotImplementedError diff --git a/apps/notifications/urls/api_urls.py b/apps/notifications/urls/api_urls.py index 14ed78e52..a65dd7b79 100644 --- a/apps/notifications/urls/api_urls.py +++ b/apps/notifications/urls/api_urls.py @@ -1,6 +1,7 @@ from rest_framework_bulk.routes import BulkRouter from django.urls import path +from django.conf import settings from notifications import api @@ -14,3 +15,8 @@ router.register('site-message', api.SiteMessageViewSet, 'site-message') urlpatterns = [ path('backends/', api.BackendListView.as_view(), name='backends') ] + router.urls + +if settings.DEBUG: + urlpatterns += [ + path('debug-msgs/', api.get_all_test_messages, name='debug-all-msgs') + ] diff --git a/apps/ops/notifications.py b/apps/ops/notifications.py index 7e0643dcc..442a81cf6 100644 --- a/apps/ops/notifications.py +++ b/apps/ops/notifications.py @@ -1,4 +1,5 @@ from django.utils.translation import gettext_lazy as _ +from django.template.loader import render_to_string from notifications.notifications import SystemMessage from notifications.models import SystemMsgSubscription @@ -14,14 +15,18 @@ class ServerPerformanceMessage(SystemMessage): category_label = _('Operations') message_type_label = _('Server performance') - def __init__(self, msg): - self._msg = msg + def __init__(self, terms_with_errors): + self.terms_with_errors = terms_with_errors def get_html_msg(self) -> dict: - subject = self._msg[:80] + subject = _("Terminal health check warning") + context = { + 'terms_with_errors': self.terms_with_errors + } + message = render_to_string('ops/_msg_terminal_performance.html', context) return { - 'subject': subject.replace('
', '; '), - 'message': self._msg + 'subject': subject, + 'message': message } @classmethod @@ -33,17 +38,21 @@ class ServerPerformanceMessage(SystemMessage): @classmethod def gen_test_msg(cls): - alarm_messages = [] + from terminal.models import Terminal items_mapper = ServerPerformanceCheckUtil.items_mapper - for item, data in items_mapper.items(): - msg = data['alarm_msg_format'] - max_threshold = data['max_threshold'] - value = 123 - msg = msg.format(max_threshold=max_threshold, value=value, name='Fake terminal') - alarm_messages.append(msg) + terms_with_errors = [] + terms = Terminal.objects.all()[:5] - msg = '
'.join(alarm_messages) - return cls(msg) + for i, term in enumerate(terms, 1): + errors = [] + for item, data in items_mapper.items(): + msg = data['alarm_msg_format'] + max_threshold = data['max_threshold'] + value = 123 // i+1 + msg = msg.format(max_threshold=max_threshold, value=value, name=term.name) + errors.append(msg) + terms_with_errors.append([term, errors]) + return cls(terms_with_errors) class ServerPerformanceCheckUtil(object): @@ -56,59 +65,65 @@ class ServerPerformanceCheckUtil(object): 'disk_used': { 'default': 0, 'max_threshold': 80, - 'alarm_msg_format': _( - 'Disk used more than {max_threshold}%: => {value} ({name})' - ) + 'alarm_msg_format': _('Disk used more than {max_threshold}%: => {value}') }, 'memory_used': { 'default': 0, 'max_threshold': 85, - 'alarm_msg_format': _( - 'Memory used more than {max_threshold}%: => {value} ({name})' - ), + 'alarm_msg_format': _('Memory used more than {max_threshold}%: => {value}'), }, 'cpu_load': { 'default': 0, 'max_threshold': 5, - 'alarm_msg_format': _( - 'CPU load more than {max_threshold}: => {value} ({name})' - ), + 'alarm_msg_format': _('CPU load more than {max_threshold}: => {value}'), }, } def __init__(self): - self.alarm_messages = [] + self.terms_with_errors = [] self._terminals = [] - self._terminal = None def check_and_publish(self): self.check() self.publish() def check(self): - self.alarm_messages = [] + self.terms_with_errors = [] self.initial_terminals() - for item, data in self.items_mapper.items(): - for self._terminal in self._terminals: - self.check_item(item, data) - def check_item(self, item, data): + for term in self._terminals: + errors = self.check_terminal(term) + if not errors: + continue + self.terms_with_errors.append((term, errors)) + + def check_terminal(self, term): + errors = [] + for item, data in self.items_mapper.items(): + error = self.check_item(term, item, data) + if not error: + continue + errors.append(error) + return errors + + @staticmethod + def check_item(term, item, data): default = data['default'] max_threshold = data['max_threshold'] - value = getattr(self._terminal.stat, item, default) + value = getattr(term.stat, item, default) + if isinstance(value, bool) and value != max_threshold: return elif isinstance(value, (int, float)) and value < max_threshold: return msg = data['alarm_msg_format'] - msg = msg.format(max_threshold=max_threshold, value=value, name=self._terminal.name) - self.alarm_messages.append(msg) + error = msg.format(max_threshold=max_threshold, value=value, name=term.name) + return error def publish(self): - if not self.alarm_messages: + if not self.terms_with_errors: return - msg = '
'.join(self.alarm_messages) - ServerPerformanceMessage(msg).publish() + ServerPerformanceMessage(self.terms_with_errors).publish() def initial_terminals(self): terminals = [] diff --git a/apps/ops/templates/ops/_msg_terminal_performance.html b/apps/ops/templates/ops/_msg_terminal_performance.html new file mode 100644 index 000000000..8fa86ec11 --- /dev/null +++ b/apps/ops/templates/ops/_msg_terminal_performance.html @@ -0,0 +1,10 @@ +
+ {% for term, errors in terms_with_errors %} +

{{ term.name }}

+
    + {% for error in errors %} +
  • {{ error }}
  • + {% endfor %} +
+ {% endfor %} +
\ No newline at end of file diff --git a/apps/perms/notifications.py b/apps/perms/notifications.py index 4971e7134..54d3f188e 100644 --- a/apps/perms/notifications.py +++ b/apps/perms/notifications.py @@ -1,4 +1,6 @@ +from urllib.parse import urljoin +from django.conf import settings from django.utils.translation import ugettext as _ from django.template.loader import render_to_string @@ -6,7 +8,7 @@ from common.utils import reverse as js_reverse from notifications.notifications import UserMessage -class PermedWillExpireUserMsg(UserMessage): +class PermedAssetsWillExpireUserMsg(UserMessage): def __init__(self, user, assets): super().__init__(user) self.assets = assets @@ -114,12 +116,9 @@ class AppPermsWillExpireForOrgAdminMsg(UserMessage): def get_items_with_url(self): items_with_url = [] + perm_detail_url = urljoin(settings.SITE_URL, '/ui/#/perms/app-permissions/{}') for perm in self.perms: - url = js_reverse( - 'perms:application-permission-detail', - kwargs={'pk': perm.id}, external=True, - api_to_ui=True - ) + f'?oid={perm.org_id}' + url = perm_detail_url.format(perm.id) + f'?oid={perm.org_id}' items_with_url.append([perm.name, url]) return items_with_url diff --git a/apps/perms/tasks.py b/apps/perms/tasks.py index 023913a41..2f5edafed 100644 --- a/apps/perms/tasks.py +++ b/apps/perms/tasks.py @@ -12,7 +12,7 @@ from common.utils import get_logger from common.utils.timezone import local_now, dt_formatter, dt_parser from ops.celery.decorator import register_as_period_task from perms.notifications import ( - PermedWillExpireUserMsg, AssetPermsWillExpireForOrgAdminMsg, + PermedAssetsWillExpireUserMsg, AssetPermsWillExpireForOrgAdminMsg, PermedAppsWillExpireUserMsg, AppPermsWillExpireForOrgAdminMsg ) from perms.models import AssetPermission, ApplicationPermission @@ -83,7 +83,7 @@ def check_asset_permission_will_expired(): user_asset_mapper[u].update(assets) for user, assets in user_asset_mapper.items(): - PermedWillExpireUserMsg(user, assets).publish_async() + PermedAssetsWillExpireUserMsg(user, assets).publish_async() for org, perms in org_perm_mapper.items(): org_admins = org.admins.all() diff --git a/apps/perms/templates/perms/_msg_permed_items_expire.html b/apps/perms/templates/perms/_msg_permed_items_expire.html index 6a73f1b5f..74df4556d 100644 --- a/apps/perms/templates/perms/_msg_permed_items_expire.html +++ b/apps/perms/templates/perms/_msg_permed_items_expire.html @@ -17,8 +17,5 @@

- ---
- - {% trans 'If you have any question, please contact the administrator' %} - + {% trans 'If you have any question, please contact the administrator' %}

diff --git a/apps/settings/api/public.py b/apps/settings/api/public.py index a94ff7e49..ad9c1fbe6 100644 --- a/apps/settings/api/public.py +++ b/apps/settings/api/public.py @@ -1,12 +1,11 @@ from rest_framework import generics from rest_framework.permissions import AllowAny from django.conf import settings -from django.utils.translation import ugettext_lazy as _ -from django.templatetags.static import static from jumpserver.utils import has_valid_xpack_license from common.utils import get_logger from .. import serializers +from ..utils import get_interface_setting logger = get_logger(__name__) @@ -19,30 +18,14 @@ class PublicSettingApi(generics.RetrieveAPIView): @staticmethod def get_logo_urls(): - logo_urls = { - 'logo_logout': static('img/logo.png'), - 'logo_index': static('img/logo_text.png'), - 'login_image': static('img/login_image.jpg'), - 'favicon': static('img/facio.ico') - } - if not settings.XPACK_ENABLED: - return logo_urls - from xpack.plugins.interface.models import Interface - obj = Interface.interface() - if not obj: - return logo_urls - for attr in ['logo_logout', 'logo_index', 'login_image', 'favicon']: - if getattr(obj, attr, '') and getattr(obj, attr).url: - logo_urls.update({attr: getattr(obj, attr).url}) - return logo_urls + interface = get_interface_setting() + keys = ['logo_logout', 'logo_index', 'login_image', 'favicon'] + return {k: interface[k] for k in keys} @staticmethod def get_login_title(): - default_title = _('Welcome to the JumpServer open source Bastion Host') - if not settings.XPACK_ENABLED: - return default_title - from xpack.plugins.interface.models import Interface - return Interface.get_login_title() + interface = get_interface_setting() + return interface['login_title'] def get_object(self): instance = { diff --git a/apps/settings/utils/common.py b/apps/settings/utils/common.py index e64ceaf7f..6289b2711 100644 --- a/apps/settings/utils/common.py +++ b/apps/settings/utils/common.py @@ -1,4 +1,6 @@ # coding: utf-8 +from jumpserver.context_processor import default_interface +from django.conf import settings class ObjectDict(dict): @@ -16,3 +18,14 @@ class ObjectDict(dict): del self[name] else: raise AttributeError("No such attribute: " + name) + + +def get_interface_setting(): + if not settings.XPACK_ENABLED: + return default_interface + from xpack.plugins.interface.models import Interface + return Interface.get_interface_setting() + + +def get_login_title(): + return get_interface_setting()['login_title'] diff --git a/apps/terminal/models/terminal.py b/apps/terminal/models/terminal.py index d15010fef..7ae262603 100644 --- a/apps/terminal/models/terminal.py +++ b/apps/terminal/models/terminal.py @@ -134,11 +134,8 @@ class Terminal(StorageMixin, TerminalStatusMixin, models.Model): @staticmethod def get_login_title_setting(): - login_title = None - if settings.XPACK_ENABLED: - from xpack.plugins.interface.models import Interface - login_title = Interface.get_login_title() - return {'TERMINAL_HEADER_TITLE': login_title} + from settings.utils import get_login_title + return {'TERMINAL_HEADER_TITLE': get_login_title()} @property def config(self): diff --git a/apps/terminal/notifications.py b/apps/terminal/notifications.py index 461bc94a1..8aa7ef0b5 100644 --- a/apps/terminal/notifications.py +++ b/apps/terminal/notifications.py @@ -13,6 +13,8 @@ from notifications.models import SystemMsgSubscription from notifications.backends import BACKEND from orgs.utils import tmp_to_root_org from common.utils import lazyproperty +from common.utils.timezone import local_now_display + logger = get_logger(__name__) @@ -73,25 +75,26 @@ class CommandAlertMessage(CommandAlertMixin, SystemMessage): @classmethod def gen_test_msg(cls): command = Command.objects.first().to_dict() + command['session'] = Session.objects.first().id return cls(command) def get_html_msg(self) -> dict: command = self.command - - with tmp_to_root_org(): - session = Session.objects.get(id=command['session']) session_detail_url = reverse( 'api-terminal:session-detail', kwargs={'pk': command['session']}, external=True, api_to_ui=True - ) + ) + '?oid={}'.format(self.command['org_id']) + level = Command.get_risk_level_str(command['risk_level']) + items = { + _("Asset"): command['asset'], + _("User"): command['user'], + _("Level"): level, + _("Date"): local_now_display(), + } context = { - 'command': command['input'], - 'hostname': command['asset'], - 'host_ip': session.asset_obj.ip, - 'user': command['user'], - 'risk_level': Command.get_risk_level_str(command['risk_level']), - 'session_detail_url': session_detail_url, - 'oid': session.org_id + 'items': items, + 'session_url': session_detail_url, + "command": command['input'], } message = render_to_string('terminal/_msg_command_alert.html', context) return { @@ -122,19 +125,25 @@ class CommandExecutionAlert(CommandAlertMixin, SystemMessage): def get_html_msg(self) -> dict: command = self.command - _input = command['input'] - _input = _input.replace('\n', '
') - assets_with_url = [] for asset in command['assets']: - url = reverse('assets:asset-detail', kwargs={'pk': asset.id}, api_to_ui=True, external=True) + url = reverse( + 'assets:asset-detail', kwargs={'pk': asset.id}, + api_to_ui=True, external=True + ) + '?oid={}'.format(asset.org_id) assets_with_url.append([asset, url]) + level = Command.get_risk_level_str(command['risk_level']) + items = { + _("User"): command['user'], + _("Level"): level, + _("Date"): local_now_display(), + } + context = { - 'command': _input, + 'items': items, 'assets_with_url': assets_with_url, - 'user': command['user'], - 'risk_level': Command.get_risk_level_str(command['risk_level']) + 'command': command['input'], } message = render_to_string('terminal/_msg_command_execute_alert.html', context) return { diff --git a/apps/terminal/templates/terminal/_msg_command_alert.html b/apps/terminal/templates/terminal/_msg_command_alert.html index b980e87bc..4b7eb26ed 100644 --- a/apps/terminal/templates/terminal/_msg_command_alert.html +++ b/apps/terminal/templates/terminal/_msg_command_alert.html @@ -1,17 +1,16 @@ {% load i18n %} -

- {% trans 'Command' %}: {{ command }} -

-

- {% trans 'Asset' %}: {{ hostname }}({{ host_ip }}) -

-

- {% trans 'User' %}: {{ user }} -

-

- {% trans 'Level' %}: {{ risk_level }} -

-

- {% trans 'Session' %}: {% trans 'view' %} -

+
+ {% for item, value in items.items %} + + {{ item }}: {{ value }} + +
+ {% endfor %} + {% trans 'Session' %}: {% trans 'view' %} +
+ {% trans 'Command' %}:
+
+{{ command }}
+    
+
diff --git a/apps/terminal/templates/terminal/_msg_command_execute_alert.html b/apps/terminal/templates/terminal/_msg_command_execute_alert.html index e9136c6a1..61022d156 100644 --- a/apps/terminal/templates/terminal/_msg_command_execute_alert.html +++ b/apps/terminal/templates/terminal/_msg_command_execute_alert.html @@ -1,16 +1,16 @@ {% load i18n %} -

- {% trans 'User' %}: {{ user }} -

-

- {% trans 'Level' %}: {{ risk_level }} -

- {% trans 'Command' %}:
-
+{% for item, value in items.items %}
+    
+        {{ item }}: {{ value }}
+    
+    
+{% endfor %} + {% trans 'Command' %}:
+
 {{ command }}
-
+
{% trans 'Assets' %}:
diff --git a/apps/tickets/notifications.py b/apps/tickets/notifications.py index 2df08845f..a70e6d497 100644 --- a/apps/tickets/notifications.py +++ b/apps/tickets/notifications.py @@ -19,6 +19,7 @@ class BaseTicketMessage(UserMessage): @property def ticket_detail_url(self): + tp = self.ticket.type return urljoin(settings.SITE_URL, const.TICKET_DETAIL_URL.format(id=str(self.ticket.id))) @property diff --git a/apps/users/notifications.py b/apps/users/notifications.py index ab04b6fae..83ab9c532 100644 --- a/apps/users/notifications.py +++ b/apps/users/notifications.py @@ -1,4 +1,3 @@ -from datetime import datetime from urllib.parse import urljoin from django.utils import timezone @@ -10,6 +9,38 @@ from common.utils import reverse, get_request_ip_or_data, get_request_user_agent from notifications.notifications import UserMessage +class UserCreatedMsg(UserMessage): + def get_html_msg(self) -> dict: + user = self.user + subject = _('Create account successfully') + if settings.EMAIL_CUSTOM_USER_CREATED_SUBJECT: + subject = settings.EMAIL_CUSTOM_USER_CREATED_SUBJECT + + honorific = settings.EMAIL_CUSTOM_USER_CREATED_HONORIFIC or _('Hello {}').format(user.name) + signature = settings.EMAIL_CUSTOM_USER_CREATED_SIGNATURE or 'JumpServer' + + context = { + 'honorific': honorific, + 'signature': signature, + 'username': user.username, + 'rest_password_url': reverse('authentication:reset-password', external=True), + 'rest_password_token': user.generate_reset_token(), + 'forget_password_url': reverse('authentication:forgot-password', external=True), + 'email': user.email, + 'login_url': reverse('authentication:login', external=True), + } + message = render_to_string('users/_msg_user_created.html', context) + return { + 'subject': subject, + 'message': message + } + + @classmethod + def gen_test_msg(cls): + user = cls.get_test_user() + return cls(user) + + class ResetPasswordMsg(UserMessage): def __init__(self, user): super().__init__(user) @@ -71,18 +102,17 @@ class ResetPasswordSuccessMsg(UserMessage): class PasswordExpirationReminderMsg(UserMessage): - update_password_url = urljoin(settings.SITE_URL, '/ui/#/users/profile/?activeTab=PasswordUpdate') - def get_html_msg(self) -> dict: user = self.user subject = _('Password is about expire') date_password_expired_local = timezone.localtime(user.date_password_expired) + update_password_url = urljoin(settings.SITE_URL, '/ui/#/users/profile/?activeTab=PasswordUpdate') date_password_expired = date_password_expired_local.strftime('%Y-%m-%d %H:%M:%S') context = { 'name': user.name, 'date_password_expired': date_password_expired, - 'update_password_url': self.update_password_url, + 'update_password_url': update_password_url, 'forget_password_url': reverse('authentication:forgot-password', external=True), 'email': user.email, 'login_url': reverse('authentication:login', external=True), @@ -125,9 +155,10 @@ class UserExpirationReminderMsg(UserMessage): class ResetSSHKeyMsg(UserMessage): def get_html_msg(self) -> dict: subject = _('Reset SSH Key') + update_url = urljoin(settings.SITE_URL, '/ui/#/users/profile/?activeTab=SSHUpdate') context = { 'name': self.user.name, - 'login_url': reverse('authentication:login', external=True), + 'url': update_url, } message = render_to_string('users/_msg_reset_ssh_key.html', context) return { @@ -147,7 +178,7 @@ class ResetMFAMsg(UserMessage): subject = _('Reset MFA') context = { 'name': self.user.name, - 'login_url': reverse('authentication:login', external=True), + 'url': reverse('authentication:user-otp-enable-start', external=True), } message = render_to_string('users/_msg_reset_mfa.html', context) return { diff --git a/apps/users/templates/users/_msg_password_expire_reminder.html b/apps/users/templates/users/_msg_password_expire_reminder.html index 5037a332f..0c8175c23 100644 --- a/apps/users/templates/users/_msg_password_expire_reminder.html +++ b/apps/users/templates/users/_msg_password_expire_reminder.html @@ -13,10 +13,6 @@

- {% trans 'If your password has expired, please click' %} + {% trans 'If your password has expired, please click the link below to' %} {% trans 'Reset password' %} - {% trans 'to apply for a password reset email.' %}

---- -
-{% trans 'Login direct' %} \ No newline at end of file diff --git a/apps/users/templates/users/_msg_reset_mfa.html b/apps/users/templates/users/_msg_reset_mfa.html index 073d97ccb..c214adef2 100644 --- a/apps/users/templates/users/_msg_reset_mfa.html +++ b/apps/users/templates/users/_msg_reset_mfa.html @@ -4,9 +4,9 @@ {% trans 'Hello' %} {{ name }},

- {% trans 'Your MFA has been reset by site administrator.' %}
- {% trans 'Please login and reset your MFA.' %} + {% trans 'Your MFA has been reset by site administrator' %}
+ {% trans 'Please click the link below to set' %} +
+
+ {% trans 'Click here set' %}

-

- {% trans 'Login direct' %} -

\ No newline at end of file diff --git a/apps/users/templates/users/_msg_reset_ssh_key.html b/apps/users/templates/users/_msg_reset_ssh_key.html index ab705aca7..58f97524a 100644 --- a/apps/users/templates/users/_msg_reset_ssh_key.html +++ b/apps/users/templates/users/_msg_reset_ssh_key.html @@ -4,9 +4,9 @@ {% trans 'Hello' %} {{ name }},

- {% trans 'Your ssh public key has been reset by site administrator.' %}
- {% trans 'Please login and reset your ssh public key.' %} + {% trans 'Your ssh public key has been reset by site administrator' %}
+ {% trans 'Please click the link below to set' %} +
+
+ {% trans 'Click here set' %}

-

- {% trans 'Login direct' %} -

\ No newline at end of file diff --git a/apps/users/templates/users/_msg_user_created.html b/apps/users/templates/users/_msg_user_created.html new file mode 100644 index 000000000..c0515e9f6 --- /dev/null +++ b/apps/users/templates/users/_msg_user_created.html @@ -0,0 +1,20 @@ +{% load i18n %} + +

+ {{ honorific }}: +

+ +
+

{% trans 'Your account has been created successfully' %}

+

+ {% trans 'Username' %}: {{ username }}
+ {% trans 'Password' %}: + + {% trans 'click here to set your password' %} + +

+

+ {% trans 'This link is valid for 1 hour. After it expires' %} + {% trans 'request new one' %} +

+
diff --git a/apps/users/utils.py b/apps/users/utils.py index b89dacdb2..8be6a4928 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -8,67 +8,24 @@ import logging import time from django.conf import settings -from django.utils.translation import ugettext as _ from django.core.cache import cache from common.tasks import send_mail_async -from common.utils import reverse, get_object_or_none, get_request_ip_or_data, get_request_user_agent +from common.utils import reverse, get_object_or_none from .models import User logger = logging.getLogger('jumpserver') -def construct_user_created_email_body(user): - default_body = _(""" -
-

Your account has been created successfully

-
- Username: %(username)s -
- Password: - click here to set your password - (This link is valid for 1 hour. After it expires, request new one) -
-
-

---

- Login direct -
-
- """) % { - 'username': user.username, - 'rest_password_url': reverse('authentication:reset-password', external=True), - 'rest_password_token': user.generate_reset_token(), - 'forget_password_url': reverse('authentication:forgot-password', external=True), - 'email': user.email, - 'login_url': reverse('authentication:login', external=True), - } - - if settings.EMAIL_CUSTOM_USER_CREATED_BODY: - custom_body = '

' + settings.EMAIL_CUSTOM_USER_CREATED_BODY + '

' - else: - custom_body = '' - body = custom_body + default_body - return body - - def send_user_created_mail(user): + from .notifications import UserCreatedMsg + recipient_list = [user.email] - subject = _('Create account successfully') - if settings.EMAIL_CUSTOM_USER_CREATED_SUBJECT: - subject = settings.EMAIL_CUSTOM_USER_CREATED_SUBJECT + msg = UserCreatedMsg.html_msg + subject = msg['subject'] + message = msg['message'] - honorific = '

' + _('Hello %(name)s') % {'name': user.name} + ':

' - if settings.EMAIL_CUSTOM_USER_CREATED_HONORIFIC: - honorific = '

' + settings.EMAIL_CUSTOM_USER_CREATED_HONORIFIC + ':

' - - body = construct_user_created_email_body(user) - - signature = '

jumpserver

' - if settings.EMAIL_CUSTOM_USER_CREATED_SIGNATURE: - signature = '

' + settings.EMAIL_CUSTOM_USER_CREATED_SIGNATURE + '

' - - message = honorific + body + signature if settings.DEBUG: try: print(message) From cd5094f10d90e00fac0afbb738efd4107945ec27 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 25 Oct 2021 10:44:19 +0800 Subject: [PATCH 18/24] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E9=80=9A?= =?UTF-8?q?=E7=9F=A5=E4=B8=AD=E7=9A=84=E8=BF=9E=E6=8E=A5=E7=82=B9=E5=87=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/authentication/_msg_reset_password.html | 4 +++- apps/perms/templates/perms/_msg_item_permissions_expire.html | 2 +- apps/terminal/templates/terminal/_msg_command_alert.html | 2 +- .../templates/terminal/_msg_command_execute_alert.html | 2 +- apps/tickets/templates/tickets/_msg_ticket.html | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/authentication/templates/authentication/_msg_reset_password.html b/apps/authentication/templates/authentication/_msg_reset_password.html index 036eea542..7bb80a17d 100644 --- a/apps/authentication/templates/authentication/_msg_reset_password.html +++ b/apps/authentication/templates/authentication/_msg_reset_password.html @@ -6,7 +6,9 @@ {% trans 'Please click the link below to reset your password, if not your request, concern your account security' %}

- {% trans 'Click here reset password' %} + + {% trans 'Click here reset password' %} +

diff --git a/apps/perms/templates/perms/_msg_item_permissions_expire.html b/apps/perms/templates/perms/_msg_item_permissions_expire.html index f96b5e61d..139f958b3 100644 --- a/apps/perms/templates/perms/_msg_item_permissions_expire.html +++ b/apps/perms/templates/perms/_msg_item_permissions_expire.html @@ -11,6 +11,6 @@

diff --git a/apps/terminal/templates/terminal/_msg_command_alert.html b/apps/terminal/templates/terminal/_msg_command_alert.html index 4b7eb26ed..a3447c97d 100644 --- a/apps/terminal/templates/terminal/_msg_command_alert.html +++ b/apps/terminal/templates/terminal/_msg_command_alert.html @@ -7,7 +7,7 @@
{% endfor %} - {% trans 'Session' %}: {% trans 'view' %} + {% trans 'Session' %}: {% trans 'view' %}
{% trans 'Command' %}:
diff --git a/apps/terminal/templates/terminal/_msg_command_execute_alert.html b/apps/terminal/templates/terminal/_msg_command_execute_alert.html
index 61022d156..ff59e0c4b 100644
--- a/apps/terminal/templates/terminal/_msg_command_execute_alert.html
+++ b/apps/terminal/templates/terminal/_msg_command_execute_alert.html
@@ -17,7 +17,7 @@
     
diff --git a/apps/tickets/templates/tickets/_msg_ticket.html b/apps/tickets/templates/tickets/_msg_ticket.html
index deba0d72a..15e2b2a3f 100644
--- a/apps/tickets/templates/tickets/_msg_ticket.html
+++ b/apps/tickets/templates/tickets/_msg_ticket.html
@@ -8,7 +8,7 @@
     

From 1f50a2fe33fd554fb404a5541b9089c3f5c8e676 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 25 Oct 2021 10:08:49 +0800 Subject: [PATCH 19/24] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E7=AD=BE?= =?UTF-8?q?=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/notifications/notifications.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/notifications/notifications.py b/apps/notifications/notifications.py index cb2d5144f..11e8f7011 100644 --- a/apps/notifications/notifications.py +++ b/apps/notifications/notifications.py @@ -140,10 +140,12 @@ class Message(metaclass=MessageType): msg = self.get_html_msg() msg['message'] = textwrap.dedent(""" {} +

- {} + {} +
""").format(msg['message'], self.signature) return msg From 0b67c7a9539b1537ab9794936ea6f18fa03d1cc6 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Sat, 23 Oct 2021 16:12:24 +0800 Subject: [PATCH 20/24] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=99=BB?= =?UTF-8?q?=E9=99=86=E5=A4=8D=E5=90=88acl=E6=97=B6=20=E6=9C=AA=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E8=A7=84=E5=88=99bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/acls/migrations/0002_auto_20210926_1047.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/acls/migrations/0002_auto_20210926_1047.py b/apps/acls/migrations/0002_auto_20210926_1047.py index 05e0e7cf1..fa45cd25a 100644 --- a/apps/acls/migrations/0002_auto_20210926_1047.py +++ b/apps/acls/migrations/0002_auto_20210926_1047.py @@ -7,6 +7,8 @@ from acls.models import LoginACL LOGIN_CONFIRM_ZH = '登录复核' LOGIN_CONFIRM_EN = 'Login confirm' +DEFAULT_TIME_PERIODS = [{'id': i, 'value': '00:00~00:00'} for i in range(7)] + def has_zh(name: str) -> bool: for i in name: @@ -31,7 +33,8 @@ def migrate_login_confirm(apps, schema_editor): 'user': user, 'name': f'{user.name}-{login_confirm} ({date_created})', 'created_by': instance.created_by, - 'action': LoginACL.ActionChoices.confirm + 'action': LoginACL.ActionChoices.confirm, + 'rules': {'ip_group': ['*'], 'time_period': DEFAULT_TIME_PERIODS} } instance = login_acl_model.objects.create(**data) instance.reviewers.set(reviewers) @@ -39,11 +42,10 @@ def migrate_login_confirm(apps, schema_editor): def migrate_ip_group(apps, schema_editor): login_acl_model = apps.get_model("acls", "LoginACL") - default_time_periods = [{'id': i, 'value': '00:00~00:00'} for i in range(7)] updates = list() with transaction.atomic(): for instance in login_acl_model.objects.exclude(action=LoginACL.ActionChoices.confirm): - instance.rules = {'ip_group': instance.ip_group, 'time_period': default_time_periods} + instance.rules = {'ip_group': instance.ip_group, 'time_period': DEFAULT_TIME_PERIODS} updates.append(instance) login_acl_model.objects.bulk_update(updates, ['rules', ]) From 52889cb67a6cca05517c5cdb29a9dc8e65449e77 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Mon, 25 Oct 2021 11:03:54 +0800 Subject: [PATCH 21/24] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=B7=A5?= =?UTF-8?q?=E5=8D=95=E5=8F=91=E9=82=AE=E4=BB=B6=E5=8F=8A=E6=8E=88=E6=9D=83?= =?UTF-8?q?=E8=B5=84=E4=BA=A7=E5=A4=87=E6=B3=A8=E4=BF=A1=E6=81=AF=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/tickets/handler/apply_application.py | 2 +- apps/tickets/handler/apply_asset.py | 2 +- apps/tickets/handler/base.py | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/tickets/handler/apply_application.py b/apps/tickets/handler/apply_application.py index 9a504f39f..5cfc06e38 100644 --- a/apps/tickets/handler/apply_application.py +++ b/apps/tickets/handler/apply_application.py @@ -84,7 +84,7 @@ class Handler(BaseHandler): ).format( self.ticket.title, self.ticket.applicant_display, - str(self.ticket.processor), + ','.join([i['processor_display'] for i in self.ticket.process_map]), str(self.ticket.id) ) permissions_data = { diff --git a/apps/tickets/handler/apply_asset.py b/apps/tickets/handler/apply_asset.py index 883c7f45b..f457efdba 100644 --- a/apps/tickets/handler/apply_asset.py +++ b/apps/tickets/handler/apply_asset.py @@ -77,7 +77,7 @@ class Handler(BaseHandler): ).format( self.ticket.title, self.ticket.applicant_display, - str(self.ticket.processor), + ','.join([i['processor_display'] for i in self.ticket.process_map]), str(self.ticket.id) ) diff --git a/apps/tickets/handler/base.py b/apps/tickets/handler/base.py index 2bd674b0e..5e13b7a60 100644 --- a/apps/tickets/handler/base.py +++ b/apps/tickets/handler/base.py @@ -23,16 +23,18 @@ class BaseHandler(object): def _on_approve(self): if self.ticket.approval_step != len(self.ticket.process_map): + self._send_processed_mail_to_applicant(self.ticket.processor) self.ticket.approval_step += 1 self.ticket.create_related_node() self._send_applied_mail_to_assignees() is_finished = False else: + self._send_processed_mail_to_applicant(self.ticket.processor) self.ticket.set_state_approve() self.ticket.set_status_closed() is_finished = True - self.__on_process(self.ticket.processor) + self.ticket.save() return is_finished def _on_reject(self): From 25d1b7144832e8ea4d95bc9f3964e8dee4b27847 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Mon, 25 Oct 2021 14:24:43 +0800 Subject: [PATCH 22/24] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=88=86?= =?UTF-8?q?=E6=97=B6=E7=99=BB=E9=99=86bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/utils/time_period.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/common/utils/time_period.py b/apps/common/utils/time_period.py index c25d63bee..cf4f0f0b9 100644 --- a/apps/common/utils/time_period.py +++ b/apps/common/utils/time_period.py @@ -14,7 +14,7 @@ def contains_time_period(time_periods): if not today_time_period: return False - for time in today_time_period['value'].split('、'): + for time in today_time_period.split('、'): start, end = time.split('~') end = "24:00" if end == "00:00" else end if start <= current_time <= end: From 7d56678a8e830a6add5b250ea2163a089fca0210 Mon Sep 17 00:00:00 2001 From: xinwen Date: Mon, 25 Oct 2021 11:32:13 +0800 Subject: [PATCH 23/24] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E6=AC=A1=E6=95=B0=E5=87=BA=E7=8E=B0=200?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/errors.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/authentication/errors.py b/apps/authentication/errors.py index 79adb4904..a1c7dd427 100644 --- a/apps/authentication/errors.py +++ b/apps/authentication/errors.py @@ -134,6 +134,10 @@ class CredentialError(AuthFailedNeedLogMixin, AuthFailedNeedBlockMixin, AuthFail times_remainder = util.get_remainder_times() block_time = settings.SECURITY_LOGIN_LIMIT_TIME + if times_remainder < 1: + self.msg = block_login_msg.format(settings.SECURITY_LOGIN_LIMIT_TIME) + return + default_msg = invalid_login_msg.format( times_try=times_remainder, block_time=block_time ) From 84326cc99948d12c10a0c07b072ebb9a56204b41 Mon Sep 17 00:00:00 2001 From: Michael Bai Date: Mon, 25 Oct 2021 14:58:59 +0800 Subject: [PATCH 24/24] =?UTF-8?q?fix:=20=E7=B3=BB=E7=BB=9F=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E5=88=97=E8=A1=A8=E6=B7=BB=E5=8A=A0=20=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E6=95=B0=E9=87=8F=20=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/base.py | 17 +++ apps/assets/serializers/system_user.py | 5 +- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 176 +++++++++++++------------ 4 files changed, 112 insertions(+), 90 deletions(-) diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index d3ac5f493..38b7218c4 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -185,10 +185,18 @@ class BaseUser(OrgModelMixin, AuthMixin): ASSETS_AMOUNT_CACHE_KEY = "ASSET_USER_{}_ASSETS_AMOUNT" ASSET_USER_CACHE_TIME = 600 + APPS_AMOUNT_CACHE_KEY = "APP_USER_{}_APPS_AMOUNT" + APP_USER_CACHE_TIME = 600 + def get_related_assets(self): assets = self.assets.filter(org_id=self.org_id) return assets + def get_related_apps(self): + from applications.models import Account + apps = Account.objects.filter(systemuser=self) + return apps + def get_username(self): return self.username @@ -201,6 +209,15 @@ class BaseUser(OrgModelMixin, AuthMixin): cache.set(cache_key, cached, self.ASSET_USER_CACHE_TIME) return cached + @property + def apps_amount(self): + cache_key = self.APPS_AMOUNT_CACHE_KEY.format(self.id) + cached = cache.get(cache_key) + if not cached: + cached = self.get_related_apps().count() + cache.set(cache_key, cached, self.APP_USER_CACHE_TIME) + return cached + def expire_assets_amount(self): cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id) cache.delete(cache_key) diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index a380c3831..85968ca25 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -26,6 +26,7 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): auto_generate_key = serializers.BooleanField(initial=True, required=False, write_only=True) type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type display')) ssh_key_fingerprint = serializers.ReadOnlyField(label=_('SSH key fingerprint')) + applications_amount = serializers.IntegerField(source='apps_amount', label=_('Apps amount')) class Meta: model = SystemUser @@ -39,7 +40,7 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): 'username_same_with_user', 'auto_push', 'auto_generate_key', 'date_created', 'date_updated', 'comment', 'created_by', ] - fields_m2m = ['cmd_filters', 'assets_amount', 'nodes'] + fields_m2m = ['cmd_filters', 'assets_amount', 'applications_amount', 'nodes'] fields = fields_small + fields_m2m extra_kwargs = { 'password': { @@ -203,7 +204,7 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ queryset = queryset\ - .annotate(assets_amount=Count("assets"))\ + .annotate(assets_amount=Count("assets")) \ .prefetch_related('nodes', 'cmd_filters') return queryset diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 555ebb9f5..dac4a4b09 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b2663d90ceadd419c40abd472a3b5030b007b1e1db1fcaeda9893ff90cd7cdd6 -size 89955 +oid sha256:55a2062981ea7eef4ca28142f325f52e15cb7679ad0a2600234a5bdb6d005c87 +size 89996 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index c2945ae51..5dcdd8488 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-10-22 19:15+0800\n" +"POT-Creation-Date: 2021-10-25 14:56+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -35,12 +35,12 @@ msgid "Name" msgstr "名称" #: acls/models/base.py:27 assets/models/cmd_filter.py:54 -#: assets/models/user.py:207 +#: assets/models/user.py:199 msgid "Priority" msgstr "优先级" #: acls/models/base.py:28 assets/models/cmd_filter.py:54 -#: assets/models/user.py:207 +#: assets/models/user.py:199 msgid "1-100, the lower the value will be match first" msgstr "优先级可选范围为 1-100 (数值越小越优先)" @@ -85,7 +85,7 @@ msgstr "登录复核" #: orgs/models.py:19 orgs/models.py:433 perms/models/base.py:45 #: templates/index.html:78 terminal/backends/command/models.py:18 #: terminal/backends/command/serializers.py:12 terminal/models/session.py:38 -#: terminal/notifications.py:91 terminal/notifications.py:136 +#: terminal/notifications.py:90 terminal/notifications.py:138 #: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:173 #: users/models/user.py:801 users/models/user.py:827 #: users/serializers/group.py:19 @@ -123,11 +123,11 @@ msgstr "系统用户" #: acls/models/login_asset_acl.py:22 #: applications/serializers/attrs/application_category/remote_app.py:37 #: assets/models/asset.py:357 assets/models/authbook.py:18 -#: assets/models/gathered_user.py:14 assets/serializers/system_user.py:215 +#: assets/models/gathered_user.py:14 assets/serializers/system_user.py:231 #: audits/models.py:38 perms/models/asset_permission.py:99 #: templates/index.html:82 terminal/backends/command/models.py:19 #: terminal/backends/command/serializers.py:13 terminal/models/session.py:40 -#: terminal/notifications.py:90 +#: terminal/notifications.py:89 #: users/templates/users/user_asset_permission.html:40 #: users/templates/users/user_asset_permission.html:70 #: xpack/plugins/change_auth_plan/models/asset.py:195 @@ -196,7 +196,7 @@ msgid "" msgstr "格式为逗号分隔的字符串, * 表示匹配所有. 可选的协议有: {}" #: acls/serializers/login_asset_acl.py:55 assets/models/asset.py:184 -#: assets/models/domain.py:63 assets/models/user.py:208 +#: assets/models/domain.py:63 assets/models/user.py:200 #: terminal/serializers/session.py:30 terminal/serializers/storage.py:69 msgid "Protocol" msgstr "协议" @@ -252,7 +252,7 @@ msgid "Custom" msgstr "自定义" #: applications/models/account.py:11 assets/models/authbook.py:19 -#: assets/models/user.py:281 audits/models.py:39 +#: assets/models/user.py:273 audits/models.py:39 #: perms/models/application_permission.py:32 #: perms/models/asset_permission.py:101 templates/_nav.html:45 #: terminal/backends/command/models.py:20 @@ -293,7 +293,7 @@ msgstr "类别" #: applications/models/application.py:171 #: applications/serializers/application.py:90 assets/models/cmd_filter.py:53 -#: assets/models/user.py:206 perms/models/application_permission.py:23 +#: assets/models/user.py:198 perms/models/application_permission.py:23 #: perms/serializers/application/user_permission.py:34 #: terminal/models/storage.py:55 terminal/models/storage.py:116 #: tickets/models/flow.py:51 tickets/models/ticket.py:48 @@ -403,7 +403,7 @@ msgstr "运行参数" msgid "Target url" msgstr "目标URL" -#: assets/api/domain.py:50 +#: assets/api/domain.py:52 msgid "Number required" msgstr "需要为数字" @@ -447,7 +447,7 @@ msgstr "系统平台" msgid "Protocols" msgstr "协议组" -#: assets/models/asset.py:189 assets/models/user.py:198 +#: assets/models/asset.py:189 assets/models/user.py:190 #: perms/models/asset_permission.py:100 #: xpack/plugins/change_auth_plan/models/asset.py:44 #: xpack/plugins/gathered_user/models.py:24 @@ -460,7 +460,7 @@ msgid "Is active" msgstr "激活" #: assets/models/asset.py:193 assets/models/cluster.py:19 -#: assets/models/user.py:195 assets/models/user.py:330 templates/_nav.html:44 +#: assets/models/user.py:187 assets/models/user.py:322 templates/_nav.html:44 msgid "Admin user" msgstr "特权用户" @@ -638,7 +638,7 @@ msgstr "系统" msgid "Default Cluster" msgstr "默认Cluster" -#: assets/models/cmd_filter.py:33 assets/models/user.py:213 +#: assets/models/cmd_filter.py:33 assets/models/user.py:205 msgid "Command filter" msgstr "命令过滤器" @@ -747,7 +747,7 @@ msgstr "全称" msgid "Parent key" msgstr "ssh私钥" -#: assets/models/node.py:559 assets/serializers/system_user.py:214 +#: assets/models/node.py:559 assets/serializers/system_user.py:230 #: users/templates/users/user_asset_permission.html:41 #: users/templates/users/user_asset_permission.html:73 #: users/templates/users/user_asset_permission.html:158 @@ -755,67 +755,67 @@ msgstr "ssh私钥" msgid "Node" msgstr "节点" -#: assets/models/user.py:189 +#: assets/models/user.py:181 msgid "Automatic managed" msgstr "托管密码" -#: assets/models/user.py:190 +#: assets/models/user.py:182 msgid "Manually input" msgstr "手动输入" -#: assets/models/user.py:194 +#: assets/models/user.py:186 msgid "Common user" msgstr "普通用户" -#: assets/models/user.py:197 +#: assets/models/user.py:189 msgid "Username same with user" msgstr "用户名与用户相同" -#: assets/models/user.py:200 assets/serializers/domain.py:29 +#: assets/models/user.py:192 assets/serializers/domain.py:29 #: templates/_nav.html:39 #: terminal/templates/terminal/_msg_command_execute_alert.html:16 #: xpack/plugins/change_auth_plan/models/asset.py:40 msgid "Assets" msgstr "资产" -#: assets/models/user.py:204 templates/_nav.html:17 +#: assets/models/user.py:196 templates/_nav.html:17 #: users/views/profile/pubkey.py:37 msgid "Users" msgstr "用户管理" -#: assets/models/user.py:205 +#: assets/models/user.py:197 msgid "User groups" msgstr "用户组" -#: assets/models/user.py:209 +#: assets/models/user.py:201 msgid "Auto push" msgstr "自动推送" -#: assets/models/user.py:210 +#: assets/models/user.py:202 msgid "Sudo" msgstr "Sudo" -#: assets/models/user.py:211 +#: assets/models/user.py:203 msgid "Shell" msgstr "Shell" -#: assets/models/user.py:212 +#: assets/models/user.py:204 msgid "Login mode" msgstr "认证方式" -#: assets/models/user.py:214 +#: assets/models/user.py:206 msgid "SFTP Root" msgstr "SFTP根路径" -#: assets/models/user.py:215 authentication/models.py:45 +#: assets/models/user.py:207 authentication/models.py:45 msgid "Token" msgstr "" -#: assets/models/user.py:216 +#: assets/models/user.py:208 msgid "Home" msgstr "家目录" -#: assets/models/user.py:217 +#: assets/models/user.py:209 msgid "System groups" msgstr "用户组" @@ -848,7 +848,7 @@ msgstr "节点名称" msgid "Hardware info" msgstr "硬件信息" -#: assets/serializers/asset.py:104 assets/serializers/system_user.py:233 +#: assets/serializers/asset.py:104 assets/serializers/system_user.py:249 #: orgs/mixins/serializers.py:26 msgid "Org name" msgstr "组织名称" @@ -862,7 +862,7 @@ msgid "private key invalid" msgstr "密钥不合法" #: assets/serializers/domain.py:13 assets/serializers/label.py:12 -#: assets/serializers/system_user.py:53 +#: assets/serializers/system_user.py:54 #: perms/serializers/asset/permission.py:72 msgid "Assets amount" msgstr "资产数量" @@ -892,48 +892,52 @@ msgstr "同级别节点名字不能重复" msgid "SSH key fingerprint" msgstr "密钥指纹" -#: assets/serializers/system_user.py:52 +#: assets/serializers/system_user.py:29 +msgid "Apps amount" +msgstr "应用数量" + +#: assets/serializers/system_user.py:53 #: perms/serializers/asset/permission.py:73 msgid "Nodes amount" msgstr "节点数量" -#: assets/serializers/system_user.py:54 assets/serializers/system_user.py:216 +#: assets/serializers/system_user.py:55 assets/serializers/system_user.py:232 msgid "Login mode display" msgstr "认证方式名称" -#: assets/serializers/system_user.py:56 +#: assets/serializers/system_user.py:57 msgid "Ad domain" msgstr "Ad 网域" -#: assets/serializers/system_user.py:57 +#: assets/serializers/system_user.py:58 msgid "Is asset protocol" msgstr "" -#: assets/serializers/system_user.py:97 +#: assets/serializers/system_user.py:98 msgid "Username same with user with protocol {} only allow 1" msgstr "用户名和用户相同的一种协议只允许存在一个" -#: assets/serializers/system_user.py:107 common/validators.py:14 +#: assets/serializers/system_user.py:108 common/validators.py:14 msgid "Special char not allowed" msgstr "不能包含特殊字符" -#: assets/serializers/system_user.py:116 +#: assets/serializers/system_user.py:117 msgid "* Automatic login mode must fill in the username." msgstr "自动登录模式,必须填写用户名" -#: assets/serializers/system_user.py:130 +#: assets/serializers/system_user.py:132 msgid "Path should starts with /" msgstr "路径应该以 / 开头" -#: assets/serializers/system_user.py:155 +#: assets/serializers/system_user.py:144 msgid "Password or private key required" msgstr "密码或密钥密码需要一个" -#: assets/serializers/system_user.py:232 +#: assets/serializers/system_user.py:248 msgid "System user name" msgstr "系统用户名称" -#: assets/serializers/system_user.py:242 +#: assets/serializers/system_user.py:258 msgid "Asset hostname" msgstr "资产主机名" @@ -1625,35 +1629,35 @@ msgstr "等待登录复核处理" msgid "Login confirm ticket was {}" msgstr "登录复核 {}" -#: authentication/errors.py:261 +#: authentication/errors.py:265 msgid "IP is not allowed" msgstr "来源 IP 不被允许登录" -#: authentication/errors.py:268 +#: authentication/errors.py:272 msgid "Time Period is not allowed" msgstr "该 时间段 不被允许登录" -#: authentication/errors.py:301 +#: authentication/errors.py:305 msgid "SSO auth closed" msgstr "SSO 认证关闭了" -#: authentication/errors.py:306 authentication/mixins.py:340 +#: authentication/errors.py:310 authentication/mixins.py:340 msgid "Your password is too simple, please change it for security" msgstr "你的密码过于简单,为了安全,请修改" -#: authentication/errors.py:315 authentication/mixins.py:347 +#: authentication/errors.py:319 authentication/mixins.py:347 msgid "You should to change your password before login" msgstr "登录完成前,请先修改密码" -#: authentication/errors.py:324 authentication/mixins.py:354 +#: authentication/errors.py:328 authentication/mixins.py:354 msgid "Your password has expired, please reset before logging in" msgstr "您的密码已过期,先修改再登录" -#: authentication/errors.py:358 +#: authentication/errors.py:362 msgid "Your password is invalid" msgstr "您的密码无效" -#: authentication/errors.py:364 +#: authentication/errors.py:368 msgid "No upload or download permission" msgstr "没有上传下载权限" @@ -1732,7 +1736,7 @@ msgid "Secret" msgstr "密钥" #: authentication/templates/authentication/_access_key_modal.html:33 -#: terminal/notifications.py:93 terminal/notifications.py:138 +#: terminal/notifications.py:92 terminal/notifications.py:140 msgid "Date" msgstr "日期" @@ -1821,16 +1825,16 @@ msgid "" "concern your account security" msgstr "请点击下面链接重置密码, 如果不是您申请的,请关注账号安全" -#: authentication/templates/authentication/_msg_reset_password.html:9 +#: authentication/templates/authentication/_msg_reset_password.html:10 msgid "Click here reset password" msgstr "点击这里重置密码" -#: authentication/templates/authentication/_msg_reset_password.html:13 +#: authentication/templates/authentication/_msg_reset_password.html:15 #: users/templates/users/_msg_user_created.html:17 msgid "This link is valid for 1 hour. After it expires" msgstr "这个链接有效期1小时, 超过时间您可以" -#: authentication/templates/authentication/_msg_reset_password.html:15 +#: authentication/templates/authentication/_msg_reset_password.html:17 #: users/templates/users/_msg_user_created.html:18 msgid "request new one" msgstr "重新申请" @@ -2243,7 +2247,7 @@ msgstr "邮件" msgid "Site message" msgstr "站内信" -#: notifications/notifications.py:167 ops/models/adhoc.py:246 +#: notifications/notifications.py:172 ops/models/adhoc.py:246 #: xpack/plugins/change_auth_plan/models/base.py:108 #: xpack/plugins/change_auth_plan/models/base.py:190 #: xpack/plugins/gathered_user/models.py:79 @@ -2424,11 +2428,11 @@ msgstr "任务列表" msgid "Update task content: {}" msgstr "更新任务内容: {}" -#: orgs/api.py:77 +#: orgs/api.py:79 msgid "The current organization ({}) cannot be deleted" msgstr "当前组织 ({}) 不能被删除" -#: orgs/api.py:85 +#: orgs/api.py:87 msgid "The organization have resource ({}) cannot be deleted" msgstr "组织存在资源 ({}) 不能被删除" @@ -2534,35 +2538,35 @@ msgstr "失效日期" msgid "From ticket" msgstr "来自工单" -#: perms/notifications.py:15 +#: perms/notifications.py:17 msgid "You permed assets is about to expire" msgstr "你授权的资产即将到期" -#: perms/notifications.py:19 +#: perms/notifications.py:21 msgid "permed assets" msgstr "授权的资产" -#: perms/notifications.py:57 +#: perms/notifications.py:59 msgid "Asset permissions is about to expire" msgstr "资产授权规则将要过期" -#: perms/notifications.py:61 +#: perms/notifications.py:63 msgid "asset permissions of organization {}" msgstr "组织 ({}) 的资产授权" -#: perms/notifications.py:87 +#: perms/notifications.py:89 msgid "Your permed applications is about to expire" msgstr "你授权的应用即将过期" -#: perms/notifications.py:90 +#: perms/notifications.py:92 msgid "permed applications" msgstr "授权的应用" -#: perms/notifications.py:128 +#: perms/notifications.py:127 msgid "Application permissions is about to expire" msgstr "应用授权规则即将过期" -#: perms/notifications.py:131 +#: perms/notifications.py:130 msgid "application permissions of organization {}" msgstr "组织 ({}) 的应用授权" @@ -4235,11 +4239,11 @@ msgstr "录像存储" msgid "Danger command alert" msgstr "危险命令告警" -#: terminal/notifications.py:92 terminal/notifications.py:137 +#: terminal/notifications.py:91 terminal/notifications.py:139 msgid "Level" msgstr "级别" -#: terminal/notifications.py:110 +#: terminal/notifications.py:109 msgid "Batch danger command alert" msgstr "批量危险命令告警" @@ -4462,35 +4466,35 @@ msgid "" msgstr "" "通过工单创建, 工单标题: {}, 工单申请人: {}, 工单处理人: {}, 工单 ID: {}" -#: tickets/handler/base.py:86 +#: tickets/handler/base.py:88 msgid "{} {} the ticket" msgstr "{} {} 工单" -#: tickets/handler/base.py:114 +#: tickets/handler/base.py:116 msgid "Ticket title" msgstr "工单标题" -#: tickets/handler/base.py:115 +#: tickets/handler/base.py:117 msgid "Ticket type" msgstr "工单类型" -#: tickets/handler/base.py:116 +#: tickets/handler/base.py:118 msgid "Ticket status" msgstr "工单状态" -#: tickets/handler/base.py:117 +#: tickets/handler/base.py:119 msgid "Ticket applicant" msgstr "工单申请人" -#: tickets/handler/base.py:119 +#: tickets/handler/base.py:121 msgid "Ticket basic info" msgstr "工单基本信息" -#: tickets/handler/base.py:130 +#: tickets/handler/base.py:132 msgid "No content" msgstr "无内容" -#: tickets/handler/base.py:132 +#: tickets/handler/base.py:134 msgid "Ticket applied info" msgstr "工单申请信息" @@ -4611,19 +4615,19 @@ msgstr "流程" msgid "TicketFlow" msgstr "工单流程" -#: tickets/notifications.py:56 +#: tickets/notifications.py:57 msgid "Your has a new ticket, applicant - {}" msgstr "你有一个新的工单, 申请人 - {}" -#: tickets/notifications.py:62 +#: tickets/notifications.py:63 msgid "New Ticket - {} ({})" msgstr "新工单 - {} ({})" -#: tickets/notifications.py:84 +#: tickets/notifications.py:85 msgid "Your ticket has been processed, processor - {}" msgstr "你的工单已被处理, 处理人 - {}" -#: tickets/notifications.py:88 +#: tickets/notifications.py:89 msgid "Ticket has processed - {} ({})" msgstr "你的工单已被处理, 处理人 - {} ({})" @@ -4748,7 +4752,7 @@ msgstr "当前组织已存在该类型" msgid "Click here to review" msgstr "点击查看" -#: users/api/user.py:207 +#: users/api/user.py:209 msgid "Could not reset self otp, use profile reset instead" msgstr "不能在该页面重置多因子认证, 请去个人信息页面重置" @@ -4923,19 +4927,19 @@ msgstr "重置密码" msgid "Reset password success" msgstr "重置密码成功" -#: users/notifications.py:110 +#: users/notifications.py:107 msgid "Password is about expire" msgstr "密码即将过期" -#: users/notifications.py:137 +#: users/notifications.py:135 msgid "Account is about expire" msgstr "账号即将过期" -#: users/notifications.py:161 +#: users/notifications.py:157 msgid "Reset SSH Key" msgstr "重置 SSH 密钥" -#: users/notifications.py:181 +#: users/notifications.py:178 msgid "Reset MFA" msgstr "重置 MFA" @@ -5364,8 +5368,8 @@ msgstr "* 新密码不能是最近 {} 次的密码" msgid "Reset password success, return to login page" msgstr "重置密码成功,返回到登录页面" -#: xpack/plugins/change_auth_plan/api/app.py:113 -#: xpack/plugins/change_auth_plan/api/asset.py:100 +#: xpack/plugins/change_auth_plan/api/app.py:114 +#: xpack/plugins/change_auth_plan/api/asset.py:101 msgid "The parameter 'action' must be [{}]" msgstr "参数 'action' 必须是 [{}]"