perf: mfa interface optimization, mobile phone number can be empty

pull/13316/head^2
feng 2024-05-28 16:09:22 +08:00 committed by Bryan
parent f95cbd6977
commit 91a1da57e9
9 changed files with 215 additions and 104 deletions

View File

@ -44,13 +44,13 @@ class MFASms(BaseMFA):
return settings.SMS_ENABLED return settings.SMS_ENABLED
def get_enable_url(self) -> str: def get_enable_url(self) -> str:
return '/ui/#/profile/setting?activeTab=ProfileUpdate' return '/ui/#/profile/index'
def can_disable(self) -> bool: def can_disable(self) -> bool:
return True return True
def disable(self): def disable(self):
return '/ui/#/profile/setting?activeTab=ProfileUpdate' return '/ui/#/profile/index'
@staticmethod @staticmethod
def help_text_of_enable(): def help_text_of_enable():
@ -61,4 +61,4 @@ class MFASms(BaseMFA):
return _("Clear phone number to disable") return _("Clear phone number to disable")
def get_disable_url(self) -> str: def get_disable_url(self) -> str:
return '/ui/#/profile/setting?activeTab=ProfileUpdate' return '/ui/#/profile/index'

View File

@ -26,10 +26,12 @@ COUNTRY_CALLING_CODES = [
{'name': 'HongKong(中国香港)', 'value': '+852'}, {'name': 'HongKong(中国香港)', 'value': '+852'},
{'name': 'Macao(中国澳门)', 'value': '+853'}, {'name': 'Macao(中国澳门)', 'value': '+853'},
{'name': 'Taiwan(中国台湾)', 'value': '+886'}, {'name': 'Taiwan(中国台湾)', 'value': '+886'},
{'name': 'America(America)', 'value': '+1'}, {'name': 'Russia(Россия)', 'value': '+7'}, {'name': 'America(America)', 'value': '+1'},
{'name': 'Russia(Россия)', 'value': '+7'},
{'name': 'France(français)', 'value': '+33'}, {'name': 'France(français)', 'value': '+33'},
{'name': 'Britain(Britain)', 'value': '+44'}, {'name': 'Britain(Britain)', 'value': '+44'},
{'name': 'Germany(Deutschland)', 'value': '+49'}, {'name': 'Germany(Deutschland)', 'value': '+49'},
{'name': 'Japan(日本)', 'value': '+81'}, {'name': 'Korea(한국)', 'value': '+82'}, {'name': 'Japan(日本)', 'value': '+81'},
{'name': 'Korea(한국)', 'value': '+82'},
{'name': 'India(भारत)', 'value': '+91'} {'name': 'India(भारत)', 'value': '+91'}
] ]

View File

@ -251,21 +251,21 @@ class PhoneField(serializers.CharField):
data = '+{}{}'.format(code, phone) data = '+{}{}'.format(code, phone)
else: else:
data = phone data = phone
try: if data:
phone = phonenumbers.parse(data, 'CN') try:
data = '+{}{}'.format(phone.country_code, phone.national_number) phone = phonenumbers.parse(data, 'CN')
except phonenumbers.NumberParseException: data = '+{}{}'.format(phone.country_code, phone.national_number)
data = '+86{}'.format(data) except phonenumbers.NumberParseException:
data = '+86{}'.format(data)
return super().to_internal_value(data) return super().to_internal_value(data)
def to_representation(self, value): def to_representation(self, value):
if value: try:
try: phone = phonenumbers.parse(value, 'CN')
phone = phonenumbers.parse(value, 'CN') value = {'code': '+%s' % phone.country_code, 'phone': phone.national_number}
value = {'code': '+%s' % phone.country_code, 'phone': phone.national_number} except phonenumbers.NumberParseException:
except phonenumbers.NumberParseException: value = {'code': '+86', 'phone': value}
value = {'code': '+86', 'phone': value}
return value return value

View File

@ -47,6 +47,9 @@ class PhoneValidator:
message = _('The mobile phone number format is incorrect') message = _('The mobile phone number format is incorrect')
def __call__(self, value): def __call__(self, value):
if not value:
return
try: try:
phone = phonenumbers.parse(value, 'CN') phone = phonenumbers.parse(value, 'CN')
valid = phonenumbers.is_valid_number(phone) valid = phonenumbers.is_valid_number(phone)

View File

@ -23,8 +23,8 @@ button{
/*header样式*/ /*header样式*/
header{ header{
overflow:hidden ; overflow:hidden ;
background: #dedede; background: #ffffff;
padding:15px 200px; padding:15px 10%;
} }
header .logo a{ header .logo a{
float:left; float:left;

View File

@ -25,7 +25,7 @@
<button class="btn btn-primary full-width btn-challenge" <button class="btn btn-primary full-width btn-challenge"
type='button' onclick="sendChallengeCode(this)" type='button' onclick="sendChallengeCode(this)"
> >
{% trans 'Send verification code' %} {% trans 'Send' %}
</button> </button>
{% endif %} {% endif %}
</div> </div>
@ -39,15 +39,14 @@
} }
.challenge-required .input-style { .challenge-required .input-style {
width: calc(100% - 160px); width: calc(100% - 104px);
display: inline-block; display: inline-block;
} }
.btn-challenge { .btn-challenge {
width: 156px !important; width: 100px !important;
height: 100%; height: 100%;
vertical-align: top; vertical-align: top;
padding: 8px 12px;
} }
</style> </style>
<script> <script>

View File

@ -20,7 +20,7 @@
<a href="{% url 'index' %}"> <a href="{% url 'index' %}">
<img src="{{ INTERFACE.logo_logout }}" alt="" width="50px" height="50px"/> <img src="{{ INTERFACE.logo_logout }}" alt="" width="50px" height="50px"/>
</a> </a>
<span style="font-size: 18px; line-height: 50px">{{ INTERFACE.login_title }}</span> <span style="font-size: 21px; line-height: 50px">{{ INTERFACE.login_title }}</span>
</div> </div>
<div> <div>
<a href="{% url 'index' %}">{% trans 'Home page' %}</a> <a href="{% url 'index' %}">{% trans 'Home page' %}</a>

View File

@ -143,7 +143,7 @@ class PasswordExpirationReminderMsg(UserMessage):
subject = _('Password is about expire') subject = _('Password is about expire')
date_password_expired_local = timezone.localtime(user.date_password_expired) date_password_expired_local = timezone.localtime(user.date_password_expired)
update_password_url = urljoin(settings.SITE_URL, '/ui/#/profile/setting/?activeTab=PasswordUpdate') update_password_url = urljoin(settings.SITE_URL, '/ui/#/profile/index')
date_password_expired = date_password_expired_local.strftime('%Y-%m-%d %H:%M:%S') date_password_expired = date_password_expired_local.strftime('%Y-%m-%d %H:%M:%S')
context = { context = {
'name': user.name, 'name': user.name,

View File

@ -4,114 +4,221 @@
{% block body %} {% block body %}
<style> <style>
.help-inline { .help-inline {
color: #7d8293; color: #7d8293;
font-size: 12px; font-size: 12px;
padding-right: 10px; padding-right: 10px;
} }
.btn-xs { .btn-xs {
width: 54px; width: 54px;
} }
.onoffswitch-switch { .onoffswitch-switch {
height: 20px; height: 20px;
} }
.container {
max-width: 72%;
margin: 20px auto;
background-color: #ffffff;
padding: 15px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
}
.header h2 {
margin-top: 0;
margin-bottom: -5px;
}
.section {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 0;
border-bottom: 1px dashed #ddd;
}
.section:last-child {
border-bottom: none;
}
.section .title {
flex: 2;
font-size: 16px;
font-weight: bold;
}
.section .description {
flex: 10;
color: #666;
}
.section .summarize {
flex: 12;
color: #666;
}
.section .action {
flex: 3;
display: flex;
justify-content: flex-end;
align-items: center;
}
.section .status {
display: flex;
align-items: center;
}
.section .status .status-text {
margin-left: 5px;
font-weight: bold;
}
.section .action-buttons {
display: flex;
align-items: center;
}
.section .action-buttons .divider {
margin: 0 10px;
color: #ddd;
}
.section .action-buttons a {
color: #1e88e5;
text-decoration: none;
cursor: pointer;
}
.status-warning {
color: #ff9800;
}
.status-set {
color: #4caf50;
}
.status-icon {
font-size: 18px;
}
</style> </style>
<article> <div class="container">
<div> <div class="header">
{# // Todoi:#} <h2>MFA</h2>
<h3>{% trans 'Enable MFA' %}</h3> </div>
<div class="row" style="padding-top: 10px"> <hr>
<li class="col-sm-6" style="font-size: 14px">{% trans 'Enable' %} MFA</li> <div class="row" style="padding-top: 10px">
<div class="switch col-sm-6"> <li class="col-sm-6" style="font-size: 14px">{% trans 'Enable' %} MFA</li>
<div class="switch col-sm-6">
<span class="help-inline"> <span class="help-inline">
{% if user.mfa_force_enabled %} {% if user.mfa_force_enabled %}
{% trans 'MFA force enable, cannot disable' %} {% trans 'MFA force enable, cannot disable' %}
{% endif %} {% endif %}
</span> </span>
<div class="onoffswitch" style="float: right"> <div class="onoffswitch" style="float: right">
<input type="checkbox" class="onoffswitch-checkbox" <input type="checkbox" class="onoffswitch-checkbox"
id="mfa-switch" onchange="switchMFA()" id="mfa-switch" onchange="switchMFA()"
{% if user.mfa_force_enabled %} disabled {% endif %} {% if user.mfa_force_enabled %} disabled {% endif %}
{% if user.mfa_enabled %} checked {% endif %} {% if user.mfa_enabled %} checked {% endif %}
> >
<label class="onoffswitch-label" for="mfa-switch"> <label class="onoffswitch-label" for="mfa-switch">
<span class="onoffswitch-inner"></span> <span class="onoffswitch-inner"></span>
<span class="onoffswitch-switch"></span> <span class="onoffswitch-switch"></span>
</label> </label>
</div>
</div> </div>
</div> </div>
</div> </div>
<div id="mfa-setting" style="display: none; padding-top: 30px"> </div>
<h3>{% trans 'MFA setting' %}</h3> <div id="mfa-setting" class="container">
<div style="height: 100%; width: 100%;"> <div class="header">
{% for b in mfa_backends %} <h2>{% trans 'MFA setting' %}</h2>
<div class="row" style="padding-top: 10px"> </div>
<li class="col-sm-6" style="font-size: 14px"> <hr>
{{ b.display_name }} {% for b in mfa_backends %}
</li> <div class="section">
<span class="col-sm-6"> <div class="title">{{ b.display_name }}</div>
{% if b.is_active %}
<div class="description">{{ b.help_text_of_disable }}</div>
{% else %}
<div class="description">{{ b.help_text_of_enable }}</div>
{% endif %}
<div class="action">
<div class="status {% if b.is_active %}status-set{% else %}status-warning{% endif %}">
<span class="status-icon">{% if b.is_active %}{% else %}{% endif %}</span>
<span class="status-text">
{% if b.is_active %}{% trans 'Enable' %}
{% else %}
{% trans 'Not enabled' %}
{% endif %}
</span>
</div>
<div class="action-buttons">
<span class="divider">|</span>
{% if b.is_active %} {% if b.is_active %}
<button class="btn btn-warning btn-xs" style="float: right" <button class="btn btn-warning btn-xs"
{% if not b.can_disable %} disabled {% endif %} {% if not b.can_disable %} disabled {% endif %}
onclick="goTo('{{ b.get_disable_url }}')" onclick="goTo('{{ b.get_disable_url }}')"
> >
{% trans 'Reset' %} {% trans 'Reset' %}
</button> </button>
<span class="help-inline">{{ b.help_text_of_disable }}</span>
{% else %} {% else %}
<button class="btn btn-primary btn-xs" style="float: right" <button class="btn btn-primary btn-xs"
onclick="goTo('{{ b.get_enable_url }}')" onclick="goTo('{{ b.get_enable_url }}')"
> >
{% trans 'Enable' %} {% trans 'Enable' %}
</button> </button>
<span class="help-inline">{{ b.help_text_of_enable }}</span>
{% endif %} {% endif %}
</span>
</div> </div>
{% endfor %} </div>
</div> </div>
</div> {% endfor %}
</article> </div>
<script src="{% static 'js/jumpserver.js' %}"></script> <script src="{% static 'js/jumpserver.js' %}"></script>
<script> <script>
function goTo(url) { function goTo(url) {
window.open(url, '_self') window.open(url, '_self')
}
function switchMFA() {
const switchRef = $('#mfa-switch')
const enabled = switchRef.is(":checked")
requestApi({
url: '/api/v1/users/profile/',
data: {
mfa_level: enabled ? 1 : 0
},
method: 'PATCH',
success() {
showSettingOrNot()
},
error() {
switchRef.prop('checked', !enabled)
}
})
showSettingOrNot()
}
function showSettingOrNot() {
const enabled = $('#mfa-switch').is(":checked")
const settingRef = $('#mfa-setting')
if (enabled) {
settingRef.show()
} else {
settingRef.hide()
} }
}
window.onload = function () { function switchMFA() {
showSettingOrNot() const switchRef = $('#mfa-switch')
} const enabled = switchRef.is(":checked")
requestApi({
url: '/api/v1/users/profile/',
data: {
mfa_level: enabled ? 1 : 0
},
method: 'PATCH',
success() {
showSettingOrNot()
},
error() {
switchRef.prop('checked', !enabled)
}
})
showSettingOrNot()
}
function showSettingOrNot() {
const enabled = $('#mfa-switch').is(":checked")
const settingRef = $('#mfa-setting')
if (enabled) {
settingRef.show()
} else {
settingRef.hide()
}
}
window.onload = function () {
showSettingOrNot()
}
</script> </script>
{% endblock %} {% endblock %}