mirror of https://github.com/jumpserver/jumpserver
[Update] 修改tickets
parent
bd323d608e
commit
9e4874834f
|
@ -10,7 +10,7 @@ from common.utils import get_logger, get_object_or_none
|
||||||
from common.permissions import IsOrgAdmin
|
from common.permissions import IsOrgAdmin
|
||||||
from ..models import LoginConfirmSetting
|
from ..models import LoginConfirmSetting
|
||||||
from ..serializers import LoginConfirmSettingSerializer
|
from ..serializers import LoginConfirmSettingSerializer
|
||||||
from .. import errors
|
from .. import errors, mixins
|
||||||
|
|
||||||
__all__ = ['LoginConfirmSettingUpdateApi', 'LoginConfirmTicketStatusApi']
|
__all__ = ['LoginConfirmSettingUpdateApi', 'LoginConfirmTicketStatusApi']
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
@ -31,7 +31,7 @@ class LoginConfirmSettingUpdateApi(UpdateAPIView):
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
class LoginConfirmTicketStatusApi(APIView):
|
class LoginConfirmTicketStatusApi(mixins.AuthMixin, APIView):
|
||||||
permission_classes = ()
|
permission_classes = ()
|
||||||
|
|
||||||
def get_ticket(self):
|
def get_ticket(self):
|
||||||
|
@ -45,24 +45,9 @@ class LoginConfirmTicketStatusApi(APIView):
|
||||||
return ticket
|
return ticket
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
ticket_id = self.request.session.get("auth_ticket_id")
|
|
||||||
ticket = self.get_ticket()
|
|
||||||
try:
|
try:
|
||||||
if not ticket:
|
self.check_user_login_confirm()
|
||||||
raise errors.LoginConfirmOtherError(ticket_id, _("not found"))
|
return Response({"msg": "ok"})
|
||||||
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 == ticket.ACTION_REJECT:
|
|
||||||
raise errors.LoginConfirmOtherError(
|
|
||||||
ticket_id, ticket.get_action_display()
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise errors.LoginConfirmOtherError(
|
|
||||||
ticket_id, ticket.get_status_display()
|
|
||||||
)
|
|
||||||
except errors.NeedMoreInfoError as e:
|
except errors.NeedMoreInfoError as e:
|
||||||
return Response(e.as_data(), status=200)
|
return Response(e.as_data(), status=200)
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ class TokenCreateApi(AuthMixin, CreateAPIView):
|
||||||
self.create_session_if_need()
|
self.create_session_if_need()
|
||||||
# 如果认证没有过,检查账号密码
|
# 如果认证没有过,检查账号密码
|
||||||
try:
|
try:
|
||||||
user = self.check_user_auth()
|
user = self.check_user_auth_if_need()
|
||||||
self.check_user_mfa_if_need(user)
|
self.check_user_mfa_if_need(user)
|
||||||
self.check_user_login_confirm_if_need(user)
|
self.check_user_login_confirm_if_need(user)
|
||||||
self.send_auth_signal(success=True, user=user)
|
self.send_auth_signal(success=True, user=user)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
import time
|
import time
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
from common.utils import get_object_or_none, get_request_ip, get_logger
|
from common.utils import get_object_or_none, get_request_ip, get_logger
|
||||||
from users.models import User
|
from users.models import User
|
||||||
|
@ -49,8 +50,8 @@ class AuthMixin:
|
||||||
raise errors.BlockLoginError(username=username, ip=ip)
|
raise errors.BlockLoginError(username=username, ip=ip)
|
||||||
|
|
||||||
def check_user_auth(self):
|
def check_user_auth(self):
|
||||||
request = self.request
|
|
||||||
self.check_is_block()
|
self.check_is_block()
|
||||||
|
request = self.request
|
||||||
if hasattr(request, 'data'):
|
if hasattr(request, 'data'):
|
||||||
username = request.data.get('username', '')
|
username = request.data.get('username', '')
|
||||||
password = request.data.get('password', '')
|
password = request.data.get('password', '')
|
||||||
|
@ -73,11 +74,20 @@ class AuthMixin:
|
||||||
request.session['user_id'] = str(user.id)
|
request.session['user_id'] = str(user.id)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
def check_user_auth_if_need(self):
|
||||||
|
request = self.request
|
||||||
|
if request.session.get('auth_password') and \
|
||||||
|
request.session.get('user_id'):
|
||||||
|
user = self.get_user_from_session()
|
||||||
|
if user:
|
||||||
|
return user
|
||||||
|
return self.check_user_auth()
|
||||||
|
|
||||||
def check_user_mfa_if_need(self, user):
|
def check_user_mfa_if_need(self, user):
|
||||||
if self.request.session.get('auth_mfa'):
|
if self.request.session.get('auth_mfa'):
|
||||||
return True
|
return
|
||||||
if not user.otp_enabled or not user.otp_secret_key:
|
if not user.otp_enabled or not user.otp_secret_key:
|
||||||
return True
|
return
|
||||||
raise errors.MFARequiredError()
|
raise errors.MFARequiredError()
|
||||||
|
|
||||||
def check_user_mfa(self, code):
|
def check_user_mfa(self, code):
|
||||||
|
@ -90,28 +100,53 @@ class AuthMixin:
|
||||||
return
|
return
|
||||||
raise errors.MFAFailedError(username=user.username, request=self.request)
|
raise errors.MFAFailedError(username=user.username, request=self.request)
|
||||||
|
|
||||||
def check_user_login_confirm_if_need(self, user):
|
def get_ticket(self):
|
||||||
from tickets.models import LoginConfirmTicket
|
from tickets.models import LoginConfirmTicket
|
||||||
confirm_setting = user.get_login_confirm_setting()
|
ticket_id = self.request.session.get("auth_ticket_id")
|
||||||
if self.request.session.get('auth_confirm') or not confirm_setting:
|
logger.debug('Login confirm ticket id: {}'.format(ticket_id))
|
||||||
return
|
if not ticket_id:
|
||||||
ticket = None
|
ticket = None
|
||||||
if self.request.session.get('auth_ticket_id'):
|
else:
|
||||||
ticket_id = self.request.session['auth_ticket_id']
|
|
||||||
ticket = get_object_or_none(LoginConfirmTicket, pk=ticket_id)
|
ticket = get_object_or_none(LoginConfirmTicket, pk=ticket_id)
|
||||||
|
return ticket
|
||||||
|
|
||||||
|
def get_ticket_or_create(self, confirm_setting):
|
||||||
|
ticket = self.get_ticket()
|
||||||
if not ticket:
|
if not ticket:
|
||||||
ticket = confirm_setting.create_confirm_ticket(self.request)
|
ticket = confirm_setting.create_confirm_ticket(self.request)
|
||||||
self.request.session['auth_ticket_id'] = str(ticket.id)
|
self.request.session['auth_ticket_id'] = str(ticket.id)
|
||||||
|
return ticket
|
||||||
|
|
||||||
if ticket.status == "accepted":
|
def check_user_login_confirm(self):
|
||||||
return
|
ticket = self.get_ticket()
|
||||||
elif ticket.status == "rejected":
|
if not ticket:
|
||||||
raise errors.LoginConfirmOtherError(ticket.id)
|
raise errors.LoginConfirmOtherError('', "Not found")
|
||||||
else:
|
if ticket.status == ticket.STATUS_OPEN:
|
||||||
raise errors.LoginConfirmWaitError(ticket.id)
|
raise errors.LoginConfirmWaitError(ticket.id)
|
||||||
|
elif ticket.action == ticket.ACTION_APPROVE:
|
||||||
|
self.request.session["auth_confirm"] = "1"
|
||||||
|
return
|
||||||
|
elif ticket.action == ticket.ACTION_REJECT:
|
||||||
|
raise errors.LoginConfirmOtherError(
|
||||||
|
ticket.id, ticket.get_action_display()
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise errors.LoginConfirmOtherError(
|
||||||
|
ticket.id, ticket.get_status_display()
|
||||||
|
)
|
||||||
|
|
||||||
|
def check_user_login_confirm_if_need(self, user):
|
||||||
|
if not settings.CONFIG.LOGIN_CONFIRM_ENABLE:
|
||||||
|
return
|
||||||
|
confirm_setting = user.get_login_confirm_setting()
|
||||||
|
if self.request.session.get('auth_confirm') or not confirm_setting:
|
||||||
|
return
|
||||||
|
self.get_ticket_or_create(confirm_setting)
|
||||||
|
self.check_user_login_confirm()
|
||||||
|
|
||||||
def clear_auth_mark(self):
|
def clear_auth_mark(self):
|
||||||
self.request.session['auth_password'] = ''
|
self.request.session['auth_password'] = ''
|
||||||
|
self.request.session['auth_user_id'] = ''
|
||||||
self.request.session['auth_mfa'] = ''
|
self.request.session['auth_mfa'] = ''
|
||||||
self.request.session['auth_confirm'] = ''
|
self.request.session['auth_confirm'] = ''
|
||||||
self.request.session['auth_ticket_id'] = ''
|
self.request.session['auth_ticket_id'] = ''
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-3">
|
<div class="col-lg-3">
|
||||||
<a class="btn btn-primary btn-sm block btn-copy" data-link="{{ order_detail_url }}">
|
<a class="btn btn-primary btn-sm block btn-copy" data-link="{{ ticket_detail_url }}">
|
||||||
<i class="fa fa-clipboard"></i> {% trans 'Copy link' %}
|
<i class="fa fa-clipboard"></i> {% trans 'Copy link' %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -132,7 +132,11 @@ $(document).ready(function () {
|
||||||
checkInterval = setInterval(doRequestAuth, 5000);
|
checkInterval = setInterval(doRequestAuth, 5000);
|
||||||
doRequestAuth();
|
doRequestAuth();
|
||||||
initClipboard();
|
initClipboard();
|
||||||
|
window.onbeforeunload = function (e) {
|
||||||
|
return "{% trans "Confirm" %}";
|
||||||
|
};
|
||||||
}).on('click', '.btn-refresh', function () {
|
}).on('click', '.btn-refresh', function () {
|
||||||
|
window.onbeforeunload = function() {};
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -19,9 +19,7 @@ from django.conf import settings
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
|
|
||||||
from common.utils import get_request_ip, get_object_or_none
|
from common.utils import get_request_ip, get_object_or_none
|
||||||
from users.models import User
|
|
||||||
from users.utils import (
|
from users.utils import (
|
||||||
get_user_or_tmp_user, increase_login_failed_count,
|
|
||||||
redirect_user_first_login_or_index
|
redirect_user_first_login_or_index
|
||||||
)
|
)
|
||||||
from ..signals import post_auth_success, post_auth_failed
|
from ..signals import post_auth_success, post_auth_failed
|
||||||
|
@ -117,42 +115,28 @@ class UserLoginGuardView(mixins.AuthMixin, RedirectView):
|
||||||
return url
|
return url
|
||||||
|
|
||||||
def get_redirect_url(self, *args, **kwargs):
|
def get_redirect_url(self, *args, **kwargs):
|
||||||
if not self.request.session.get('auth_password'):
|
try:
|
||||||
|
user = self.check_user_auth_if_need()
|
||||||
|
self.check_user_mfa_if_need(user)
|
||||||
|
self.check_user_login_confirm_if_need(user)
|
||||||
|
except errors.CredentialError:
|
||||||
return self.format_redirect_url(self.login_url)
|
return self.format_redirect_url(self.login_url)
|
||||||
user = self.get_user_from_session()
|
except errors.MFARequiredError:
|
||||||
# 启用并设置了otp
|
|
||||||
if user.otp_enabled and user.otp_secret_key and \
|
|
||||||
not self.request.session.get('auth_mfa'):
|
|
||||||
return self.format_redirect_url(self.login_otp_url)
|
return self.format_redirect_url(self.login_otp_url)
|
||||||
confirm_setting = user.get_login_confirm_setting()
|
except errors.LoginConfirmBaseError:
|
||||||
if confirm_setting and not self.request.session.get('auth_confirm'):
|
return self.format_redirect_url(self.login_confirm_url)
|
||||||
ticket = confirm_setting.create_confirm_ticket(self.request)
|
|
||||||
self.request.session['auth_ticket_id'] = str(ticket.id)
|
|
||||||
url = self.format_redirect_url(self.login_confirm_url)
|
|
||||||
return url
|
|
||||||
self.login_success(user)
|
|
||||||
self.clear_auth_mark()
|
|
||||||
# 启用但是没有设置otp
|
|
||||||
if user.otp_enabled and not user.otp_secret_key:
|
|
||||||
# 1,2,mfa_setting & F
|
|
||||||
return reverse('users:user-otp-enable-authentication')
|
|
||||||
url = redirect_user_first_login_or_index(
|
|
||||||
self.request, self.redirect_field_name
|
|
||||||
)
|
|
||||||
return url
|
|
||||||
|
|
||||||
def login_success(self, user):
|
|
||||||
auth_login(self.request, user)
|
|
||||||
self.send_auth_signal(success=True, user=user)
|
|
||||||
|
|
||||||
def send_auth_signal(self, success=True, user=None, username='', reason=''):
|
|
||||||
if success:
|
|
||||||
post_auth_success.send(sender=self.__class__, user=user, request=self.request)
|
|
||||||
else:
|
else:
|
||||||
post_auth_failed.send(
|
auth_login(self.request, user)
|
||||||
sender=self.__class__, username=username,
|
self.send_auth_signal(success=True, user=user)
|
||||||
request=self.request, reason=reason
|
self.clear_auth_mark()
|
||||||
|
# 启用但是没有设置otp
|
||||||
|
if user.otp_enabled and not user.otp_secret_key:
|
||||||
|
# 1,2,mfa_setting & F
|
||||||
|
return reverse('users:user-otp-enable-authentication')
|
||||||
|
url = redirect_user_first_login_or_index(
|
||||||
|
self.request, self.redirect_field_name
|
||||||
)
|
)
|
||||||
|
return url
|
||||||
|
|
||||||
|
|
||||||
class UserLoginWaitConfirmView(TemplateView):
|
class UserLoginWaitConfirmView(TemplateView):
|
||||||
|
|
Binary file not shown.
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Jumpserver 0.3.3\n"
|
"Project-Id-Version: Jumpserver 0.3.3\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2019-11-08 15:42+0800\n"
|
"POT-Creation-Date: 2019-11-08 17:27+0800\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
||||||
"Language-Team: Jumpserver team<ibuler@qq.com>\n"
|
"Language-Team: Jumpserver team<ibuler@qq.com>\n"
|
||||||
|
@ -354,6 +354,7 @@ msgstr "重置"
|
||||||
#: terminal/templates/terminal/command_list.html:47
|
#: terminal/templates/terminal/command_list.html:47
|
||||||
#: terminal/templates/terminal/session_list.html:52
|
#: terminal/templates/terminal/session_list.html:52
|
||||||
#: terminal/templates/terminal/terminal_update.html:46
|
#: terminal/templates/terminal/terminal_update.html:46
|
||||||
|
#: tickets/templates/tickets/login_confirm_ticket_list.html:32
|
||||||
#: users/templates/users/_user.html:52
|
#: users/templates/users/_user.html:52
|
||||||
#: users/templates/users/forgot_password.html:42
|
#: users/templates/users/forgot_password.html:42
|
||||||
#: users/templates/users/user_bulk_update.html:24
|
#: users/templates/users/user_bulk_update.html:24
|
||||||
|
@ -528,7 +529,7 @@ msgstr "创建远程应用"
|
||||||
#: terminal/templates/terminal/session_list.html:36
|
#: terminal/templates/terminal/session_list.html:36
|
||||||
#: terminal/templates/terminal/terminal_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:18
|
||||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:92
|
#: tickets/templates/tickets/login_confirm_ticket_list.html:105
|
||||||
#: users/templates/users/_granted_assets.html:34
|
#: users/templates/users/_granted_assets.html:34
|
||||||
#: users/templates/users/user_group_list.html:38
|
#: users/templates/users/user_group_list.html:38
|
||||||
#: users/templates/users/user_list.html:41
|
#: users/templates/users/user_list.html:41
|
||||||
|
@ -1480,7 +1481,7 @@ msgid "Asset user auth"
|
||||||
msgstr "资产用户信息"
|
msgstr "资产用户信息"
|
||||||
|
|
||||||
#: assets/templates/assets/_asset_user_auth_view_modal.html:54
|
#: assets/templates/assets/_asset_user_auth_view_modal.html:54
|
||||||
#: authentication/templates/authentication/login_wait_confirm.html:114
|
#: authentication/templates/authentication/login_wait_confirm.html:115
|
||||||
msgid "Copy success"
|
msgid "Copy success"
|
||||||
msgstr "复制成功"
|
msgstr "复制成功"
|
||||||
|
|
||||||
|
@ -1666,6 +1667,7 @@ msgstr "选择节点"
|
||||||
#: assets/templates/assets/system_user_detail.html:182
|
#: assets/templates/assets/system_user_detail.html:182
|
||||||
#: assets/templates/assets/system_user_list.html:135
|
#: assets/templates/assets/system_user_list.html:135
|
||||||
#: authentication/templates/authentication/_mfa_confirm_modal.html:20
|
#: authentication/templates/authentication/_mfa_confirm_modal.html:20
|
||||||
|
#: authentication/templates/authentication/login_wait_confirm.html:136
|
||||||
#: settings/templates/settings/terminal_setting.html:168
|
#: settings/templates/settings/terminal_setting.html:168
|
||||||
#: templates/_modal.html:23 terminal/templates/terminal/session_detail.html:112
|
#: templates/_modal.html:23 terminal/templates/terminal/session_detail.html:112
|
||||||
#: users/templates/users/user_detail.html:271
|
#: users/templates/users/user_detail.html:271
|
||||||
|
@ -2294,7 +2296,7 @@ msgstr "原因"
|
||||||
|
|
||||||
#: audits/models.py:88 audits/templates/audits/login_log_list.html:64
|
#: 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:16
|
||||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:88
|
#: tickets/templates/tickets/login_confirm_ticket_list.html:101
|
||||||
#: tickets/templates/tickets/ticket_detail.html:34
|
#: tickets/templates/tickets/ticket_detail.html:34
|
||||||
#: xpack/plugins/cloud/models.py:275 xpack/plugins/cloud/models.py:310
|
#: 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_history.html:70
|
||||||
|
@ -2390,10 +2392,6 @@ msgstr "登录日志"
|
||||||
msgid "Command execution log"
|
msgid "Command execution log"
|
||||||
msgstr "命令执行"
|
msgstr "命令执行"
|
||||||
|
|
||||||
#: authentication/api/login_confirm.py:52
|
|
||||||
msgid "not found"
|
|
||||||
msgstr "没有发现"
|
|
||||||
|
|
||||||
#: authentication/backends/api.py:53
|
#: authentication/backends/api.py:53
|
||||||
msgid "Invalid signature header. No credentials provided."
|
msgid "Invalid signature header. No credentials provided."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -2703,11 +2701,11 @@ msgstr "返回"
|
||||||
msgid "Welcome back, please enter username and password to login"
|
msgid "Welcome back, please enter username and password to login"
|
||||||
msgstr "欢迎回来,请输入用户名和密码登录"
|
msgstr "欢迎回来,请输入用户名和密码登录"
|
||||||
|
|
||||||
#: authentication/views/login.py:73
|
#: authentication/views/login.py:71
|
||||||
msgid "Please enable cookies and try again."
|
msgid "Please enable cookies and try again."
|
||||||
msgstr "设置你的浏览器支持cookie"
|
msgstr "设置你的浏览器支持cookie"
|
||||||
|
|
||||||
#: authentication/views/login.py:172
|
#: authentication/views/login.py:170
|
||||||
msgid ""
|
msgid ""
|
||||||
"Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n"
|
"Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n"
|
||||||
" Don't close this page"
|
" Don't close this page"
|
||||||
|
@ -2715,15 +2713,15 @@ msgstr ""
|
||||||
"等待 <b>{}</b> 确认, 你也可以复制链接发给他/她 <br/>\n"
|
"等待 <b>{}</b> 确认, 你也可以复制链接发给他/她 <br/>\n"
|
||||||
" 不要关闭本页面"
|
" 不要关闭本页面"
|
||||||
|
|
||||||
#: authentication/views/login.py:177
|
#: authentication/views/login.py:175
|
||||||
msgid "No ticket found"
|
msgid "No ticket found"
|
||||||
msgstr "没有发现工单"
|
msgstr "没有发现工单"
|
||||||
|
|
||||||
#: authentication/views/login.py:200
|
#: authentication/views/login.py:198
|
||||||
msgid "Logout success"
|
msgid "Logout success"
|
||||||
msgstr "退出登录成功"
|
msgstr "退出登录成功"
|
||||||
|
|
||||||
#: authentication/views/login.py:201
|
#: authentication/views/login.py:199
|
||||||
msgid "Logout success, return login page"
|
msgid "Logout success, return login page"
|
||||||
msgstr "退出登录成功,返回到登录页面"
|
msgstr "退出登录成功,返回到登录页面"
|
||||||
|
|
||||||
|
@ -4571,8 +4569,8 @@ msgstr "接受"
|
||||||
#: terminal/templates/terminal/terminal_list.html:80
|
#: terminal/templates/terminal/terminal_list.html:80
|
||||||
#: tickets/models/login_confirm.py:16
|
#: tickets/models/login_confirm.py:16
|
||||||
#: tickets/templates/tickets/login_confirm_ticket_detail.html:10
|
#: 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:70
|
||||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:94
|
#: tickets/templates/tickets/login_confirm_ticket_list.html:107
|
||||||
msgid "Reject"
|
msgid "Reject"
|
||||||
msgstr "拒绝"
|
msgstr "拒绝"
|
||||||
|
|
||||||
|
@ -4610,12 +4608,12 @@ msgid ""
|
||||||
msgstr "你可以使用ssh客户端工具连接终端"
|
msgstr "你可以使用ssh客户端工具连接终端"
|
||||||
|
|
||||||
#: tickets/models/base.py:16 tickets/models/base.py:52
|
#: tickets/models/base.py:16 tickets/models/base.py:52
|
||||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:89
|
#: tickets/templates/tickets/login_confirm_ticket_list.html:102
|
||||||
msgid "Open"
|
msgid "Open"
|
||||||
msgstr ""
|
msgstr "开启"
|
||||||
|
|
||||||
#: tickets/models/base.py:17
|
#: tickets/models/base.py:17
|
||||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:90
|
#: tickets/templates/tickets/login_confirm_ticket_list.html:103
|
||||||
msgid "Closed"
|
msgid "Closed"
|
||||||
msgstr "关闭"
|
msgstr "关闭"
|
||||||
|
|
||||||
|
@ -4629,7 +4627,7 @@ msgstr "用户显示名称"
|
||||||
|
|
||||||
#: tickets/models/base.py:28
|
#: tickets/models/base.py:28
|
||||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:14
|
#: tickets/templates/tickets/login_confirm_ticket_list.html:14
|
||||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:87
|
#: tickets/templates/tickets/login_confirm_ticket_list.html:100
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "标题"
|
msgstr "标题"
|
||||||
|
|
||||||
|
@ -4659,8 +4657,8 @@ msgstr "{} {} 这个工单"
|
||||||
|
|
||||||
#: tickets/models/login_confirm.py:15
|
#: tickets/models/login_confirm.py:15
|
||||||
#: tickets/templates/tickets/login_confirm_ticket_detail.html:9
|
#: 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:69
|
||||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:93
|
#: tickets/templates/tickets/login_confirm_ticket_list.html:106
|
||||||
msgid "Approve"
|
msgid "Approve"
|
||||||
msgstr "同意"
|
msgstr "同意"
|
||||||
|
|
||||||
|
@ -4668,6 +4666,14 @@ msgstr "同意"
|
||||||
msgid "this order"
|
msgid "this order"
|
||||||
msgstr "这个工单"
|
msgstr "这个工单"
|
||||||
|
|
||||||
|
#: tickets/templates/tickets/login_confirm_ticket_list.html:27
|
||||||
|
msgid "Approve selected"
|
||||||
|
msgstr "同意所选"
|
||||||
|
|
||||||
|
#: tickets/templates/tickets/login_confirm_ticket_list.html:28
|
||||||
|
msgid "Reject selected"
|
||||||
|
msgstr "拒绝所选"
|
||||||
|
|
||||||
#: tickets/templates/tickets/ticket_detail.html:66
|
#: tickets/templates/tickets/ticket_detail.html:66
|
||||||
#: tickets/templates/tickets/ticket_detail.html:81
|
#: tickets/templates/tickets/ticket_detail.html:81
|
||||||
msgid "ago"
|
msgid "ago"
|
||||||
|
@ -6431,6 +6437,12 @@ msgstr "密码匣子"
|
||||||
msgid "vault create"
|
msgid "vault create"
|
||||||
msgstr "创建"
|
msgstr "创建"
|
||||||
|
|
||||||
|
#~ msgid "selected"
|
||||||
|
#~ msgstr "所选"
|
||||||
|
|
||||||
|
#~ msgid "not found"
|
||||||
|
#~ msgstr "没有发现"
|
||||||
|
|
||||||
#~ msgid "Log in frequently and try again later"
|
#~ msgid "Log in frequently and try again later"
|
||||||
#~ msgstr "登录频繁, 稍后重试"
|
#~ msgstr "登录频繁, 稍后重试"
|
||||||
|
|
||||||
|
@ -6446,9 +6458,6 @@ msgstr "创建"
|
||||||
#~ msgid "Accepted"
|
#~ msgid "Accepted"
|
||||||
#~ msgstr "已接受"
|
#~ msgstr "已接受"
|
||||||
|
|
||||||
#~ msgid "Rejected"
|
|
||||||
#~ msgstr "已拒绝"
|
|
||||||
|
|
||||||
#~ msgid "New order"
|
#~ msgid "New order"
|
||||||
#~ msgstr "新工单"
|
#~ msgstr "新工单"
|
||||||
|
|
||||||
|
|
|
@ -1319,5 +1319,5 @@ function initDateRangePicker(selector, options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function reloadPage() {
|
function reloadPage() {
|
||||||
window.location.reload();
|
setTimeout( function () {window.location.reload();}, 300);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
from rest_framework import viewsets, generics
|
from rest_framework_bulk import BulkModelViewSet
|
||||||
from rest_framework.serializers import ValidationError
|
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
|
|
||||||
from common.permissions import IsValidUser
|
from common.permissions import IsValidUser
|
||||||
from common.mixins import CommonApiMixin
|
from common.mixins import CommonApiMixin
|
||||||
|
@ -10,21 +8,9 @@ from .. import serializers, mixins
|
||||||
from ..models import LoginConfirmTicket
|
from ..models import LoginConfirmTicket
|
||||||
|
|
||||||
|
|
||||||
class LoginConfirmTicketViewSet(CommonApiMixin, mixins.TicketMixin, viewsets.ModelViewSet):
|
class LoginConfirmTicketViewSet(CommonApiMixin, mixins.TicketMixin, BulkModelViewSet):
|
||||||
serializer_class = serializers.LoginConfirmTicketSerializer
|
serializer_class = serializers.LoginConfirmTicketSerializer
|
||||||
permission_classes = (IsValidUser,)
|
permission_classes = (IsValidUser,)
|
||||||
queryset = LoginConfirmTicket.objects.all()
|
queryset = LoginConfirmTicket.objects.all()
|
||||||
filter_fields = ['status', 'title', 'action', 'ip']
|
filter_fields = ['status', 'title', 'action', 'ip']
|
||||||
search_fields = ['user_display', 'title', 'ip', 'city']
|
search_fields = ['user_display', 'title', 'ip', 'city']
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
#
|
#
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from common.serializers import AdaptedBulkListSerializer
|
||||||
|
from common.mixins.serializers import BulkSerializerMixin
|
||||||
from .base import TicketSerializer
|
from .base import TicketSerializer
|
||||||
from ..models import LoginConfirmTicket
|
from ..models import LoginConfirmTicket
|
||||||
|
|
||||||
|
@ -9,8 +11,9 @@ from ..models import LoginConfirmTicket
|
||||||
__all__ = ['LoginConfirmTicketSerializer', 'LoginConfirmTicketActionSerializer']
|
__all__ = ['LoginConfirmTicketSerializer', 'LoginConfirmTicketActionSerializer']
|
||||||
|
|
||||||
|
|
||||||
class LoginConfirmTicketSerializer(serializers.ModelSerializer):
|
class LoginConfirmTicketSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
list_serializer_class = AdaptedBulkListSerializer
|
||||||
model = LoginConfirmTicket
|
model = LoginConfirmTicket
|
||||||
fields = TicketSerializer.Meta.fields + [
|
fields = TicketSerializer.Meta.fields + [
|
||||||
'ip', 'city', 'action'
|
'ip', 'city', 'action'
|
||||||
|
@ -24,11 +27,14 @@ class LoginConfirmTicketSerializer(serializers.ModelSerializer):
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
action = validated_data.get("action")
|
action = validated_data.get("action")
|
||||||
user = self.context["request"].user
|
user = self.context["request"].user
|
||||||
|
|
||||||
if action and user not in instance.assignees.all():
|
if action and user not in instance.assignees.all():
|
||||||
error = {"action": "Only assignees can update"}
|
error = {"action": "Only assignees can update"}
|
||||||
raise serializers.ValidationError(error)
|
raise serializers.ValidationError(error)
|
||||||
|
if instance.status == instance.STATUS_CLOSED:
|
||||||
|
validated_data.pop('action')
|
||||||
instance = super().update(instance, validated_data)
|
instance = super().update(instance, validated_data)
|
||||||
if action:
|
if not instance.status == instance.STATUS_CLOSED:
|
||||||
instance.perform_action(action, user)
|
instance.perform_action(action, user)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,19 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<div id="actions" class="hide">
|
||||||
|
<div class="input-group">
|
||||||
|
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
|
||||||
|
<option value="approve">{% trans 'Approve selected' %}</option>
|
||||||
|
<option value="reject">{% trans 'Reject selected' %}</option>
|
||||||
|
</select>
|
||||||
|
<div class="input-group-btn pull-left" style="padding-left: 5px;">
|
||||||
|
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
|
||||||
|
{% trans 'Submit' %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% include '_filter_dropdown.html' %}
|
{% include '_filter_dropdown.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content_bottom_left %}{% endblock %}
|
{% block content_bottom_left %}{% endblock %}
|
||||||
|
@ -38,9 +51,9 @@ function initTable() {
|
||||||
$(td).html(detailBtn.replace("{{ DEFAULT_PK }}", rowData.id));
|
$(td).html(detailBtn.replace("{{ DEFAULT_PK }}", rowData.id));
|
||||||
}},
|
}},
|
||||||
{targets: 3, createdCell: function (td, cellData, rowData) {
|
{targets: 3, createdCell: function (td, cellData, rowData) {
|
||||||
if (cellData === "approval") {
|
if (cellData === "approve") {
|
||||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||||
} else if (cellData === "rejected") {
|
} else if (cellData === "reject") {
|
||||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||||
} else if (cellData === "open") {
|
} else if (cellData === "open") {
|
||||||
$(td).html('<i class="fa fa-spinner text-info"></i>')
|
$(td).html('<i class="fa fa-spinner text-info"></i>')
|
||||||
|
@ -70,9 +83,9 @@ function initTable() {
|
||||||
columns: [
|
columns: [
|
||||||
{data: "id"}, {data: "title"},
|
{data: "id"}, {data: "title"},
|
||||||
{data: "user_display"},
|
{data: "user_display"},
|
||||||
{data: "status", ticketable: false},
|
{data: "action", width: "40px"},
|
||||||
{data: "date_created", width: "120px"},
|
{data: "date_created", width: "120px"},
|
||||||
{data: "id", ticketable: false}
|
{data: "id", orderable: false}
|
||||||
],
|
],
|
||||||
op_html: $('#actions').html()
|
op_html: $('#actions').html()
|
||||||
};
|
};
|
||||||
|
@ -85,6 +98,7 @@ $(document).ready(function(){
|
||||||
var menu = [
|
var menu = [
|
||||||
{title: "IP", value: "ip"},
|
{title: "IP", value: "ip"},
|
||||||
{title: "{% trans 'Title' %}", value: "title"},
|
{title: "{% trans 'Title' %}", value: "title"},
|
||||||
|
{title: "{% trans 'User' %}", value: "user_display"},
|
||||||
{title: "{% trans 'Status' %}", value: "status", submenu: [
|
{title: "{% trans 'Status' %}", value: "status", submenu: [
|
||||||
{title: "{% trans 'Open' %}", value: "open"},
|
{title: "{% trans 'Open' %}", value: "open"},
|
||||||
{title: "{% trans 'Closed' %}", value: "closed"},
|
{title: "{% trans 'Closed' %}", value: "closed"},
|
||||||
|
@ -107,6 +121,33 @@ $(document).ready(function(){
|
||||||
success: reloadPage
|
success: reloadPage
|
||||||
};
|
};
|
||||||
requestApi(data);
|
requestApi(data);
|
||||||
|
}).on('click', '#btn_bulk_update', function () {
|
||||||
|
var action = $('#slct_bulk_update').val();
|
||||||
|
var idList = ticketTable.selected;
|
||||||
|
if (idList.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var theUrl = "{% url 'api-tickets:login-confirm-ticket-list' %}";
|
||||||
|
|
||||||
|
function doAction(action) {
|
||||||
|
var data = [];
|
||||||
|
$.each(idList, function(index, object_id) {
|
||||||
|
var obj = {
|
||||||
|
"pk": object_id, "action": action
|
||||||
|
};
|
||||||
|
data.push(obj);
|
||||||
|
});
|
||||||
|
requestApi({
|
||||||
|
url: theUrl,
|
||||||
|
method: 'PATCH',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
success: function (){
|
||||||
|
$(".ipt_check_all").prop("checked", false)
|
||||||
|
ticketTable.ajax.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
doAction(action)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
from django.urls import path
|
from rest_framework_bulk.routes import BulkRouter
|
||||||
from rest_framework.routers import DefaultRouter
|
|
||||||
|
|
||||||
from .. import api
|
from .. import api
|
||||||
|
|
||||||
app_name = 'tickets'
|
app_name = 'tickets'
|
||||||
router = DefaultRouter()
|
router = BulkRouter()
|
||||||
|
|
||||||
router.register('tickets', api.TicketViewSet, 'ticket')
|
router.register('tickets', api.TicketViewSet, 'ticket')
|
||||||
router.register('tickets/(?P<ticket_id>[0-9a-zA-Z\-]{36})/comments', api.TicketCommentViewSet, 'ticket-comment')
|
router.register('tickets/(?P<ticket_id>[0-9a-zA-Z\-]{36})/comments', api.TicketCommentViewSet, 'ticket-comment')
|
||||||
|
|
Loading…
Reference in New Issue