From f9e41d71dc0283b496e29089f6ed19dfc94c41f4 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 8 Nov 2019 15:48:01 +0800 Subject: [PATCH] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E5=B7=A5=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/login_confirm.py | 50 +- apps/authentication/errors.py | 15 +- apps/authentication/mixins.py | 2 +- apps/authentication/models.py | 5 +- apps/authentication/serializers.py | 11 +- .../authentication/login_wait_confirm.html | 2 +- apps/authentication/urls/api_urls.py | 2 +- apps/locale/zh/LC_MESSAGES/django.mo | Bin 84606 -> 84326 bytes apps/locale/zh/LC_MESSAGES/django.po | 713 +++++++++--------- apps/static/js/jumpserver.js | 4 + apps/templates/_nav.html | 2 +- apps/tickets/api/base.py | 35 +- apps/tickets/api/login_confirm.py | 41 +- apps/tickets/apps.py | 4 + apps/tickets/mixins.py | 18 + apps/tickets/models/base.py | 24 +- apps/tickets/models/login_confirm.py | 13 + apps/tickets/permissions.py | 6 + apps/tickets/serializers/base.py | 17 + apps/tickets/serializers/login_confirm.py | 17 +- apps/tickets/signals_handler.py | 26 +- .../tickets/login_confirm_ticket_detail.html | 155 +--- .../tickets/login_confirm_ticket_list.html | 30 +- .../templates/tickets/ticket_detail.html | 162 ++++ apps/tickets/urls/api_urls.py | 6 +- apps/tickets/views.py | 13 +- apps/users/serializers/user.py | 10 +- 27 files changed, 784 insertions(+), 599 deletions(-) create mode 100644 apps/tickets/mixins.py create mode 100644 apps/tickets/permissions.py create mode 100644 apps/tickets/templates/tickets/ticket_detail.html diff --git a/apps/authentication/api/login_confirm.py b/apps/authentication/api/login_confirm.py index 62350e5c9..57c6dff3f 100644 --- a/apps/authentication/api/login_confirm.py +++ b/apps/authentication/api/login_confirm.py @@ -4,6 +4,7 @@ from rest_framework.generics import UpdateAPIView from rest_framework.response import Response from rest_framework.views import APIView from django.shortcuts import get_object_or_404 +from django.utils.translation import ugettext as _ from common.utils import get_logger, get_object_or_none from common.permissions import IsOrgAdmin @@ -11,7 +12,7 @@ from ..models import LoginConfirmSetting from ..serializers import LoginConfirmSettingSerializer from .. import errors -__all__ = ['LoginConfirmSettingUpdateApi', 'UserTicketAcceptAuthApi'] +__all__ = ['LoginConfirmSettingUpdateApi', 'LoginConfirmTicketStatusApi'] logger = get_logger(__name__) @@ -30,10 +31,10 @@ class LoginConfirmSettingUpdateApi(UpdateAPIView): return s -class UserTicketAcceptAuthApi(APIView): +class LoginConfirmTicketStatusApi(APIView): permission_classes = () - def get(self, request, *args, **kwargs): + def get_ticket(self): from tickets.models import LoginConfirmTicket ticket_id = self.request.session.get("auth_ticket_id") logger.debug('Login confirm ticket id: {}'.format(ticket_id)) @@ -41,31 +42,32 @@ class UserTicketAcceptAuthApi(APIView): ticket = None else: ticket = get_object_or_none(LoginConfirmTicket, pk=ticket_id) + return ticket + + def get(self, request, *args, **kwargs): + ticket_id = self.request.session.get("auth_ticket_id") + ticket = self.get_ticket() try: if not ticket: - raise errors.LoginConfirmTicketNotFound(ticket_id) - if ticket.action == LoginConfirmTicket.ACTION_APPROVE: + raise errors.LoginConfirmOtherError(ticket_id, _("not found")) + if ticket.status == 'open': + raise errors.LoginConfirmWaitError(ticket_id) + elif ticket.action == ticket.ACTION_APPROVE: self.request.session["auth_confirm"] = "1" return Response({"msg": "ok"}) - elif ticket.action == LoginConfirmTicket.ACTION_REJECT: - raise errors.LoginConfirmRejectedError(ticket_id) + elif ticket.action == ticket.ACTION_REJECT: + raise errors.LoginConfirmOtherError( + ticket_id, ticket.get_action_display() + ) else: - raise errors.LoginConfirmWaitError(ticket_id) + raise errors.LoginConfirmOtherError( + ticket_id, ticket.get_status_display() + ) except errors.AuthFailedError as e: - data = e.as_data() - return Response(data, status=400) + return Response(e.as_data(), status=400) - -class UserTicketCancelAuthApi(APIView): - permission_classes = () - - def get(self, request, *args, **kwargs): - from tickets.models import LoginConfirmTicket - ticket_id = self.request.session.get("auth_ticket_id") - logger.debug('Login confirm ticket id: {}'.format(ticket_id)) - if not ticket_id: - ticket = None - else: - ticket = get_object_or_none(LoginConfirmTicket, pk=ticket_id) - if not ticket: - ticket.status = "close" + def delete(self, request, *args, **kwargs): + ticket = self.get_ticket() + if ticket: + ticket.perform_status('closed', request.user) + return Response('', status=200) diff --git a/apps/authentication/errors.py b/apps/authentication/errors.py index a6a73240f..5e7506e90 100644 --- a/apps/authentication/errors.py +++ b/apps/authentication/errors.py @@ -48,8 +48,7 @@ mfa_failed_msg = _("MFA code invalid, or ntp sync server time") mfa_required_msg = _("MFA required") login_confirm_required_msg = _("Login confirm required") login_confirm_wait_msg = _("Wait login confirm ticket for accept") -login_confirm_rejected_msg = _("Login confirm ticket was rejected") -login_confirm_ticket_not_found_msg = _("Ticket not found") +login_confirm_error_msg = _("Login confirm ticket was {}") class AuthFailedNeedLogMixin: @@ -174,11 +173,9 @@ class LoginConfirmWaitError(LoginConfirmError): error = 'login_confirm_wait' -class LoginConfirmRejectedError(LoginConfirmError): - msg = login_confirm_rejected_msg - error = 'login_confirm_rejected' +class LoginConfirmOtherError(LoginConfirmError): + error = 'login_confirm_error' - -class LoginConfirmTicketNotFound(LoginConfirmError): - msg = login_confirm_ticket_not_found_msg - error = 'login_confirm_ticket_not_found' + def __init__(self, ticket_id, status): + msg = login_confirm_error_msg.format(status) + super().__init__(ticket_id=ticket_id, msg=msg) diff --git a/apps/authentication/mixins.py b/apps/authentication/mixins.py index b33d0fdae..3d6ca0321 100644 --- a/apps/authentication/mixins.py +++ b/apps/authentication/mixins.py @@ -106,7 +106,7 @@ class AuthMixin: if ticket.status == "accepted": return elif ticket.status == "rejected": - raise errors.LoginConfirmRejectedError(ticket.id) + raise errors.LoginConfirmOtherError(ticket.id) else: raise errors.LoginConfirmWaitError(ticket.id) diff --git a/apps/authentication/models.py b/apps/authentication/models.py index 913e04f73..cb4f97e4d 100644 --- a/apps/authentication/models.py +++ b/apps/authentication/models.py @@ -62,12 +62,9 @@ class LoginConfirmSetting(CommonModelMixin): remote_addr = '127.0.0.1' body = '' reviewer = self.reviewers.all() - reviewer_names = ','.join([u.name for u in reviewer]) ticket = LoginConfirmTicket.objects.create( - user=self.user, user_display=str(self.user), - title=title, body=body, + user=self.user, title=title, body=body, city=city, ip=remote_addr, - assignees_display=reviewer_names, type=LoginConfirmTicket.TYPE_LOGIN_CONFIRM, ) ticket.assignees.set(reviewer) diff --git a/apps/authentication/serializers.py b/apps/authentication/serializers.py index 029c51c1e..f000c3438 100644 --- a/apps/authentication/serializers.py +++ b/apps/authentication/serializers.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- # -from django.core.cache import cache from rest_framework import serializers from common.utils import get_object_or_none from users.models import User +from users.serializers import UserProfileSerializer from .models import AccessKey, LoginConfirmSetting @@ -26,14 +26,15 @@ class OtpVerifySerializer(serializers.Serializer): class BearerTokenSerializer(serializers.Serializer): - username = serializers.CharField(allow_null=True, required=False) + username = serializers.CharField(allow_null=True, required=False, write_only=True) password = serializers.CharField(write_only=True, allow_null=True, - required=False) + required=False, allow_blank=True) public_key = serializers.CharField(write_only=True, allow_null=True, - required=False) + allow_blank=True, required=False) token = serializers.CharField(read_only=True) keyword = serializers.SerializerMethodField() date_expired = serializers.DateTimeField(read_only=True) + user = UserProfileSerializer(read_only=True) @staticmethod def get_keyword(obj): @@ -52,9 +53,9 @@ class BearerTokenSerializer(serializers.Serializer): ) token, date_expired = user.create_bearer_token(request) instance = { - "username": user.username, "token": token, "date_expired": date_expired, + "user": user } return instance diff --git a/apps/authentication/templates/authentication/login_wait_confirm.html b/apps/authentication/templates/authentication/login_wait_confirm.html index 0b22cbd1c..e653fb072 100644 --- a/apps/authentication/templates/authentication/login_wait_confirm.html +++ b/apps/authentication/templates/authentication/login_wait_confirm.html @@ -73,7 +73,7 @@ var infoMsgRef = $(".info-messages"); var timestamp = '{{ timestamp }}'; var progressBarRef = $(".progress-bar"); var interval, checkInterval; -var url = "{% url 'api-auth:user-order-auth' %}"; +var url = "{% url 'api-auth:login-confirm-ticket-status' %}"; var successUrl = "{% url 'authentication:login-guard' %}"; function doRequestAuth() { diff --git a/apps/authentication/urls/api_urls.py b/apps/authentication/urls/api_urls.py index a7e15053b..2da89a19f 100644 --- a/apps/authentication/urls/api_urls.py +++ b/apps/authentication/urls/api_urls.py @@ -18,7 +18,7 @@ urlpatterns = [ path('connection-token/', api.UserConnectionTokenApi.as_view(), name='connection-token'), path('otp/verify/', api.UserOtpVerifyApi.as_view(), name='user-otp-verify'), - path('order/auth/', api.UserTicketAcceptAuthApi.as_view(), name='user-order-auth'), + path('login-confirm-ticket/status/', api.LoginConfirmTicketStatusApi.as_view(), name='login-confirm-ticket-status'), path('login-confirm-settings//', api.LoginConfirmSettingUpdateApi.as_view(), name='login-confirm-setting-update') ] diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 2f408e2249b2f0790ca19b07b33f8ecf4ecc2b17..b591fda3cebf031a86eaee0c9278171b85c86701 100644 GIT binary patch delta 24909 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 delta 25160 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 Bw^{%I diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index fb4a66a48..00b3ac1c4 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-11-05 15:00+0800\n" +"POT-Creation-Date: 2019-11-08 15:42+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -96,7 +96,7 @@ msgstr "运行参数" #: terminal/templates/terminal/session_list.html:28 #: terminal/templates/terminal/session_list.html:72 #: xpack/plugins/change_auth_plan/forms.py:73 -#: xpack/plugins/change_auth_plan/models.py:419 +#: xpack/plugins/change_auth_plan/models.py:412 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:46 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:54 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:13 @@ -152,7 +152,7 @@ msgstr "资产" #: users/templates/users/user_profile.html:51 #: users/templates/users/user_pubkey_update.html:57 #: xpack/plugins/change_auth_plan/forms.py:56 -#: xpack/plugins/change_auth_plan/models.py:64 +#: xpack/plugins/change_auth_plan/models.py:63 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:61 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12 #: xpack/plugins/cloud/models.py:59 xpack/plugins/cloud/models.py:144 @@ -199,7 +199,7 @@ msgstr "参数" #: perms/templates/perms/remote_app_permission_detail.html:90 #: users/models/user.py:423 users/serializers/group.py:32 #: users/templates/users/user_detail.html:112 -#: xpack/plugins/change_auth_plan/models.py:109 +#: xpack/plugins/change_auth_plan/models.py:108 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113 #: xpack/plugins/cloud/models.py:80 xpack/plugins/cloud/models.py:179 #: xpack/plugins/gathered_user/models.py:46 @@ -219,11 +219,11 @@ msgstr "创建者" #: assets/templates/assets/system_user_detail.html:96 #: common/mixins/models.py:51 ops/models/adhoc.py:45 #: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:64 -#: orders/templates/orders/login_confirm_order_detail.html:60 orgs/models.py:17 -#: perms/models/base.py:55 +#: orgs/models.py:17 perms/models/base.py:55 #: perms/templates/perms/asset_permission_detail.html:94 #: perms/templates/perms/remote_app_permission_detail.html:86 -#: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:17 +#: terminal/templates/terminal/terminal_detail.html:59 +#: tickets/templates/tickets/ticket_detail.html:52 users/models/group.py:17 #: users/templates/users/user_group_detail.html:63 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:105 #: xpack/plugins/cloud/models.py:83 xpack/plugins/cloud/models.py:182 @@ -254,18 +254,17 @@ msgstr "创建日期" #: assets/templates/assets/domain_list.html:28 #: assets/templates/assets/system_user_detail.html:104 #: assets/templates/assets/system_user_list.html:55 ops/models/adhoc.py:43 -#: orders/serializers.py:23 -#: orders/templates/orders/login_confirm_order_detail.html:96 orgs/models.py:18 -#: perms/models/base.py:56 +#: orgs/models.py:18 perms/models/base.py:56 #: perms/templates/perms/asset_permission_detail.html:102 #: perms/templates/perms/remote_app_permission_detail.html:94 #: settings/models.py:34 terminal/models.py:33 -#: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:15 +#: terminal/templates/terminal/terminal_detail.html:63 +#: tickets/templates/tickets/ticket_detail.html:106 users/models/group.py:15 #: users/models/user.py:415 users/templates/users/user_detail.html:130 #: users/templates/users/user_group_detail.html:67 #: users/templates/users/user_group_list.html:37 #: users/templates/users/user_profile.html:138 -#: xpack/plugins/change_auth_plan/models.py:105 +#: xpack/plugins/change_auth_plan/models.py:104 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:117 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:19 #: xpack/plugins/cloud/models.py:77 xpack/plugins/cloud/models.py:173 @@ -311,7 +310,7 @@ msgstr "远程应用" #: settings/templates/settings/security_setting.html:73 #: settings/templates/settings/terminal_setting.html:71 #: terminal/templates/terminal/terminal_update.html:45 -#: users/templates/users/_user.html:50 +#: users/templates/users/_user.html:51 #: users/templates/users/user_bulk_update.html:23 #: users/templates/users/user_detail.html:179 #: users/templates/users/user_group_create_update.html:31 @@ -355,7 +354,7 @@ msgstr "重置" #: terminal/templates/terminal/command_list.html:47 #: terminal/templates/terminal/session_list.html:52 #: terminal/templates/terminal/terminal_update.html:46 -#: users/templates/users/_user.html:51 +#: users/templates/users/_user.html:52 #: users/templates/users/forgot_password.html:42 #: users/templates/users/user_bulk_update.html:24 #: users/templates/users/user_list.html:57 @@ -519,7 +518,6 @@ msgstr "创建远程应用" #: authentication/templates/authentication/_access_key_modal.html:34 #: ops/templates/ops/adhoc_history.html:59 ops/templates/ops/task_adhoc.html:64 #: ops/templates/ops/task_history.html:65 ops/templates/ops/task_list.html:18 -#: orders/templates/orders/login_confirm_order_list.html:19 #: perms/forms/asset_permission.py:21 #: perms/templates/perms/asset_permission_create_update.html:50 #: perms/templates/perms/asset_permission_list.html:56 @@ -529,6 +527,8 @@ msgstr "创建远程应用" #: settings/templates/settings/terminal_setting.html:107 #: terminal/templates/terminal/session_list.html:36 #: terminal/templates/terminal/terminal_list.html:36 +#: tickets/templates/tickets/login_confirm_ticket_list.html:18 +#: tickets/templates/tickets/login_confirm_ticket_list.html:92 #: users/templates/users/_granted_assets.html:34 #: users/templates/users/user_group_list.html:38 #: users/templates/users/user_list.html:41 @@ -606,7 +606,7 @@ msgstr "端口" #: assets/templates/assets/asset_detail.html:196 #: assets/templates/assets/system_user_assets.html:83 #: perms/models/asset_permission.py:81 -#: xpack/plugins/change_auth_plan/models.py:75 +#: xpack/plugins/change_auth_plan/models.py:74 #: xpack/plugins/gathered_user/models.py:31 #: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:17 msgid "Nodes" @@ -700,21 +700,21 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC" #: assets/templates/assets/admin_user_list.html:45 #: assets/templates/assets/domain_gateway_list.html:71 #: assets/templates/assets/system_user_detail.html:62 -#: assets/templates/assets/system_user_list.html:48 audits/models.py:82 +#: assets/templates/assets/system_user_list.html:48 audits/models.py:81 #: audits/templates/audits/login_log_list.html:57 authentication/forms.py:13 -#: authentication/templates/authentication/login.html:60 -#: authentication/templates/authentication/xpack_login.html:87 +#: authentication/templates/authentication/login.html:58 +#: authentication/templates/authentication/xpack_login.html:91 #: ops/models/adhoc.py:189 perms/templates/perms/asset_permission_list.html:70 #: perms/templates/perms/asset_permission_user.html:55 #: perms/templates/perms/remote_app_permission_user.html:54 -#: settings/templates/settings/_ldap_list_users_modal.html:30 users/forms.py:13 +#: settings/templates/settings/_ldap_list_users_modal.html:30 users/forms.py:14 #: users/models/user.py:380 users/templates/users/_select_user_modal.html:14 #: users/templates/users/user_detail.html:68 #: users/templates/users/user_list.html:36 #: users/templates/users/user_profile.html:47 #: xpack/plugins/change_auth_plan/forms.py:58 -#: xpack/plugins/change_auth_plan/models.py:66 -#: xpack/plugins/change_auth_plan/models.py:415 +#: xpack/plugins/change_auth_plan/models.py:65 +#: xpack/plugins/change_auth_plan/models.py:408 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:65 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:53 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:12 @@ -732,17 +732,17 @@ msgstr "密码或密钥密码" #: assets/templates/assets/_asset_user_auth_update_modal.html:21 #: assets/templates/assets/_asset_user_auth_view_modal.html:27 #: authentication/forms.py:15 -#: authentication/templates/authentication/login.html:63 -#: authentication/templates/authentication/xpack_login.html:90 -#: settings/forms.py:114 users/forms.py:15 users/forms.py:27 +#: authentication/templates/authentication/login.html:66 +#: authentication/templates/authentication/xpack_login.html:99 +#: settings/forms.py:114 users/forms.py:16 users/forms.py:42 #: users/templates/users/reset_password.html:53 #: users/templates/users/user_password_authentication.html:18 #: users/templates/users/user_password_update.html:44 #: users/templates/users/user_profile_update.html:41 #: users/templates/users/user_pubkey_update.html:41 #: users/templates/users/user_update.html:20 -#: xpack/plugins/change_auth_plan/models.py:96 -#: xpack/plugins/change_auth_plan/models.py:264 +#: xpack/plugins/change_auth_plan/models.py:95 +#: xpack/plugins/change_auth_plan/models.py:263 msgid "Password" msgstr "密码" @@ -797,8 +797,6 @@ msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig" #: assets/templates/assets/domain_gateway_list.html:68 #: assets/templates/assets/user_asset_list.html:76 #: audits/templates/audits/login_log_list.html:60 -#: orders/templates/orders/login_confirm_order_detail.html:33 -#: orders/templates/orders/login_confirm_order_list.html:16 #: perms/templates/perms/asset_permission_asset.html:58 settings/forms.py:144 #: users/templates/users/_granted_assets.html:31 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:54 @@ -938,13 +936,13 @@ msgstr "版本" msgid "AuthBook" msgstr "" -#: assets/models/base.py:31 xpack/plugins/change_auth_plan/models.py:100 -#: xpack/plugins/change_auth_plan/models.py:271 +#: assets/models/base.py:31 xpack/plugins/change_auth_plan/models.py:99 +#: xpack/plugins/change_auth_plan/models.py:270 msgid "SSH private key" msgstr "ssh密钥" -#: assets/models/base.py:32 xpack/plugins/change_auth_plan/models.py:103 -#: xpack/plugins/change_auth_plan/models.py:267 +#: assets/models/base.py:32 xpack/plugins/change_auth_plan/models.py:102 +#: xpack/plugins/change_auth_plan/models.py:266 msgid "SSH public key" msgstr "ssh公钥" @@ -1041,12 +1039,13 @@ msgstr "过滤器" #: assets/models/cmd_filter.py:51 #: assets/templates/assets/cmd_filter_rule_list.html:58 -#: audits/templates/audits/login_log_list.html:58 orders/models.py:41 +#: audits/templates/audits/login_log_list.html:58 #: perms/templates/perms/remote_app_permission_remote_app.html:54 #: settings/templates/settings/command_storage_create.html:31 #: settings/templates/settings/replay_storage_create.html:31 #: settings/templates/settings/terminal_setting.html:84 #: settings/templates/settings/terminal_setting.html:106 +#: tickets/models/base.py:34 tickets/templates/tickets/ticket_detail.html:33 msgid "Type" msgstr "类型" @@ -1105,10 +1104,7 @@ msgstr "默认资产组" #: audits/templates/audits/password_change_log_list.html:39 #: audits/templates/audits/password_change_log_list.html:56 #: authentication/models.py:43 ops/templates/ops/command_execution_list.html:38 -#: ops/templates/ops/command_execution_list.html:63 orders/models.py:11 -#: orders/models.py:32 -#: orders/templates/orders/login_confirm_order_detail.html:32 -#: orders/templates/orders/login_confirm_order_list.html:15 +#: ops/templates/ops/command_execution_list.html:63 #: perms/forms/asset_permission.py:78 perms/forms/remote_app_permission.py:34 #: perms/models/base.py:49 #: perms/templates/perms/asset_permission_create_update.html:41 @@ -1120,7 +1116,10 @@ msgstr "默认资产组" #: terminal/models.py:156 terminal/templates/terminal/command_list.html:29 #: terminal/templates/terminal/command_list.html:65 #: terminal/templates/terminal/session_list.html:27 -#: terminal/templates/terminal/session_list.html:71 users/forms.py:319 +#: terminal/templates/terminal/session_list.html:71 tickets/models/base.py:25 +#: tickets/models/base.py:68 +#: tickets/templates/tickets/login_confirm_ticket_list.html:15 +#: tickets/templates/tickets/ticket_detail.html:32 users/forms.py:339 #: users/models/user.py:132 users/models/user.py:148 users/models/user.py:509 #: users/serializers/group.py:21 #: users/templates/users/user_group_detail.html:78 @@ -1187,7 +1186,7 @@ msgstr "手动登录" #: assets/views/label.py:27 assets/views/label.py:45 assets/views/label.py:73 #: assets/views/system_user.py:29 assets/views/system_user.py:46 #: assets/views/system_user.py:63 assets/views/system_user.py:79 -#: templates/_nav.html:39 xpack/plugins/change_auth_plan/models.py:71 +#: templates/_nav.html:39 xpack/plugins/change_auth_plan/models.py:70 msgid "Assets" msgstr "资产管理" @@ -1245,7 +1244,7 @@ msgstr "不可达" msgid "Reachable" msgstr "可连接" -#: assets/models/utils.py:45 assets/tasks/const.py:89 audits/utils.py:29 +#: assets/models/utils.py:45 assets/tasks/const.py:89 audits/utils.py:30 #: xpack/plugins/license/models.py:78 msgid "Unknown" msgstr "未知" @@ -1276,7 +1275,7 @@ msgstr "组织名称" msgid "Backend" msgstr "后端" -#: assets/serializers/asset_user.py:67 users/forms.py:262 +#: assets/serializers/asset_user.py:67 users/forms.py:282 #: users/models/user.py:412 users/templates/users/first_login.html:42 #: users/templates/users/user_password_update.html:49 #: users/templates/users/user_profile.html:69 @@ -1332,7 +1331,7 @@ msgstr "测试资产可连接性: {}" #: assets/tasks/asset_user_connectivity.py:27 #: assets/tasks/push_system_user.py:130 -#: xpack/plugins/change_auth_plan/models.py:528 +#: xpack/plugins/change_auth_plan/models.py:521 msgid "The asset {} system platform {} does not support run Ansible tasks" msgstr "资产 {} 系统平台 {} 不支持运行 Ansible 任务" @@ -1447,7 +1446,7 @@ msgid "Asset list" msgstr "资产列表" #: assets/templates/assets/_asset_list_modal.html:33 -#: assets/templates/assets/_node_tree.html:40 +#: assets/templates/assets/_node_tree.html:39 #: ops/templates/ops/command_execution_create.html:70 #: ops/templates/ops/command_execution_create.html:127 #: users/templates/users/_granted_assets.html:7 @@ -1494,7 +1493,8 @@ msgstr "获取认证信息错误" #: authentication/templates/authentication/_access_key_modal.html:142 #: authentication/templates/authentication/_mfa_confirm_modal.html:53 #: settings/templates/settings/_ldap_list_users_modal.html:92 -#: templates/_modal.html:22 +#: templates/_modal.html:22 tickets/models/base.py:50 +#: tickets/templates/tickets/ticket_detail.html:103 msgid "Close" msgstr "关闭" @@ -1502,9 +1502,9 @@ msgstr "关闭" #: audits/templates/audits/operate_log_list.html:77 #: audits/templates/audits/password_change_log_list.html:59 #: ops/templates/ops/task_adhoc.html:63 -#: orders/templates/orders/login_confirm_order_list.html:18 #: terminal/templates/terminal/command_list.html:33 #: terminal/templates/terminal/session_detail.html:50 +#: tickets/templates/tickets/login_confirm_ticket_list.html:17 msgid "Datetime" msgstr "日期" @@ -1544,31 +1544,31 @@ msgstr "SSH端口" msgid "If use nat, set the ssh real port" msgstr "如果使用了nat端口映射,请设置为ssh真实监听的端口" -#: assets/templates/assets/_node_tree.html:50 +#: assets/templates/assets/_node_tree.html:49 msgid "Add node" msgstr "新建节点" -#: assets/templates/assets/_node_tree.html:51 +#: assets/templates/assets/_node_tree.html:50 msgid "Rename node" msgstr "重命名节点" -#: assets/templates/assets/_node_tree.html:52 +#: assets/templates/assets/_node_tree.html:51 msgid "Delete node" msgstr "删除节点" -#: assets/templates/assets/_node_tree.html:166 +#: assets/templates/assets/_node_tree.html:165 msgid "Create node failed" msgstr "创建节点失败" -#: assets/templates/assets/_node_tree.html:178 +#: assets/templates/assets/_node_tree.html:177 msgid "Have child node, cancel" msgstr "存在子节点,不能删除" -#: assets/templates/assets/_node_tree.html:180 +#: assets/templates/assets/_node_tree.html:179 msgid "Have assets, cancel" msgstr "存在资产,不能删除" -#: assets/templates/assets/_node_tree.html:255 +#: assets/templates/assets/_node_tree.html:254 msgid "Rename success" msgstr "重命名成功" @@ -2214,7 +2214,7 @@ msgstr "操作" msgid "Filename" msgstr "文件名" -#: audits/models.py:24 audits/models.py:78 +#: audits/models.py:24 audits/models.py:77 #: audits/templates/audits/ftp_log_list.html:79 #: ops/templates/ops/command_execution_list.html:68 #: ops/templates/ops/task_list.html:15 @@ -2256,53 +2256,53 @@ msgstr "启用" msgid "-" msgstr "" -#: audits/models.py:79 xpack/plugins/cloud/models.py:264 +#: audits/models.py:78 xpack/plugins/cloud/models.py:264 #: xpack/plugins/cloud/models.py:287 msgid "Failed" msgstr "失败" -#: audits/models.py:83 +#: audits/models.py:82 msgid "Login type" msgstr "登录方式" -#: audits/models.py:84 +#: audits/models.py:83 msgid "Login ip" msgstr "登录IP" -#: audits/models.py:85 +#: audits/models.py:84 msgid "Login city" msgstr "登录城市" -#: audits/models.py:86 +#: audits/models.py:85 msgid "User agent" msgstr "Agent" -#: audits/models.py:87 audits/templates/audits/login_log_list.html:62 +#: audits/models.py:86 audits/templates/audits/login_log_list.html:62 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 -#: users/forms.py:174 users/models/user.py:404 +#: users/forms.py:194 users/models/user.py:404 #: users/templates/users/first_login.html:45 msgid "MFA" msgstr "MFA" -#: audits/models.py:88 audits/templates/audits/login_log_list.html:63 -#: xpack/plugins/change_auth_plan/models.py:423 +#: audits/models.py:87 audits/templates/audits/login_log_list.html:63 +#: xpack/plugins/change_auth_plan/models.py:416 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:15 #: xpack/plugins/cloud/models.py:278 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:69 msgid "Reason" msgstr "原因" -#: audits/models.py:89 audits/templates/audits/login_log_list.html:64 -#: orders/templates/orders/login_confirm_order_detail.html:35 -#: orders/templates/orders/login_confirm_order_list.html:17 -#: orders/templates/orders/login_confirm_order_list.html:91 +#: audits/models.py:88 audits/templates/audits/login_log_list.html:64 +#: tickets/templates/tickets/login_confirm_ticket_list.html:16 +#: tickets/templates/tickets/login_confirm_ticket_list.html:88 +#: tickets/templates/tickets/ticket_detail.html:34 #: xpack/plugins/cloud/models.py:275 xpack/plugins/cloud/models.py:310 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:70 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:65 msgid "Status" msgstr "状态" -#: audits/models.py:90 +#: audits/models.py:89 msgid "Date login" msgstr "登录日期" @@ -2314,8 +2314,8 @@ msgstr "登录日期" #: perms/templates/perms/asset_permission_detail.html:86 #: perms/templates/perms/remote_app_permission_detail.html:78 #: terminal/models.py:167 terminal/templates/terminal/session_list.html:34 -#: xpack/plugins/change_auth_plan/models.py:250 -#: xpack/plugins/change_auth_plan/models.py:426 +#: xpack/plugins/change_auth_plan/models.py:249 +#: xpack/plugins/change_auth_plan/models.py:419 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:59 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:17 #: xpack/plugins/gathered_user/models.py:143 @@ -2356,7 +2356,6 @@ msgid "UA" msgstr "Agent" #: audits/templates/audits/login_log_list.html:61 -#: orders/templates/orders/login_confirm_order_detail.html:58 msgid "City" msgstr "城市" @@ -2391,21 +2390,9 @@ msgstr "登录日志" msgid "Command execution log" msgstr "命令执行" -#: authentication/api/auth.py:58 -msgid "Log in frequently and try again later" -msgstr "登录频繁, 稍后重试" - -#: authentication/api/auth.py:83 -msgid "Please carry seed value and conduct MFA secondary certification" -msgstr "请携带seed值, 进行MFA二次认证" - -#: authentication/api/auth.py:173 -msgid "Please verify the user name and password first" -msgstr "请先进行用户名和密码验证" - -#: authentication/api/auth.py:178 -msgid "MFA certification failed" -msgstr "MFA认证失败" +#: authentication/api/login_confirm.py:52 +msgid "not found" +msgstr "没有发现" #: authentication/backends/api.py:53 msgid "Invalid signature header. No credentials provided." @@ -2482,11 +2469,11 @@ msgstr "禁用或失效" msgid "This account is inactive." msgstr "此账户已禁用" -#: authentication/errors.py:28 +#: authentication/errors.py:35 msgid "No session found, check your cookie" msgstr "会话已变更,刷新页面" -#: authentication/errors.py:30 +#: authentication/errors.py:37 #, python-brace-format msgid "" "The username or password you entered is incorrect, please enter it again. " @@ -2496,37 +2483,33 @@ msgstr "" "您输入的用户名或密码不正确,请重新输入。 您还可以尝试 {times_try} 次(账号将" "被临时 锁定 {block_time} 分钟)" -#: authentication/errors.py:36 +#: authentication/errors.py:43 msgid "" "The account has been locked (please contact admin to unlock it or try again " "after {} minutes)" msgstr "账号已被锁定(请联系管理员解锁 或 {}分钟后重试)" -#: authentication/errors.py:39 users/views/user.py:393 users/views/user.py:418 +#: authentication/errors.py:46 users/views/user.py:393 users/views/user.py:418 msgid "MFA code invalid, or ntp sync server time" msgstr "MFA验证码不正确,或者服务器端时间不对" -#: authentication/errors.py:41 +#: authentication/errors.py:48 msgid "MFA required" msgstr "" -#: authentication/errors.py:42 +#: authentication/errors.py:49 msgid "Login confirm required" msgstr "需要登录复核" -#: authentication/errors.py:43 -msgid "Wait login confirm order for accept" +#: authentication/errors.py:50 +msgid "Wait login confirm ticket for accept" msgstr "等待登录复核处理" -#: authentication/errors.py:44 -msgid "Login confirm order was rejected" -msgstr "登录已被拒绝" +#: authentication/errors.py:51 +msgid "Login confirm ticket was {}" +msgstr "登录复核 {}" -#: authentication/errors.py:45 -msgid "Order not found" -msgstr "没有发现工单" - -#: authentication/forms.py:32 users/forms.py:21 +#: authentication/forms.py:32 users/forms.py:22 msgid "MFA code" msgstr "MFA 验证码" @@ -2643,30 +2626,30 @@ msgstr "" msgid "Changes the world, starting with a little bit." msgstr "改变世界,从一点点开始。" -#: authentication/templates/authentication/login.html:46 -#: authentication/templates/authentication/login.html:68 -#: authentication/templates/authentication/xpack_login.html:96 +#: authentication/templates/authentication/login.html:45 +#: authentication/templates/authentication/login.html:76 +#: authentication/templates/authentication/xpack_login.html:110 #: templates/_header_bar.html:83 msgid "Login" msgstr "登录" -#: authentication/templates/authentication/login.html:52 -#: authentication/templates/authentication/xpack_login.html:78 +#: authentication/templates/authentication/login.html:54 +#: authentication/templates/authentication/xpack_login.html:87 msgid "Captcha invalid" msgstr "验证码错误" -#: authentication/templates/authentication/login.html:79 -#: authentication/templates/authentication/xpack_login.html:100 +#: authentication/templates/authentication/login.html:87 +#: authentication/templates/authentication/xpack_login.html:114 #: users/templates/users/forgot_password.html:10 #: users/templates/users/forgot_password.html:25 msgid "Forgot password" msgstr "忘记密码" -#: authentication/templates/authentication/login.html:86 +#: authentication/templates/authentication/login.html:94 msgid "More login options" msgstr "更多登录方式" -#: authentication/templates/authentication/login.html:90 +#: authentication/templates/authentication/login.html:98 msgid "Keycloak" msgstr "" @@ -2716,15 +2699,15 @@ msgstr "复制链接" msgid "Return" msgstr "返回" -#: authentication/templates/authentication/xpack_login.html:67 +#: authentication/templates/authentication/xpack_login.html:74 msgid "Welcome back, please enter username and password to login" msgstr "欢迎回来,请输入用户名和密码登录" -#: authentication/views/login.py:80 +#: authentication/views/login.py:73 msgid "Please enable cookies and try again." msgstr "设置你的浏览器支持cookie" -#: authentication/views/login.py:192 +#: authentication/views/login.py:172 msgid "" "Wait for {} confirm, You also can copy link to her/him
\n" " Don't close this page" @@ -2732,15 +2715,15 @@ msgstr "" "等待 {} 确认, 你也可以复制链接发给他/她
\n" " 不要关闭本页面" -#: authentication/views/login.py:197 -msgid "No order found" +#: authentication/views/login.py:177 +msgid "No ticket found" msgstr "没有发现工单" -#: authentication/views/login.py:220 +#: authentication/views/login.py:200 msgid "Logout success" msgstr "退出登录成功" -#: authentication/views/login.py:221 +#: authentication/views/login.py:201 msgid "Logout success, return login page" msgstr "退出登录成功,返回到登录页面" @@ -2906,49 +2889,49 @@ msgstr "Become" msgid "Create by" msgstr "创建者" -#: ops/models/adhoc.py:251 +#: ops/models/adhoc.py:252 msgid "{} Start task: {}" msgstr "{} 任务开始: {}" -#: ops/models/adhoc.py:263 +#: ops/models/adhoc.py:264 msgid "{} Task finish" msgstr "{} 任务结束" -#: ops/models/adhoc.py:355 +#: ops/models/adhoc.py:356 msgid "Start time" msgstr "开始时间" -#: ops/models/adhoc.py:356 +#: ops/models/adhoc.py:357 msgid "End time" msgstr "完成时间" -#: ops/models/adhoc.py:357 ops/templates/ops/adhoc_history.html:57 +#: ops/models/adhoc.py:358 ops/templates/ops/adhoc_history.html:57 #: ops/templates/ops/task_history.html:63 ops/templates/ops/task_list.html:17 -#: xpack/plugins/change_auth_plan/models.py:253 -#: xpack/plugins/change_auth_plan/models.py:429 +#: xpack/plugins/change_auth_plan/models.py:252 +#: xpack/plugins/change_auth_plan/models.py:422 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:58 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:16 #: xpack/plugins/gathered_user/models.py:146 msgid "Time" msgstr "时间" -#: ops/models/adhoc.py:358 ops/templates/ops/adhoc_detail.html:106 +#: ops/models/adhoc.py:359 ops/templates/ops/adhoc_detail.html:106 #: ops/templates/ops/adhoc_history.html:55 #: ops/templates/ops/adhoc_history_detail.html:69 #: ops/templates/ops/task_detail.html:84 ops/templates/ops/task_history.html:61 msgid "Is finished" msgstr "是否完成" -#: ops/models/adhoc.py:359 ops/templates/ops/adhoc_history.html:56 +#: ops/models/adhoc.py:360 ops/templates/ops/adhoc_history.html:56 #: ops/templates/ops/task_history.html:62 msgid "Is success" msgstr "是否成功" -#: ops/models/adhoc.py:360 +#: ops/models/adhoc.py:361 msgid "Adhoc raw result" msgstr "结果" -#: ops/models/adhoc.py:361 +#: ops/models/adhoc.py:362 msgid "Adhoc result summary" msgstr "汇总" @@ -3108,8 +3091,7 @@ msgstr "没有输入命令" msgid "No system user was selected" msgstr "没有选择系统用户" -#: ops/templates/ops/command_execution_create.html:296 orders/models.py:26 -#: orders/templates/orders/login_confirm_order_list.html:92 +#: ops/templates/ops/command_execution_create.html:296 msgid "Pending" msgstr "等待" @@ -3193,159 +3175,6 @@ msgstr "命令执行列表" msgid "Command execution" msgstr "命令执行" -#: orders/models.py:12 orders/models.py:33 -msgid "User display name" -msgstr "用户显示名称" - -#: orders/models.py:13 orders/models.py:36 -msgid "Body" -msgstr "内容" - -#: orders/models.py:24 orders/templates/orders/login_confirm_order_list.html:93 -msgid "Accepted" -msgstr "已接受" - -#: orders/models.py:25 orders/templates/orders/login_confirm_order_list.html:94 -msgid "Rejected" -msgstr "已拒绝" - -#: orders/models.py:35 orders/templates/orders/login_confirm_order_list.html:14 -#: orders/templates/orders/login_confirm_order_list.html:90 -msgid "Title" -msgstr "标题" - -#: orders/models.py:37 -#: orders/templates/orders/login_confirm_order_detail.html:59 -msgid "Assignee" -msgstr "处理人" - -#: orders/models.py:38 -msgid "Assignee display name" -msgstr "处理人名称" - -#: orders/models.py:39 -#: orders/templates/orders/login_confirm_order_detail.html:34 -msgid "Assignees" -msgstr "待处理人" - -#: orders/models.py:40 -msgid "Assignees display name" -msgstr "待处理人名称" - -#: orders/serializers.py:21 -#: orders/templates/orders/login_confirm_order_detail.html:94 -#: orders/templates/orders/login_confirm_order_list.html:59 -#: terminal/templates/terminal/terminal_list.html:78 -msgid "Accept" -msgstr "接受" - -#: orders/serializers.py:22 -#: orders/templates/orders/login_confirm_order_detail.html:95 -#: orders/templates/orders/login_confirm_order_list.html:60 -#: terminal/templates/terminal/terminal_list.html:80 -msgid "Reject" -msgstr "拒绝" - -#: orders/serializers.py:43 -msgid "this order" -msgstr "这个工单" - -#: orders/templates/orders/login_confirm_order_detail.html:75 -msgid "ago" -msgstr "前" - -#: orders/utils.py:18 -msgid "New order" -msgstr "新工单" - -#: orders/utils.py:21 -#, python-brace-format -msgid "" -"\n" -"
\n" -"

Your has a new order

\n" -"
\n" -" Title: {order.title}\n" -"
\n" -" User: {user}\n" -"
\n" -" Assignees: {order.assignees_display}\n" -"
\n" -" City: {order.city}\n" -"
\n" -" IP: {order.ip}\n" -"
\n" -" click here to review \n" -"
\n" -"
\n" -" " -msgstr "" -"\n" -"
\n" -"

您有一个新工单

\n" -"
\n" -" 标题: {order.title}\n" -"
\n" -" 用户: {user}\n" -"
\n" -" 待处理人: {order.assignees_display}\n" -"
\n" -" 城市: {order.city}\n" -"
\n" -" IP: {order.ip}\n" -"
\n" -" 点我查看 \n" -"
\n" -"
\n" -" " - -#: orders/utils.py:48 -msgid "Order has been reply" -msgstr "工单已被回复" - -#: orders/utils.py:49 -#, python-brace-format -msgid "" -"\n" -"
\n" -"

Your order has been replay

\n" -"
\n" -" Title: {order.title}\n" -"
\n" -" Assignee: {order.assignee_display}\n" -"
\n" -" Status: {order.status_display}\n" -"
\n" -"
\n" -"
\n" -" " -msgstr "" -"\n" -"
\n" -"

您的工单已被回复

\n" -"
\n" -" 标题: {order.title}\n" -"
\n" -" 处理人: {order.assignee_display}\n" -"
\n" -" 状态: {order.status_display}\n" -"
\n" -"
\n" -"
\n" -" " - -#: orders/views.py:15 orders/views.py:31 templates/_nav.html:127 -msgid "Orders" -msgstr "工单管理" - -#: orders/views.py:16 -msgid "Login confirm order list" -msgstr "登录复核工单列表" - -#: orders/views.py:32 -msgid "Login confirm order detail" -msgstr "登录复核工单详情" - #: orgs/mixins/models.py:44 orgs/mixins/serializers.py:26 orgs/models.py:31 msgid "Organization" msgstr "组织" @@ -3369,7 +3198,7 @@ msgstr "提示:RDP 协议不支持单独控制上传或下载文件" #: perms/templates/perms/asset_permission_list.html:71 #: perms/templates/perms/asset_permission_list.html:118 #: perms/templates/perms/remote_app_permission_list.html:16 -#: templates/_nav.html:21 users/forms.py:293 users/models/group.py:26 +#: templates/_nav.html:21 users/forms.py:313 users/models/group.py:26 #: users/models/user.py:388 users/templates/users/_select_user_modal.html:16 #: users/templates/users/user_detail.html:218 #: users/templates/users/user_list.html:38 @@ -3629,33 +3458,33 @@ msgstr "远程应用授权用户列表" msgid "RemoteApp permission RemoteApp list" msgstr "远程应用授权远程应用列表" -#: settings/api.py:28 +#: settings/api.py:31 msgid "Test mail sent to {}, please check" msgstr "邮件已经发送{}, 请检查" -#: settings/api.py:67 +#: settings/api.py:70 msgid "Test ldap success" msgstr "连接LDAP成功" -#: settings/api.py:104 +#: settings/api.py:107 msgid "Match {} s users" msgstr "匹配 {} 个用户" -#: settings/api.py:163 +#: settings/api.py:166 msgid "succeed: {} failed: {} total: {}" msgstr "成功:{} 失败:{} 总数:{}" -#: settings/api.py:185 settings/api.py:221 +#: settings/api.py:188 settings/api.py:224 msgid "" "Error: Account invalid (Please make sure the information such as Access key " "or Secret key is correct)" msgstr "错误:账户无效 (请确保 Access key 或 Secret key 等信息正确)" -#: settings/api.py:191 settings/api.py:227 +#: settings/api.py:194 settings/api.py:230 msgid "Create succeed" msgstr "创建成功" -#: settings/api.py:209 settings/api.py:247 +#: settings/api.py:212 settings/api.py:250 #: settings/templates/settings/terminal_setting.html:154 msgid "Delete succeed" msgstr "删除成功" @@ -4210,8 +4039,8 @@ msgid "Commercial support" msgstr "商业支持" #: templates/_header_bar.html:70 templates/_nav.html:30 -#: templates/_nav_user.html:32 users/forms.py:153 -#: users/templates/users/_user.html:43 +#: templates/_nav_user.html:32 users/forms.py:173 +#: users/templates/users/_user.html:44 #: users/templates/users/first_login.html:39 #: users/templates/users/user_password_update.html:40 #: users/templates/users/user_profile.html:17 @@ -4381,7 +4210,12 @@ msgstr "批量命令" msgid "Task monitor" msgstr "任务监控" -#: templates/_nav.html:130 users/templates/users/user_detail.html:257 +#: templates/_nav.html:127 tickets/views.py:16 tickets/views.py:30 +msgid "Tickets" +msgstr "工单管理" + +#: templates/_nav.html:130 tickets/models/base.py:23 +#: users/templates/users/user_detail.html:257 msgid "Login confirm" msgstr "登录复核" @@ -4730,6 +4564,18 @@ msgstr "地址" msgid "Alive" msgstr "在线" +#: terminal/templates/terminal/terminal_list.html:78 +msgid "Accept" +msgstr "接受" + +#: terminal/templates/terminal/terminal_list.html:80 +#: tickets/models/login_confirm.py:16 +#: tickets/templates/tickets/login_confirm_ticket_detail.html:10 +#: tickets/templates/tickets/login_confirm_ticket_list.html:57 +#: tickets/templates/tickets/login_confirm_ticket_list.html:94 +msgid "Reject" +msgstr "拒绝" + #: terminal/templates/terminal/terminal_modal_accept.html:5 msgid "Accept terminal registration" msgstr "接受终端注册" @@ -4763,11 +4609,163 @@ msgid "" "You should use your ssh client tools connect terminal: {}

{}" msgstr "你可以使用ssh客户端工具连接终端" +#: tickets/models/base.py:16 tickets/models/base.py:52 +#: tickets/templates/tickets/login_confirm_ticket_list.html:89 +msgid "Open" +msgstr "" + +#: tickets/models/base.py:17 +#: tickets/templates/tickets/login_confirm_ticket_list.html:90 +msgid "Closed" +msgstr "关闭" + +#: tickets/models/base.py:22 +msgid "General" +msgstr "一般" + +#: tickets/models/base.py:26 tickets/models/base.py:69 +msgid "User display name" +msgstr "用户显示名称" + +#: tickets/models/base.py:28 +#: tickets/templates/tickets/login_confirm_ticket_list.html:14 +#: tickets/templates/tickets/login_confirm_ticket_list.html:87 +msgid "Title" +msgstr "标题" + +#: tickets/models/base.py:29 tickets/models/base.py:70 +msgid "Body" +msgstr "内容" + +#: tickets/models/base.py:30 tickets/templates/tickets/ticket_detail.html:51 +msgid "Assignee" +msgstr "处理人" + +#: tickets/models/base.py:31 +msgid "Assignee display name" +msgstr "处理人名称" + +#: tickets/models/base.py:32 tickets/templates/tickets/ticket_detail.html:50 +msgid "Assignees" +msgstr "待处理人" + +#: tickets/models/base.py:33 +msgid "Assignees display name" +msgstr "待处理人名称" + +#: tickets/models/base.py:53 +msgid "{} {} this ticket" +msgstr "{} {} 这个工单" + +#: tickets/models/login_confirm.py:15 +#: tickets/templates/tickets/login_confirm_ticket_detail.html:9 +#: tickets/templates/tickets/login_confirm_ticket_list.html:56 +#: tickets/templates/tickets/login_confirm_ticket_list.html:93 +msgid "Approve" +msgstr "同意" + +#: tickets/models/login_confirm.py:24 +msgid "this order" +msgstr "这个工单" + +#: tickets/templates/tickets/ticket_detail.html:66 +#: tickets/templates/tickets/ticket_detail.html:81 +msgid "ago" +msgstr "前" + +#: tickets/utils.py:18 +msgid "New ticket" +msgstr "新工单" + +#: tickets/utils.py:21 +#, python-brace-format +msgid "" +"\n" +"
\n" +"

Your has a new ticket

\n" +"
\n" +" Title: {ticket.title}\n" +"
\n" +" User: {user}\n" +"
\n" +" Assignees: {ticket.assignees_display}\n" +"
\n" +" City: {ticket.city}\n" +"
\n" +" IP: {ticket.ip}\n" +"
\n" +" click here to review \n" +"
\n" +"
\n" +" " +msgstr "" +"\n" +"
\n" +"

您有一个新工单

\n" +"
\n" +" 标题: {ticket.title}\n" +"
\n" +" 用户: {user}\n" +"
\n" +" 待处理人: {ticket.assignees_display}\n" +"
\n" +" 城市: {ticket.city}\n" +"
\n" +" IP: {ticket.ip}\n" +"
\n" +" 点我查看 \n" +"
\n" +"
\n" +" " + +#: tickets/utils.py:48 +msgid "Ticket has been reply" +msgstr "工单已被回复" + +#: tickets/utils.py:49 +#, python-brace-format +msgid "" +"\n" +"
\n" +"

Your ticket has been replay

\n" +"
\n" +" Title: {ticket.title}\n" +"
\n" +" Assignee: {ticket.assignee_display}\n" +"
\n" +" Status: {ticket.status_display}\n" +"
\n" +"
\n" +"
\n" +" " +msgstr "" +"\n" +"
\n" +"

您的工单已被回复

\n" +"
\n" +" 标题: {ticket.title}\n" +"
\n" +" 处理人: {ticket.assignee_display}\n" +"
\n" +" 状态: {ticket.status_display}\n" +"
\n" +"
\n" +"
\n" +" " + +#: tickets/views.py:17 +msgid "Login confirm ticket list" +msgstr "登录复核工单列表" + +#: tickets/views.py:31 +msgid "Login confirm ticket detail" +msgstr "登录复核工单详情" + #: users/api/user.py:173 msgid "Could not reset self otp, use profile reset instead" msgstr "不能再该页面重置MFA, 请去个人信息页面重置" -#: users/forms.py:32 users/models/user.py:392 +#: users/forms.py:47 users/models/user.py:392 #: users/templates/users/_select_user_modal.html:15 #: users/templates/users/user_detail.html:88 #: users/templates/users/user_list.html:37 @@ -4775,44 +4773,51 @@ msgstr "不能再该页面重置MFA, 请去个人信息页面重置" msgid "Role" msgstr "角色" -#: users/forms.py:35 users/forms.py:232 +#: users/forms.py:51 users/models/user.py:427 +#: users/templates/users/user_detail.html:104 +#: users/templates/users/user_list.html:39 +#: users/templates/users/user_profile.html:102 +msgid "Source" +msgstr "用户来源" + +#: users/forms.py:54 users/forms.py:252 #: users/templates/users/user_update.html:30 msgid "ssh public key" msgstr "ssh公钥" -#: users/forms.py:36 users/forms.py:233 +#: users/forms.py:55 users/forms.py:253 msgid "ssh-rsa AAAA..." msgstr "" -#: users/forms.py:37 +#: users/forms.py:56 msgid "Paste user id_rsa.pub here." msgstr "复制用户公钥到这里" -#: users/forms.py:51 users/templates/users/user_detail.html:226 +#: users/forms.py:71 users/templates/users/user_detail.html:226 msgid "Join user groups" msgstr "添加到用户组" -#: users/forms.py:86 users/forms.py:247 +#: users/forms.py:106 users/forms.py:267 msgid "Public key should not be the same as your old one." msgstr "不能和原来的密钥相同" -#: users/forms.py:90 users/forms.py:251 users/serializers/user.py:110 +#: users/forms.py:110 users/forms.py:271 users/serializers/user.py:109 msgid "Not a valid ssh public key" msgstr "ssh密钥不合法" -#: users/forms.py:103 users/views/login.py:114 users/views/user.py:287 +#: users/forms.py:123 users/views/login.py:114 users/views/user.py:287 msgid "* Your password does not meet the requirements" msgstr "* 您的密码不符合要求" -#: users/forms.py:124 +#: users/forms.py:144 msgid "Reset link will be generated and sent to the user" msgstr "生成重置密码链接,通过邮件发送给用户" -#: users/forms.py:125 +#: users/forms.py:145 msgid "Set password" msgstr "设置密码" -#: users/forms.py:132 xpack/plugins/change_auth_plan/models.py:89 +#: users/forms.py:152 xpack/plugins/change_auth_plan/models.py:88 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:51 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:69 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:57 @@ -4820,7 +4825,7 @@ msgstr "设置密码" msgid "Password strategy" msgstr "密码策略" -#: users/forms.py:159 +#: users/forms.py:179 msgid "" "When enabled, you will enter the MFA binding process the next time you log " "in. you can also directly bind in \"personal information -> quick " @@ -4829,11 +4834,11 @@ msgstr "" "启用之后您将会在下次登录时进入MFA绑定流程;您也可以在(个人信息->快速修改->更" "改MFA设置)中直接绑定!" -#: users/forms.py:169 +#: users/forms.py:189 msgid "* Enable MFA authentication to make the account more secure." msgstr "* 启用MFA认证,使账号更加安全。" -#: users/forms.py:179 +#: users/forms.py:199 msgid "" "In order to protect you and your company, please keep your account, password " "and key sensitive information properly. (for example: setting complex " @@ -4842,41 +4847,41 @@ msgstr "" "为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:" "设置复杂密码,启用MFA认证)" -#: users/forms.py:186 users/templates/users/first_login.html:48 +#: users/forms.py:206 users/templates/users/first_login.html:48 #: users/templates/users/first_login.html:110 #: users/templates/users/first_login.html:139 msgid "Finish" msgstr "完成" -#: users/forms.py:192 +#: users/forms.py:212 msgid "Old password" msgstr "原来密码" -#: users/forms.py:197 +#: users/forms.py:217 msgid "New password" msgstr "新密码" -#: users/forms.py:202 +#: users/forms.py:222 msgid "Confirm password" msgstr "确认密码" -#: users/forms.py:212 +#: users/forms.py:232 msgid "Old password error" msgstr "原来密码错误" -#: users/forms.py:220 +#: users/forms.py:240 msgid "Password does not match" msgstr "密码不一致" -#: users/forms.py:230 +#: users/forms.py:250 msgid "Automatically configure and download the SSH key" msgstr "自动配置并下载SSH密钥" -#: users/forms.py:234 +#: users/forms.py:254 msgid "Paste your id_rsa.pub here." msgstr "复制你的公钥到这里" -#: users/forms.py:268 users/forms.py:273 users/forms.py:323 +#: users/forms.py:288 users/forms.py:293 users/forms.py:343 #: xpack/plugins/orgs/forms.py:18 msgid "Select users" msgstr "选择用户" @@ -4919,12 +4924,6 @@ msgstr "头像" msgid "Wechat" msgstr "微信" -#: users/models/user.py:427 users/templates/users/user_detail.html:104 -#: users/templates/users/user_list.html:39 -#: users/templates/users/user_profile.html:102 -msgid "Source" -msgstr "用户来源" - #: users/models/user.py:431 msgid "Date password last updated" msgstr "最后更新密码日期" @@ -4965,11 +4964,11 @@ msgstr " 是否过期" msgid "Avatar url" msgstr "头像路径" -#: users/serializers/user.py:66 +#: users/serializers/user.py:65 msgid "Role limit to {}" msgstr "角色只能为 {}" -#: users/serializers/user.py:78 +#: users/serializers/user.py:77 msgid "Password does not match security rules" msgstr "密码不满足安全规则" @@ -5005,7 +5004,7 @@ msgstr "选择用户" msgid "Asset num" msgstr "资产数量" -#: users/templates/users/_user.html:26 +#: users/templates/users/_user.html:27 msgid "Security and Role" msgstr "角色安全" @@ -5748,8 +5747,8 @@ msgstr "" "具)
注意: 如果同时设置了定期执行和周期执行,优先使用定期执行" #: xpack/plugins/change_auth_plan/meta.py:9 -#: xpack/plugins/change_auth_plan/models.py:117 -#: xpack/plugins/change_auth_plan/models.py:257 +#: xpack/plugins/change_auth_plan/models.py:116 +#: xpack/plugins/change_auth_plan/models.py:256 #: xpack/plugins/change_auth_plan/views.py:33 #: xpack/plugins/change_auth_plan/views.py:50 #: xpack/plugins/change_auth_plan/views.py:74 @@ -5760,20 +5759,20 @@ msgstr "" msgid "Change auth plan" msgstr "改密计划" -#: xpack/plugins/change_auth_plan/models.py:58 +#: xpack/plugins/change_auth_plan/models.py:57 msgid "Custom password" msgstr "自定义密码" -#: xpack/plugins/change_auth_plan/models.py:59 +#: xpack/plugins/change_auth_plan/models.py:58 msgid "All assets use the same random password" msgstr "所有资产使用相同的随机密码" -#: xpack/plugins/change_auth_plan/models.py:60 +#: xpack/plugins/change_auth_plan/models.py:59 msgid "All assets use different random password" msgstr "所有资产使用不同的随机密码" -#: xpack/plugins/change_auth_plan/models.py:79 -#: xpack/plugins/change_auth_plan/models.py:148 +#: xpack/plugins/change_auth_plan/models.py:78 +#: xpack/plugins/change_auth_plan/models.py:147 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:100 #: xpack/plugins/cloud/models.py:165 xpack/plugins/cloud/models.py:219 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:91 @@ -5782,8 +5781,8 @@ msgstr "所有资产使用不同的随机密码" msgid "Cycle perform" msgstr "周期执行" -#: xpack/plugins/change_auth_plan/models.py:84 -#: xpack/plugins/change_auth_plan/models.py:146 +#: xpack/plugins/change_auth_plan/models.py:83 +#: xpack/plugins/change_auth_plan/models.py:145 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:92 #: xpack/plugins/cloud/models.py:170 xpack/plugins/cloud/models.py:217 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:83 @@ -5792,37 +5791,37 @@ msgstr "周期执行" msgid "Regularly perform" msgstr "定期执行" -#: xpack/plugins/change_auth_plan/models.py:93 +#: xpack/plugins/change_auth_plan/models.py:92 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:74 msgid "Password rules" msgstr "密码规则" -#: xpack/plugins/change_auth_plan/models.py:213 +#: xpack/plugins/change_auth_plan/models.py:212 msgid "* For security, do not change {} user's password" msgstr "* 为了安全,禁止更改 {} 用户的密码" -#: xpack/plugins/change_auth_plan/models.py:217 +#: xpack/plugins/change_auth_plan/models.py:216 msgid "Assets is empty, please add the asset" msgstr "资产为空,请添加资产" -#: xpack/plugins/change_auth_plan/models.py:261 +#: xpack/plugins/change_auth_plan/models.py:260 msgid "Change auth plan snapshot" msgstr "改密计划快照" -#: xpack/plugins/change_auth_plan/models.py:276 -#: xpack/plugins/change_auth_plan/models.py:433 +#: xpack/plugins/change_auth_plan/models.py:275 +#: xpack/plugins/change_auth_plan/models.py:426 msgid "Change auth plan execution" msgstr "改密计划执行" -#: xpack/plugins/change_auth_plan/models.py:442 +#: xpack/plugins/change_auth_plan/models.py:435 msgid "Change auth plan execution subtask" msgstr "改密计划执行子任务" -#: xpack/plugins/change_auth_plan/models.py:460 +#: xpack/plugins/change_auth_plan/models.py:453 msgid "Authentication failed" msgstr "认证失败" -#: xpack/plugins/change_auth_plan/models.py:462 +#: xpack/plugins/change_auth_plan/models.py:455 msgid "Connection timeout" msgstr "连接超时" @@ -6432,6 +6431,30 @@ msgstr "密码匣子" msgid "vault create" msgstr "创建" +#~ msgid "Log in frequently and try again later" +#~ msgstr "登录频繁, 稍后重试" + +#~ msgid "Please carry seed value and conduct MFA secondary certification" +#~ msgstr "请携带seed值, 进行MFA二次认证" + +#~ msgid "Please verify the user name and password first" +#~ msgstr "请先进行用户名和密码验证" + +#~ msgid "MFA certification failed" +#~ msgstr "MFA认证失败" + +#~ msgid "Accepted" +#~ msgstr "已接受" + +#~ msgid "Rejected" +#~ msgstr "已拒绝" + +#~ msgid "New order" +#~ msgstr "新工单" + +#~ msgid "Orders" +#~ msgstr "工单管理" + #~ msgid "" #~ "The username or password you entered is incorrect, please enter it again." #~ msgstr "您输入的用户名或密码不正确,请重新输入。" diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index aea8254f6..fdbbfd1f9 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -1317,3 +1317,7 @@ function initDateRangePicker(selector, options) { options = Object.assign(defaultOption, options); return $(selector).daterangepicker(options); } + +function reloadPage() { + window.location.reload(); +} diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index 2aecd2fd6..794d9b243 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -127,7 +127,7 @@ {% trans 'Tickets' %} {% endif %} diff --git a/apps/tickets/api/base.py b/apps/tickets/api/base.py index a029d8fab..266f9f855 100644 --- a/apps/tickets/api/base.py +++ b/apps/tickets/api/base.py @@ -1,21 +1,38 @@ # -*- coding: utf-8 -*- # -from rest_framework import viewsets, generics -from .. import serializers, models +from rest_framework import viewsets +from django.shortcuts import get_object_or_404 + +from common.utils import lazyproperty +from .. import serializers, models, mixins -class TicketViewSet(viewsets.ModelViewSet): +class TicketViewSet(mixins.TicketMixin, viewsets.ModelViewSet): serializer_class = serializers.TicketSerializer - - def get_queryset(self): - queryset = models.Ticket.objects.all().none() - return queryset + queryset = models.Ticket.objects.all() -class CommentViewSet(viewsets.ModelViewSet): +class TicketCommentViewSet(viewsets.ModelViewSet): serializer_class = serializers.CommentSerializer + def check_permissions(self, request): + ticket = self.ticket + if request.user == ticket.user or request.user in ticket.assignees.all(): + return True + return False + + def get_serializer_context(self): + context = super().get_serializer_context() + context['ticket'] = self.ticket + return context + + @lazyproperty + def ticket(self): + ticket_id = self.kwargs.get('ticket_id') + ticket = get_object_or_404(models.Ticket, pk=ticket_id) + return ticket + def get_queryset(self): - queryset = models.Comment.objects.none() + queryset = self.ticket.comments.all() return queryset diff --git a/apps/tickets/api/login_confirm.py b/apps/tickets/api/login_confirm.py index c990ee339..599931a52 100644 --- a/apps/tickets/api/login_confirm.py +++ b/apps/tickets/api/login_confirm.py @@ -1,39 +1,30 @@ # -*- coding: utf-8 -*- # from rest_framework import viewsets, generics +from rest_framework.serializers import ValidationError from django.shortcuts import get_object_or_404 from common.permissions import IsValidUser from common.mixins import CommonApiMixin -from .. import serializers +from .. import serializers, mixins from ..models import LoginConfirmTicket -class LoginConfirmTicketViewSet(CommonApiMixin, viewsets.ModelViewSet): +class LoginConfirmTicketViewSet(CommonApiMixin, mixins.TicketMixin, viewsets.ModelViewSet): serializer_class = serializers.LoginConfirmTicketSerializer permission_classes = (IsValidUser,) - filter_fields = ['status', 'title'] + queryset = LoginConfirmTicket.objects.all() + filter_fields = ['status', 'title', 'action', 'ip'] search_fields = ['user_display', 'title', 'ip', 'city'] - def get_queryset(self): - queryset = LoginConfirmTicket.objects.all()\ - .filter(assignees=self.request.user) - return queryset - - -class LoginConfirmTicketsCreateActionApi(generics.CreateAPIView): - permission_classes = (IsValidUser,) - serializer_class = serializers.LoginConfirmTicketActionSerializer - - def get_ticket(self): - ticket_id = self.kwargs.get('pk') - queryset = LoginConfirmTicket.objects.all()\ - .filter(assignees=self.request.user) - ticket = get_object_or_404(queryset, id=ticket_id) - return ticket - - def get_serializer_context(self): - context = super().get_serializer_context() - ticket = self.get_ticket() - context['ticket'] = ticket - return context + # def check_update_permission(self, serializer): + # data = serializer.validated_data + # action = data.get("action") + # user = self.request.user + # instance = serializer.instance + # if action and user not in instance.assignees.all(): + # error = {"action": "Only assignees can update"} + # raise ValidationError(error) + # + # def perform_update(self, serializer): + # self.check_update_permission(serializer) diff --git a/apps/tickets/apps.py b/apps/tickets/apps.py index 3ea742ac3..d155cbde2 100644 --- a/apps/tickets/apps.py +++ b/apps/tickets/apps.py @@ -3,3 +3,7 @@ from django.apps import AppConfig class TicketsConfig(AppConfig): name = 'tickets' + + def ready(self): + from . import signals_handler + return super().ready() diff --git a/apps/tickets/mixins.py b/apps/tickets/mixins.py new file mode 100644 index 000000000..d648ffe19 --- /dev/null +++ b/apps/tickets/mixins.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# +from django.db.models import Q + + +class TicketMixin: + def get_queryset(self): + queryset = super().get_queryset() + assign = self.request.GET.get('assign', None) + if assign is None: + queryset = queryset.filter( + Q(assignees=self.request.user) | Q(user=self.request.user) + ).distinct() + elif assign in ['1']: + queryset = queryset.filter(assignees=self.request.user) + else: + queryset = queryset.filter(user=self.request.user) + return queryset diff --git a/apps/tickets/models/base.py b/apps/tickets/models/base.py index 3fcb702cf..d369e534f 100644 --- a/apps/tickets/models/base.py +++ b/apps/tickets/models/base.py @@ -31,16 +31,12 @@ class Ticket(CommonModelMixin): assignee_display = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Assignee display name")) assignees = models.ManyToManyField('users.User', related_name='%(class)s_assigned', verbose_name=_("Assignees")) assignees_display = models.CharField(max_length=128, verbose_name=_("Assignees display name"), blank=True) - type = models.CharField(max_length=16, default='general', verbose_name=_("Type")) + type = models.CharField(max_length=16, choices=TYPE_CHOICES, default=TYPE_GENERAL, verbose_name=_("Type")) status = models.CharField(choices=STATUS_CHOICES, max_length=16, default='open') def __str__(self): return '{}: {}'.format(self.user_display, self.title) - @property - def comments(self): - return Comment.objects.filter(order_id=self.id) - @property def body_as_html(self): return self.body.replace('\n', '
') @@ -49,17 +45,29 @@ class Ticket(CommonModelMixin): def status_display(self): return self.get_status_display() + def create_status_comment(self, status, user): + if status == self.STATUS_CLOSED: + action = _("Close") + else: + action = _("Open") + body = _('{} {} this ticket').format(self.user, action) + self.comments.create(user=user, body=body) + + def perform_status(self, status, user): + if self.status == status: + return + self.status = status + self.save() + class Meta: ordering = ('-date_created',) class Comment(CommonModelMixin): - ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE) + ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE, related_name='comments') user = models.ForeignKey('users.User', on_delete=models.SET_NULL, null=True, verbose_name=_("User"), related_name='comments') user_display = models.CharField(max_length=128, verbose_name=_("User display name")) body = models.TextField(verbose_name=_("Body")) class Meta: ordering = ('date_created', ) - - diff --git a/apps/tickets/models/login_confirm.py b/apps/tickets/models/login_confirm.py index 0781d20be..87baaefee 100644 --- a/apps/tickets/models/login_confirm.py +++ b/apps/tickets/models/login_confirm.py @@ -18,3 +18,16 @@ class LoginConfirmTicket(Ticket): ip = models.GenericIPAddressField(blank=True, null=True) city = models.CharField(max_length=16, blank=True, default='') action = models.CharField(choices=ACTION_CHOICES, max_length=16, default='', blank=True) + + def create_action_comment(self, action, user): + action_display = dict(self.ACTION_CHOICES).get(action) + body = '{} {} {}'.format(user, action_display, _("this order")) + self.comments.create(body=body, user=user, user_display=str(user)) + + def perform_action(self, action, user): + self.create_action_comment(action, user) + self.action = action + self.status = self.STATUS_CLOSED + self.assignee = user + self.assignees_display = str(user) + self.save() diff --git a/apps/tickets/permissions.py b/apps/tickets/permissions.py new file mode 100644 index 000000000..5bc7be14d --- /dev/null +++ b/apps/tickets/permissions.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# + +from rest_framework.permissions import BasePermission + + diff --git a/apps/tickets/serializers/base.py b/apps/tickets/serializers/base.py index eb4040f59..e465fa691 100644 --- a/apps/tickets/serializers/base.py +++ b/apps/tickets/serializers/base.py @@ -21,7 +21,24 @@ class TicketSerializer(serializers.ModelSerializer): ] +class CurrentTicket(object): + ticket = None + + def set_context(self, serializer_field): + self.ticket = serializer_field.context['ticket'] + + def __call__(self): + return self.ticket + + class CommentSerializer(serializers.ModelSerializer): + user = serializers.HiddenField( + default=serializers.CurrentUserDefault(), + ) + ticket = serializers.HiddenField( + default=CurrentTicket() + ) + class Meta: model = models.Comment fields = [ diff --git a/apps/tickets/serializers/login_confirm.py b/apps/tickets/serializers/login_confirm.py index 4649294ec..e7e224c1e 100644 --- a/apps/tickets/serializers/login_confirm.py +++ b/apps/tickets/serializers/login_confirm.py @@ -17,13 +17,28 @@ class LoginConfirmTicketSerializer(serializers.ModelSerializer): ] read_only_fields = TicketSerializer.Meta.read_only_fields + def create(self, validated_data): + validated_data.pop('action') + return super().create(validated_data) + + def update(self, instance, validated_data): + action = validated_data.get("action") + user = self.context["request"].user + if action and user not in instance.assignees.all(): + error = {"action": "Only assignees can update"} + raise serializers.ValidationError(error) + instance = super().update(instance, validated_data) + if action: + instance.perform_action(action, user) + return instance + class LoginConfirmTicketActionSerializer(serializers.ModelSerializer): comment = serializers.CharField(allow_blank=True) class Meta: model = LoginConfirmTicket - fields = ['action', 'comment'] + fields = ['action'] def update(self, instance, validated_data): pass diff --git a/apps/tickets/signals_handler.py b/apps/tickets/signals_handler.py index a1b9dbbec..212892d0c 100644 --- a/apps/tickets/signals_handler.py +++ b/apps/tickets/signals_handler.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- # from django.dispatch import receiver -from django.db.models.signals import m2m_changed, post_save +from django.db.models.signals import m2m_changed, post_save, pre_save from common.utils import get_logger -from .models import LoginConfirmTicket +from .models import LoginConfirmTicket, Ticket, Comment from .utils import ( send_login_confirm_ticket_mail_to_assignees, send_login_confirm_action_mail_to_user @@ -16,16 +16,34 @@ logger = get_logger(__name__) @receiver(m2m_changed, sender=LoginConfirmTicket.assignees.through) def on_login_confirm_ticket_assignees_set(sender, instance=None, action=None, - model=None, pk_set=None, **kwargs): + reverse=False, model=None, + pk_set=None, **kwargs): if action == 'post_add': logger.debug('New ticket create, send mail: {}'.format(instance.id)) assignees = model.objects.filter(pk__in=pk_set) send_login_confirm_ticket_mail_to_assignees(instance, assignees) + if action.startswith('post') and not reverse: + instance.assignees_display = ', '.join([ + str(u) for u in instance.assignees.all() + ]) + instance.save() @receiver(post_save, sender=LoginConfirmTicket) def on_login_confirm_ticket_status_change(sender, instance=None, created=False, **kwargs): - if created or instance.status == "pending": + if created or instance.status == "open": return logger.debug('Ticket changed, send mail: {}'.format(instance.id)) send_login_confirm_action_mail_to_user(instance) + + +@receiver(pre_save, sender=LoginConfirmTicket) +def on_ticket_create(sender, instance=None, **kwargs): + instance.user_display = str(instance.user) + if instance.assignee: + instance.assignee_display = str(instance.assignee) + + +@receiver(pre_save, sender=Comment) +def on_comment_create(sender, instance=None, **kwargs): + instance.user_display = str(instance.user) diff --git a/apps/tickets/templates/tickets/login_confirm_ticket_detail.html b/apps/tickets/templates/tickets/login_confirm_ticket_detail.html index 2b43da756..e0cc25b0d 100644 --- a/apps/tickets/templates/tickets/login_confirm_ticket_detail.html +++ b/apps/tickets/templates/tickets/login_confirm_ticket_detail.html @@ -1,137 +1,34 @@ -{% extends 'base.html' %} +{% extends 'tickets/ticket_detail.html' %} {% load static %} {% load i18n %} -{% block content %} -
-
-
-
-
-
- {{ object.title }} -
- -
-
-
-
-
-
-
-
{% trans 'User' %}:
{{ object.user_display }}
-
{% trans 'IP' %}:
{{ object.ip }}
-
{% trans 'Assignees' %}:
{{ object.assignees_display }}
-
{% trans 'Status' %}:
-
- {% if object.status == "accpeted" %} - - {{ object.get_status_display }} - - {% endif %} - {% if object.status == "rejected" %} - - {{ object.get_status_display }} - - {% endif %} - {% if object.status == "pending" %} - - {{ object.get_status_display }} - - {% endif %} -
-
-
-
-
-

-
{% trans 'City' %}:
{{ object.city }}
-
{% trans 'Assignee' %}:
{{ object.assignee_display | default_if_none:"" }}
-
{% trans 'Date created' %}:
{{ object.date_created }}
-
-
-
-
-
-
-
-
- {% for comment in object.comments %} -
- - image - -
- {{ comment.user_display }} {{ comment.date_created|timesince}} {% trans 'ago' %} -
- {{ comment.date_created }} -
- {{ comment.body }} -
-
-
- {% endfor %} -
-
- - image - -
- -
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
- +{% block status %} {% endblock %} + +{% block action %} + {% trans 'Approve' %} + {% trans 'Reject' %} +{% endblock %} + {% block custom_foot_js %} +{{ block.super }} {% endblock %} + + + diff --git a/apps/tickets/templates/tickets/login_confirm_ticket_list.html b/apps/tickets/templates/tickets/login_confirm_ticket_list.html index dd5129ea9..51a14b3ae 100644 --- a/apps/tickets/templates/tickets/login_confirm_ticket_list.html +++ b/apps/tickets/templates/tickets/login_confirm_ticket_list.html @@ -13,7 +13,6 @@ {% trans 'Title' %} {% trans 'User' %} - {% trans 'IP' %} {% trans 'Status' %} {% trans 'Datetime' %} {% trans 'Action' %} @@ -39,10 +38,6 @@ function initTable() { $(td).html(detailBtn.replace("{{ DEFAULT_PK }}", rowData.id)); }}, {targets: 3, createdCell: function (td, cellData, rowData) { - var d = cellData + "(" + rowData.city + ")"; - $(td).html(d) - }}, - {targets: 4, createdCell: function (td, cellData, rowData) { if (cellData === "approval") { $(td).html('') } else if (cellData === "rejected") { @@ -53,12 +48,12 @@ function initTable() { $(td).html('') } }}, - {targets: 5, createdCell: function (td, cellData) { + {targets: 4, createdCell: function (td, cellData) { var d = toSafeLocalDateStr(cellData); $(td).html(d) }}, - {targets: 6, createdCell: function (td, cellData, rowData) { - var acceptBtn = '{% trans "Accept" %} '; + {targets: 5, createdCell: function (td, cellData, rowData) { + var acceptBtn = '{% trans "Approve" %} '; var rejectBtn = '{% trans "Reject" %}'; acceptBtn = acceptBtn.replace('{{ DEFAULT_PK }}', cellData); rejectBtn = rejectBtn.replace('{{ DEFAULT_PK }}', cellData); @@ -74,7 +69,7 @@ function initTable() { ajax_url: '{% url "api-tickets:login-confirm-ticket-list" %}', columns: [ {data: "id"}, {data: "title"}, - {data: "user_display"}, {data: "ip"}, + {data: "user_display"}, {data: "status", ticketable: false}, {data: "date_created", width: "120px"}, {data: "id", ticketable: false} @@ -101,18 +96,15 @@ $(document).ready(function(){ ]; initTableFilterDropdown('#login_confirm_ticket_list_table_filter input', menu) }).on('click', '.btn-action', function () { - var actionCreateUrl = "{% url 'api-tickets:login-confirm-ticket-create-action' pk=DEFAULT_PK %}"; - var ticketId = $(this).data('uid'); - actionCreateUrl = actionCreateUrl.replace("{{ DEFAULT_PK }}", ticketId); + var ticketId = $(this).data("uid"); var action = $(this).data('action'); - var comment = ''; + var ticketDetailUrl = "{% url 'api-tickets:login-confirm-ticket-detail' pk=DEFAULT_PK %}"; + ticketDetailUrl = ticketDetailUrl.replace("{{ DEFAULT_PK }}", ticketId); var data = { - url: actionCreateUrl, - method: 'POST', - body: JSON.stringify({action: action, comment: comment}), - success: function () { - window.location.reload(); - } + url: ticketDetailUrl, + body: JSON.stringify({action: action}), + method: "PATCH", + success: reloadPage }; requestApi(data); }) diff --git a/apps/tickets/templates/tickets/ticket_detail.html b/apps/tickets/templates/tickets/ticket_detail.html new file mode 100644 index 000000000..320010398 --- /dev/null +++ b/apps/tickets/templates/tickets/ticket_detail.html @@ -0,0 +1,162 @@ +{% extends 'base.html' %} +{% load static %} +{% load i18n %} + +{% block content %} +
+
+
+
+
+
+ {{ object.title }} +
+ +
+
+
+
+
+
+
+
{% trans 'User' %}:
{{ object.user_display }}
+
{% trans 'Type' %}:
{{ object.get_type_display | default_if_none:"" }}
+
{% trans 'Status' %}:
+
+ {% if object.status == "open" %} + + {{ object.get_status_display }} + + {% elif object.status == "closed" %} + + {{ object.get_status_display }} + + {% endif %} +
+
+
+
+
+
{% trans 'Assignees' %}:
{{ object.assignees_display }}
+
{% trans 'Assignee' %}:
{{ object.assignee_display | default_if_none:"" }}
+
{% trans 'Date created' %}:
{{ object.date_created }}
+
+
+
+
+
+
+
+
+
+ + image + +
+ {{ object.user_display }} {{ object.date_created|timesince}} {% trans 'ago' %} +
+ {{ object.date_created }} +
+ {{ object.body_as_html | safe }} +
+
+
+ {% for comment in object.comments.all %} + +
+ + image + +
+ {{ comment.user_display }} {{ comment.date_created|timesince}} {% trans 'ago' %} +
+ {{ comment.date_created }} +
+ {{ comment.body }} +
+
+
+ {% endfor %} +
+
+ + image + +
+ +
+
+
+ {% block action %} + {% endblock %} + {% block status %} + {% trans 'Close' %} + {% endblock %} + {% block comment %} + {% trans 'Comment' %} + {% endblock %} +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +{% endblock %} +{% block custom_foot_js %} + +{% endblock %} diff --git a/apps/tickets/urls/api_urls.py b/apps/tickets/urls/api_urls.py index c998efda6..1972fb539 100644 --- a/apps/tickets/urls/api_urls.py +++ b/apps/tickets/urls/api_urls.py @@ -9,15 +9,11 @@ app_name = 'tickets' router = DefaultRouter() router.register('tickets', api.TicketViewSet, 'ticket') +router.register('tickets/(?P[0-9a-zA-Z\-]{36})/comments', api.TicketCommentViewSet, 'ticket-comment') router.register('login-confirm-tickets', api.LoginConfirmTicketViewSet, 'login-confirm-ticket') -router.register('tickets//comments/', api.CommentViewSet, 'ticket-comment') urlpatterns = [ - path('login-confirm-tickets//actions/', - api.LoginConfirmTicketsCreateActionApi.as_view(), - name='login-confirm-ticket-create-action' - ), ] urlpatterns += router.urls diff --git a/apps/tickets/views.py b/apps/tickets/views.py index 3816f8097..aac6b136c 100644 --- a/apps/tickets/views.py +++ b/apps/tickets/views.py @@ -1,13 +1,14 @@ from django.views.generic import TemplateView, DetailView from django.utils.translation import ugettext as _ -from common.permissions import PermissionsMixin, IsOrgAdmin +from common.permissions import PermissionsMixin, IsValidUser from .models import LoginConfirmTicket +from . import mixins class LoginConfirmTicketListView(PermissionsMixin, TemplateView): template_name = 'tickets/login_confirm_ticket_list.html' - permission_classes = (IsOrgAdmin,) + permission_classes = (IsValidUser,) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -18,12 +19,10 @@ class LoginConfirmTicketListView(PermissionsMixin, TemplateView): return context -class LoginConfirmTicketDetailView(PermissionsMixin, DetailView): +class LoginConfirmTicketDetailView(PermissionsMixin, mixins.TicketMixin, DetailView): template_name = 'tickets/login_confirm_ticket_detail.html' - permission_classes = (IsOrgAdmin,) - - def get_queryset(self): - return LoginConfirmTicket.objects.filter(assignees=self.request.user) + queryset = LoginConfirmTicket.objects.all() + permission_classes = (IsValidUser,) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index 7e25112d6..b7ec186b7 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -13,11 +13,11 @@ from ..models import User, UserGroup __all__ = [ 'UserSerializer', 'UserPKUpdateSerializer', 'UserUpdateGroupSerializer', 'ChangeUserPasswordSerializer', 'ResetOTPSerializer', + 'UserProfileSerializer', ] class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): - can_update = serializers.SerializerMethodField() can_delete = serializers.SerializerMethodField() @@ -135,3 +135,11 @@ class ResetOTPSerializer(serializers.Serializer): def update(self, instance, validated_data): pass + + +class UserProfileSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = [ + 'id', 'username', 'name', 'role', 'email' + ]