Merge branch 'master' of code.simcu.com:jumpserver/jumpserver

pull/530/head
unknown 2016-09-14 15:40:47 +08:00
commit 0f9bdab108
8 changed files with 167 additions and 47 deletions

View File

@ -4,10 +4,10 @@
var checked=false; var checked=false;
function check_all(form) { function check_all(form) {
var checkboxes = document.getElementById(form); var checkboxes = document.getElementById(form);
if (checked == false) { if (checked === false) {
checked = true checked = true;
} else { } else {
checked = false checked = false;
} }
for (var i = 0; i < checkboxes.elements.length; i++) { for (var i = 0; i < checkboxes.elements.length; i++) {
if (checkboxes.elements[i].type == "checkbox") { if (checkboxes.elements[i].type == "checkbox") {
@ -51,13 +51,13 @@ function GetRowData(row){
//此函数用于在多选提交时至少要选择一行 //此函数用于在多选提交时至少要选择一行
function GetTableDataBox() { function GetTableDataBox() {
var tabProduct = document.getElementById("editable"); var tabProduct = document.getElementById("editable");
var tableData = new Array(); var tableData = [];
var returnData = new Array(); var returnData = [];
var checkboxes = document.getElementById("contents_form"); var checkboxes = document.getElementById("contents_form");
var id_list = new Array(); var id_list = [];
len = checkboxes.elements.length; len = checkboxes.elements.length;
for (var i=0; i < len; i++) { for (var i=0; i < len; i++) {
if (checkboxes.elements[i].type == "checkbox" && checkboxes.elements[i].checked == true && checkboxes.elements[i].value != "checkall") { if (checkboxes.elements[i].type == "checkbox" && checkboxes.elements[i].checked === true && checkboxes.elements[i].value != "checkall") {
id_list.push(i); id_list.push(i);
} }
} }
@ -67,7 +67,7 @@ function GetTableDataBox() {
tableData.push(GetRowData(tabProduct.rows[id_list[i]])); tableData.push(GetRowData(tabProduct.rows[id_list[i]]));
} }
if (id_list.length == 0){ if (id_list.length === 0){
alert('请至少选择一行!'); alert('请至少选择一行!');
} }
returnData.push(tableData); returnData.push(tableData);
@ -77,7 +77,7 @@ function GetTableDataBox() {
function move(from, to, from_o, to_o) { function move(from, to, from_o, to_o) {
$("#" + from + " option").each(function () { $("#" + from + " option").each(function () {
if ($(this).prop("selected") == true) { if ($(this).prop("selected") === true) {
$("#" + to).append(this); $("#" + to).append(this);
if( typeof from_o !== 'undefined'){ if( typeof from_o !== 'undefined'){
$("#"+to_o).append($("#"+from_o +" option[value='"+this.value+"']")); $("#"+to_o).append($("#"+from_o +" option[value='"+this.value+"']"));
@ -88,7 +88,7 @@ function move(from, to, from_o, to_o) {
function move_left(from, to, from_o, to_o) { function move_left(from, to, from_o, to_o) {
$("#" + from + " option").each(function () { $("#" + from + " option").each(function () {
if ($(this).prop("selected") == true) { if ($(this).prop("selected") === true) {
$("#" + to).append(this); $("#" + to).append(this);
if( typeof from_o !== 'undefined'){ if( typeof from_o !== 'undefined'){
$("#"+to_o).append($("#"+from_o +" option[value='"+this.value+"']")); $("#"+to_o).append($("#"+from_o +" option[value='"+this.value+"']"));
@ -126,8 +126,8 @@ function move_left(from, to, from_o, to_o) {
function selectAll(){ function selectAll(){
// 选择该页面所有option // 选择该页面所有option
$('option').each(function(){ $('option').each(function(){
$(this).attr('selected', true) $(this).attr('selected', true);
}) });
} }
@ -156,6 +156,8 @@ function getIDall() {
function APIUpdateAttr(props) { function APIUpdateAttr(props) {
// props = {url: .., body: , success: , error: , method: ,} // props = {url: .., body: , success: , error: , method: ,}
props = props || {}; props = props || {};
success_message = props.success_message || 'Update Successfully!';
fail_message = props.fail_message || 'Error occurred while updating.';
$.ajax({ $.ajax({
url: props.url, url: props.url,
type: props.method || "PATCH", type: props.method || "PATCH",
@ -164,18 +166,18 @@ function APIUpdateAttr(props) {
dataType: props.data_type || "json", dataType: props.data_type || "json",
}).done(function(data, textStatue, jqXHR) { }).done(function(data, textStatue, jqXHR) {
if (typeof props.success === 'function') { if (typeof props.success === 'function') {
return props.success(data) return props.success(data);
} else { } else {
toastr.success('Update Success!') toastr.success(success_message);
} }
}).fail(function(jqXHR, textStatue, errorThrown) { }).fail(function(jqXHR, textStatue, errorThrown) {
if (typeof props.error === 'function') { if (typeof props.error === 'function') {
return props.error(errorThrown) return props.error(errorThrown);
} else { } else {
toastr.error('Error occurred while updating.') toastr.error(fail_message);
} }
}) });
return true; return true;
} }
var jumpserver = new Object(); var jumpserver = {};

View File

@ -0,0 +1,20 @@
{% load i18n %}
<div aria-hidden="true" role="dialog" tabindex="-1" id="{% block modal_id %}{% endblock %}" class="modal inmodal" style="display: none;">
<div class="modal-dialog">
<div class="modal-content animated fadeIn">
<div class="modal-header">
<button data-dismiss="modal" class="close" type="button"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
<h4 class="modal-title">{% block modal_title %}{% endblock %}</h4>
<small>{% block modal_comment %}{% endblock %}</small>
</div>
<div class="modal-body">
{% block modal_body %}
{% endblock %}
</div>
<div class="modal-footer">
<button data-dismiss="modal" class="btn btn-white" type="button">{% trans "Close" %}</button>
<button class="btn btn-primary" type="button" id="{% block modal_confirm_id %}{% endblock %}">{% trans 'Confirm' %}</button>
</div>
</div>
</div>
</div>

View File

@ -6,6 +6,7 @@ import logging
from rest_framework import generics from rest_framework import generics
from .serializers import UserSerializer, UserGroupSerializer, UserAttributeSerializer, UserGroupEditSerializer from .serializers import UserSerializer, UserGroupSerializer, UserAttributeSerializer, UserGroupEditSerializer
from .serializers import UserPKUpdateSerializer
from .models import User, UserGroup from .models import User, UserGroup
@ -49,3 +50,25 @@ class UserAttributeApi(generics.RetrieveUpdateDestroyAPIView):
class UserGroupEditApi(generics.RetrieveUpdateAPIView): class UserGroupEditApi(generics.RetrieveUpdateAPIView):
queryset = User.objects.all() queryset = User.objects.all()
serializer_class = UserGroupEditSerializer serializer_class = UserGroupEditSerializer
class UserResetPasswordApi(generics.UpdateAPIView):
queryset = User.objects.all()
serializer_class = UserGroupEditSerializer
def perform_update(self, serializer):
# Note: we are not updating the user object here.
# We just do the reset-password staff.
user = self.get_object()
from .utils import send_reset_password_mail
send_reset_password_mail(user)
class UserResetPKApi(generics.UpdateAPIView):
queryset = User.objects.all()
serializer_class = UserPKUpdateSerializer
def perform_update(self, serializer):
user = self.get_object()
user.private_key = serializer.validated_data['_private_key']
user.save()

View File

@ -1,6 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from .models import User, UserGroup from .models import User, UserGroup
@ -38,3 +40,17 @@ class UserGroupEditSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = User model = User
fields = ['id', 'groups'] fields = ['id', 'groups']
class UserPKUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', '_private_key']
def validate__private_key(self, value):
from users.utils import validate_ssh_pk
checked, reason = validate_ssh_pk(value)
if not checked:
raise serializers.ValidationError(_('Not a valid ssh private key.'))
return value

View File

@ -0,0 +1,8 @@
{% extends '_modal.html' %}
{% load i18n %}
{% block modal_id %}user_reset_pk_modal{% endblock %}
{% block modal_title%}{% trans 'Reset User SSH Private Key' %}{% endblock %}
{% block modal_body %}
<textarea id="txt_pk" class="form-control" cols="30" rows="10" placeholder="-----BEGIN RSA PRIVATE KEY-----"></textarea>
{% endblock %}
{% block modal_confirm_id %}btn_user_reset_pk{% endblock %}

View File

@ -6,7 +6,9 @@
{% block custom_head_css_js %} {% block custom_head_css_js %}
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet"> <link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
<link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet">
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script> <script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
<script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">
@ -22,7 +24,7 @@
</ul> </ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">
<div class="col-sm-7" style="padding-left: 0px;"> <div class="col-sm-7" style="padding-left: 0">
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="ibox-title"> <div class="ibox-title">
<span class="label"><b>{{ user_object.name }}</b></span> <span class="label"><b>{{ user_object.name }}</b></span>
@ -108,14 +110,14 @@
<div class="col-sm-5" style="padding-left: 0;padding-right: 0"> <div class="col-sm-5" style="padding-left: 0;padding-right: 0">
<div class="panel panel-primary"> <div class="panel panel-primary">
<div class="panel-heading"> <div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Quick update' %} <i class="fa fa-info-circle"></i> {% trans 'Quick modify' %}
</div> </div>
<div class="panel-body"> <div class="panel-body">
<table class="table"> <table class="table">
<tbody> <tbody>
<tr class="no-borders-tr"> <tr class="no-borders-tr">
<td width="50%">Active:</td> <td width="50%">{% trans 'Active' %}:</td>
<td><span style="float: right"> <td><span class="pull-right">
<div class="switch"> <div class="switch">
<div class="onoffswitch"> <div class="onoffswitch">
<input type="checkbox" {% if user_object.is_active %} checked {% endif %} class="onoffswitch-checkbox" id="is_active"> <input type="checkbox" {% if user_object.is_active %} checked {% endif %} class="onoffswitch-checkbox" id="is_active">
@ -128,8 +130,8 @@
</span></td> </span></td>
</tr> </tr>
<tr> <tr>
<td>二次验证:</td> <td>{% trans 'Enable OTP' %}:</td>
<td><span style="float: right"> <td><span class="pull-right">
<div class="switch"> <div class="switch">
<div class="onoffswitch"> <div class="onoffswitch">
<input type="checkbox" class="onoffswitch-checkbox" {% if user_object.enable_otp %} checked {% endif %} <input type="checkbox" class="onoffswitch-checkbox" {% if user_object.enable_otp %} checked {% endif %}
@ -145,16 +147,16 @@
<tr> <tr>
<td>{% trans 'Reset password' %}:</td> <td>{% trans 'Reset password' %}:</td>
<td> <td>
<span style="float: right"> <span class="pull-right">
<button type="button" class="btn btn-primary btn-xs" style="width: 54px">{% trans 'Reset' %}</button> <button type="button" class="btn btn-primary btn-xs" id="btn_reset_password" style="width: 54px">{% trans 'Reset' %}</button>
</span> </span>
</td> </td>
</tr> </tr>
<tr> <tr>
<td>{% trans 'Reset ssh key' %}:</td> <td>{% trans 'Reset ssh key' %}:</td>
<td> <td>
<span style="float: right"> <span class="pull-right">
<button type="button" class="btn btn-primary btn-xs" style="width: 54px;">{% trans 'Reset' %}</button> <button type="button" class="btn btn-primary btn-xs" id="btn_reset_pk" style="width: 54px;" data-toggle="modal" data-target="#user_reset_pk_modal">{% trans 'Reset' %}</button>
</span> </span>
</td> </td>
</tr> </tr>
@ -191,7 +193,7 @@
<tr> <tr>
<td ><b class="bdg_user_group" data-gid={{ group.id }}>{{ group.name }}</b></td> <td ><b class="bdg_user_group" data-gid={{ group.id }}>{{ group.name }}</b></td>
<td> <td>
<button class="btn btn-danger btn-xs btn_delete_user_group" type="button" style="float: right;"><i class="fa fa-minus"></i></button> <button class="btn btn-danger pull-right btn-sm btn_delete_user_group" type="button"><i class="fa fa-minus"></i></button>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
@ -205,12 +207,11 @@
</div> </div>
</div> </div>
</div> </div>
</div> {% include 'users/_user_reset_pk_modal.html' %}
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <script>
jumpserver.selected_groups = new Object(); jumpserver.selected_groups = {};
function updateUserGroups(user_groups) { function updateUserGroups(user_groups) {
var the_url = "{% url 'users:user-group-edit-api' pk=user_object.id%}"; var the_url = "{% url 'users:user-group-edit-api' pk=user_object.id%}";
var body = { var body = {
@ -227,43 +228,38 @@ function updateUserGroups(user_groups) {
$('.group_edit tbody').append( $('.group_edit tbody').append(
'<tr>' + '<tr>' +
'<td><b class="bdg_user_group" data-gid="' + index + '">' + group_name + '</b></td>' + '<td><b class="bdg_user_group" data-gid="' + index + '">' + group_name + '</b></td>' +
'<td><button class="btn btn-danger btn-sm btn_delete_user_group" type="button" style="float: right;"><i class="fa fa-minus"></i></button></td>' + '<td><button class="btn btn-danger btn-sm pull-right btn_delete_user_group" type="button"><i class="fa fa-minus"></i></button></td>' +
'</tr>' '</tr>'
) )
}); });
// clear jumpserver.selected_groups // clear jumpserver.selected_groups
jumpserver.selected_groups = {}; jumpserver.selected_groups = {};
toastr.success('{% trans "Update success!" %}') toastr.success('{% trans "UserGroup Update Success!" %}')
} };
APIUpdateAttr({ url: the_url, body: JSON.stringify(body), success: success, method: 'PUT'}); APIUpdateAttr({ url: the_url, body: JSON.stringify(body), success: success, method: 'PUT'});
} }
$(document).ready(function () { $(document).ready(function () {
$('.select2').select2() $('.select2').select2()
.on('select2:select', function(evt, params) { .on('select2:select', function(evt) {
var data = evt.params.data; var data = evt.params.data;
jumpserver.selected_groups[data.id] = data.text; jumpserver.selected_groups[data.id] = data.text;
}).on('select2:unselect', function(evt) { }).on('select2:unselect', function(evt) {
var data = evt.params.data; var data = evt.params.data;
delete jumpserver.selected_groups[data.id] delete jumpserver.selected_groups[data.id]
}) })
}); }).on('click', '#is_active', function(){
$(document).on('click', '#is_active', function(){
var the_url = "{% url 'users:user-patch-api' pk=user_object.id %}"; var the_url = "{% url 'users:user-patch-api' pk=user_object.id %}";
var checked = !$(this).prop('checked'); var checked = !$(this).prop('checked');
var body = {'is_active': checked }; var body = {'is_active': checked };
var success = function(data) { var success = '{% trans "Update Successfully!" %}';
toastr.success('{% trans "Update success!" %}') APIUpdateAttr({ url: the_url, body: JSON.stringify(body), success_message: success});
}
APIUpdateAttr({ url: the_url, body: body, success: success});
}).on('click', '#enable_otp', function(){ }).on('click', '#enable_otp', function(){
var the_url = "{% url 'users:user-patch-api' pk=user_object.id %}"; var the_url = "{% url 'users:user-patch-api' pk=user_object.id %}";
var checked = !$(this).prop('checked'); var checked = !$(this).prop('checked');
var body = {'enable_otp': checked }; var body = {'enable_otp': checked };
var success = function(data) { var success = '{% trans "Update Successfully!" %}';
toastr.success('{% trans "Update success!" %}') APIUpdateAttr({ url: the_url, body: JSON.stringify(body), success_message: success});
}
APIUpdateAttr({ url: the_url, body: body, success: success});
}).on('click', '#btn_add_user_group', function(){ }).on('click', '#btn_add_user_group', function(){
if (Object.keys(jumpserver.selected_groups).length === 0) { if (Object.keys(jumpserver.selected_groups).length === 0) {
return false; return false;
@ -290,6 +286,56 @@ $(document).on('click', '#is_active', function(){
return $(this).data('gid'); return $(this).data('gid');
}).get(); }).get();
updateUserGroups(user_groups) updateUserGroups(user_groups)
}) }).on('click', '#btn_reset_password', function(){
function doReset() {
var the_url = '{% url "users:user-reset-password-api" pk=user_object.id %}';
var body = {};
var success = function() {
var msg = "{% trans 'E-mail sent successfully. An e-mail has been sent to the user\'s mailbox.' %}";
swal("{% trans 'Password-Reset' %}", msg, "success");
}
APIUpdateAttr({ url: the_url, body: JSON.stringify(body), success: success});
}
swal({
title: "{% trans 'Are you sure?' %}",
text: "{% trans 'This will reset the user\'s password.' %}",
type: "warning",
showCancelButton: true,
confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false
}, function () {
doReset();
}
);
}).on('click', '#btn_user_reset_pk', function(){
var $this = $(this);
var pk = $('#txt_pk').val();
var the_url = '{% url "users:user-reset-pk-api" pk=user_object.id %}';
var body = {'_private_key': pk};
var success = function() {
$('#txt_pk').val('');
$this.closest('.modal').modal('hide');
var msg = "{% trans 'Successfully updated the SSH private key.' %}";
swal("{% trans 'User SSH Private Key Reset' %}", msg, "success");
};
var fail = function() {
var msg = "{% trans 'Failed to update the user\'s SSH private key.' %}";
swal({
title: "{% trans 'User SSH Private Key Reset' %}",
text: msg,
type: "error",
showCancelButton: false,
confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: true
}, function () {
$('#txt_pk').focus();
}
);
}
APIUpdateAttr({ url: the_url, body: JSON.stringify(body), success: success, error: fail});
});
</script> </script>
{% endblock %} {% endblock %}

View File

@ -35,6 +35,8 @@ urlpatterns += [
api.UserDetailDeleteUpdateApi.as_view(), name='user-detail-api'), api.UserDetailDeleteUpdateApi.as_view(), name='user-detail-api'),
url(r'^v1/users/(?P<pk>[0-9]+)/patch$', url(r'^v1/users/(?P<pk>[0-9]+)/patch$',
api.UserAttributeApi.as_view(), name='user-patch-api'), api.UserAttributeApi.as_view(), name='user-patch-api'),
url(r'^v1/users/(?P<pk>\d+)/reset-password/$', api.UserResetPasswordApi.as_view(), name='user-reset-password-api'),
url(r'^v1/users/(?P<pk>\d+)/reset-pk/$', api.UserResetPKApi.as_view(), name='user-reset-pk-api'),
url(r'^v1/user-groups$', api.UserGroupListAddApi.as_view(), name='user-group-list-api'), url(r'^v1/user-groups$', api.UserGroupListAddApi.as_view(), name='user-group-list-api'),
url(r'^v1/user-groups/(?P<pk>[0-9]+)$', url(r'^v1/user-groups/(?P<pk>[0-9]+)$',
api.UserGroupDetailDeleteUpdateApi.as_view(), name='user-group-detail-api'), api.UserGroupDetailDeleteUpdateApi.as_view(), name='user-group-detail-api'),

View File

@ -5,6 +5,7 @@ import logging
import os import os
import re import re
from django.conf import settings
from django.contrib.auth.mixins import UserPassesTestMixin from django.contrib.auth.mixins import UserPassesTestMixin
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
@ -121,6 +122,8 @@ def send_reset_password_mail(user):
'email': user.email, 'email': user.email,
'login_url': reverse('users:login', external=True), 'login_url': reverse('users:login', external=True),
} }
if settings.DEBUG:
logger.debug(message)
send_mail_async.delay(subject, message, recipient_list, html_message=message) send_mail_async.delay(subject, message, recipient_list, html_message=message)