User management.

This commit is contained in:
Harshal Dhumal 2016-06-06 13:34:08 +01:00 committed by Dave Page
parent 4398d1d869
commit e3ab4501d5
7 changed files with 1068 additions and 5 deletions

View File

@ -150,7 +150,7 @@ MAX_SESSION_IDLE_TIME = 60
# The schema version number for the configuration database
# DO NOT CHANGE UNLESS YOU ARE A PGADMIN DEVELOPER!!
SETTINGS_SCHEMA_VERSION = 10
SETTINGS_SCHEMA_VERSION = 11
# The default path to the SQLite database used to store user accounts and
# settings. This default places the file in the same directory as this

View File

@ -475,6 +475,7 @@ def index():
return render_template(
MODULE_NAME + "/index.html",
username=current_user.email,
is_admin=current_user.has_role("Administrator"),
_=gettext
)

View File

@ -68,6 +68,10 @@ try {
<ul class="dropdown-menu navbar-inverse">
<li><a href="{{ url_for('security.change_password') }}">{{ _('Change Password') }}</a></li>
<li class="divider"></li>
{% if is_admin %}
<li><a onclick="pgAdmin.Browser.UserManagement.show_users()">{{ _('Users') }}</a></li>
<li class="divider"></li>
{% endif %}
<li><a href="{{ url_for('security.logout') }}">{{ _('Logout') }}</a></li>
</ul>
</li>

View File

@ -1103,7 +1103,7 @@ span.button-label {
}
button.pg-alertify-button {
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
font-size: initial;
font-size: 15px;
}
.fa.pg-alertify-button:before {
font: normal normal normal 18px/1 FontAwesome;
@ -1233,6 +1233,7 @@ form[name="change_password_form"] .help-block {
}
}
/* Override Backgrid's default z-index */
.dashboard-tab-container .backgrid-filter .search {
z-index: 10 !important;
@ -1264,3 +1265,64 @@ form[name="change_password_form"] .help-block {
-webkit-appearance: none;
-moz-appearance: none;
}
.subnode-footer {
text-align: right;
border-color: #a9a9a9;
border-style: inset inset inset solid;
border-width: 2px 1px 0;
margin-top: -10px;
}
.subnode-footer .ajs-button {
margin: 2px 2px 0;
}
.user_management {
margin: 0 10px !important;
width: calc(100% - 20px);
height: 100%;
overflow: hidden;
}
.user_management .search_users form {
margin: 0;
}
.user_management table {
display: block;
height: 100%;
overflow: auto;
border: 0 none;
}
.user_management .backform-tab {
height: calc(100% - 75px);
}
.user_management .search_users {
float:right;
margin-right: 5px;
padding:0 !important;
}
.user_management .search_users input{
height:15px;
margin-top: 3px;
}
.user_management .user_container {
height: calc(100% - 35px);
}
.user_management input[placeholder] {
font-size: 12px;
}
.user_management-pg-alertify-button {
font-size: 14px !important;
}
.alertify_tools_dialog_backgrid_properties {
top: 43px !important;
}

View File

@ -0,0 +1,327 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""Implements pgAdmin4 User Management Utility"""
import json
import re
from flask import render_template, request, \
url_for, Response, abort
from flask.ext.babel import gettext as _
from flask.ext.security import login_required, roles_required, current_user
from pgadmin.utils.ajax import make_response as ajax_response,\
make_json_response, bad_request, internal_server_error
from pgadmin.utils import PgAdminModule
from pgadmin.model import db, Role, User, UserPreference, Server,\
ServerGroup, Process, Setting
from flask.ext.security.utils import encrypt_password
# set template path for sql scripts
MODULE_NAME = 'user_management'
server_info = {}
class UserManagementModule(PgAdminModule):
"""
class UserManagementModule(Object):
It is a utility which inherits PgAdminModule
class and define methods to load its own
javascript file.
"""
LABEL = _('Users')
def get_own_javascripts(self):
""""
Returns:
list: js files used by this module
"""
return [{
'name': 'pgadmin.tools.user_management',
'path': url_for('user_management.index') + 'user_management',
'when': None
}]
def show_system_objects(self):
"""
return system preference objects
"""
return self.pref_show_system_objects
# Create blueprint for BackupModule class
blueprint = UserManagementModule(
MODULE_NAME, __name__, static_url_path=''
)
def validate_user(data):
new_data = dict()
email_filter = '^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$'
if ('newPassword' in data and data['newPassword'] != "" and
'confirmPassword' in data and data['confirmPassword'] != ""):
if data['newPassword'] == data['confirmPassword']:
new_data['password'] = encrypt_password(data['newPassword'])
else:
raise Exception(_("Passwords do not match."))
if 'email' in data and data['email'] != "":
if re.match(email_filter, data['email']):
new_data['email'] = data['email']
else:
raise Exception(_("Invalid email address."))
if 'role' in data and data['role'] != "":
new_data['roles'] = int(data['role'])
if 'active' in data and data['active'] != "":
new_data['active'] = data['active']
return new_data
@blueprint.route("/")
@login_required
def index():
return bad_request(errormsg=_("This URL can not be called directly."))
@blueprint.route("/user_management.js")
@login_required
def script():
"""render own javascript"""
return Response(
response=render_template(
"user_management/js/user_management.js", _=_,
is_admin=current_user.has_role("Administrator"),
user_id=current_user.id
),
status=200,
mimetype="application/javascript"
)
@blueprint.route('/user/', methods=['GET'], defaults={'uid': None})
@blueprint.route('/user/<int:uid>', methods=['GET'])
@roles_required('Administrator')
def user(uid):
"""
Args:
uid: User id
Returns: List of pgAdmin4 users or single user if uid is provided.
"""
if uid:
u = User.query.get(uid)
res = {'id': u.id,
'email': u.email,
'active': u.active,
'role': u.roles[0].id
}
else:
users = User.query.all()
users_data = []
for u in users:
users_data.append({'id': u.id,
'email': u.email,
'active': u.active,
'role': u.roles[0].id
})
res = users_data
return ajax_response(
response=res,
status=200
)
@blueprint.route('/user/', methods=['POST'])
@roles_required('Administrator')
def create():
"""
Returns:
"""
data = request.form if request.form else json.loads(request.data.decode())
for f in ('email', 'role', 'active', 'newPassword', 'confirmPassword'):
if f in data and data[f] != '':
continue
else:
return bad_request(errormsg=_("Missing field: '{0}'".format(f)))
try:
new_data = validate_user(data)
if 'roles' in new_data:
new_data['roles'] = [Role.query.get(new_data['roles'])]
except Exception as e:
return bad_request(errormsg=_(str(e)))
try:
usr = User(email=new_data['email'],
roles=new_data['roles'],
active=new_data['active'],
password=new_data['password'])
db.session.add(usr)
db.session.commit()
# Add default server group for new user.
server_group = ServerGroup(user_id=usr.id, name="Servers")
db.session.add(server_group)
db.session.commit()
except Exception as e:
return internal_server_error(errormsg=str(e))
res = {'id': usr.id,
'email': usr.email,
'active': usr.active,
'role': usr.roles[0].id
}
return ajax_response(
response=res,
status=200
)
@blueprint.route('/user/<int:uid>', methods=['DELETE'])
@roles_required('Administrator')
def delete(uid):
"""
Args:
uid:
Returns:
"""
usr = User.query.get(uid)
if not usr:
abort(404)
try:
Setting.query.filter_by(user_id=uid).delete()
UserPreference.query.filter_by(uid=uid).delete()
Server.query.filter_by(user_id=uid).delete()
ServerGroup.query.filter_by(user_id=uid).delete()
Process.query.filter_by(user_id=uid).delete()
# Finally delete user
db.session.delete(usr)
db.session.commit()
return make_json_response(
success=1,
info=_("User Deleted."),
data={}
)
except Exception as e:
return internal_server_error(errormsg=str(e))
@blueprint.route('/user/<int:uid>', methods=['PUT'])
@roles_required('Administrator')
def update(uid):
"""
Args:
uid:
Returns:
"""
usr = User.query.get(uid)
if not usr:
abort(404)
data = request.form if request.form else json.loads(request.data.decode())
try:
new_data = validate_user(data)
if 'roles' in new_data:
new_data['roles'] = [Role.query.get(new_data['roles'])]
except Exception as e:
return bad_request(errormsg=_(str(e)))
try:
for k, v in new_data.items():
setattr(usr, k, v)
db.session.commit()
res = {'id': usr.id,
'email': usr.email,
'active': usr.active,
'role': usr.roles[0].id
}
return ajax_response(
response=res,
status=200
)
except Exception as e:
return internal_server_error(errormsg=str(e))
@blueprint.route('/role/', methods=['GET'], defaults={'rid': None})
@blueprint.route('/role/<int:rid>', methods=['GET'])
@roles_required('Administrator')
def role(rid):
"""
Args:
rid: Role id
Returns: List of pgAdmin4 users roles or single role if rid is provided.
"""
if rid:
r = Role.query.get(rid)
res = {'id': r.id, 'name': r.name}
else:
roles = Role.query.all()
roles_data = []
for r in roles:
roles_data.append({'id': r.id,
'name': r.name})
res = roles_data
return ajax_response(
response=res,
status=200
)

View File

@ -0,0 +1,651 @@
define([
'jquery', 'underscore', 'underscore.string', 'alertify',
'pgadmin.browser', 'backbone', 'backgrid', 'backform', 'pgadmin.browser.node',
'backgrid.select.all', 'backgrid.filter'
],
// This defines Backup dialog
function($, _, S, alertify, pgBrowser, Backbone, Backgrid, Backform, pgNode) {
// if module is already initialized, refer to that.
if (pgBrowser.UserManagement) {
return pgBrowser.UserManagement;
}
var BASEURL = '{{ url_for('user_management.index')}}',
USERURL = BASEURL + 'user/',
ROLEURL = BASEURL + 'role/',
userFilter = function(collection) {
return (new Backgrid.Extension.ClientSideFilter({
collection: collection,
placeholder: _('Filter by email'),
// The model fields to search for matches
fields: ['email'],
// How long to wait after typing has stopped before searching can start
wait: 150
}));
},
StringDepCell = Backgrid.StringCell.extend({
initialize: function() {
Backgrid.StringCell.prototype.initialize.apply(this, arguments);
Backgrid.Extension.DependentCell.prototype.initialize.apply(this, arguments);
},
dependentChanged: function () {
this.$el.empty();
var self = this,
model = this.model,
column = this.column,
editable = this.column.get("editable");
this.render();
is_editable = _.isFunction(editable) ? !!editable.apply(column, [model]) : !!editable;
setTimeout(function() {
self.$el.removeClass("editor");
if (is_editable){ self.$el.addClass("editable"); }
else { self.$el.removeClass("editable"); }
}, 10);
this.delegateEvents();
return this;
},
remove: Backgrid.Extension.DependentCell.prototype.remove
});
pgBrowser.UserManagement = {
init: function() {
if (this.initialized)
return;
this.initialized = true;
return this;
}
{% if is_admin %},
// Callback to draw User Management Dialog.
show_users: function(action, item, params) {
var Roles = [];
var UserModel = pgAdmin.Browser.Node.Model.extend({
idAttribute: 'id',
urlRoot: USERURL,
defaults: {
id: undefined,
email: undefined,
active: true,
role: undefined,
newPassword: undefined,
confirmPassword: undefined
},
schema: [
{
id: 'email', label: '{{ _('Email') }}', type: 'text',
cell:StringDepCell, cellHeaderClasses:'width_percent_30',
deps: ['id'],
editable: function(m) {
if(m instanceof Backbone.Collection) {
return false;
}
// Disable email edit for existing user.
if (m.isNew()){
return true;
}
return false;
}
},{
id: 'role', label: '{{ _('Role') }}',
type: 'text', control: "Select2", cellHeaderClasses:'width_percent_20',
cell: 'select2', select2: {allowClear: false, openOnEnter: false},
options: function (controlOrCell) {
var options = [];
if( controlOrCell instanceof Backform.Control){
// This is be backform select2 control
_.each(Roles, function(role) {
options.push({
label: role.name,
value: role.id.toString()}
);
});
} else {
// This must be backgrid select2 cell
_.each(Roles, function(role) {
options.push([role.name, role.id.toString()]);
});
}
return options;
},
editable: function(m) {
if(m instanceof Backbone.Collection) {
return true;
}
if (m.get("id") == {{user_id}}){
return false;
} else {
return true;
}
}
},{
id: 'active', label: '{{ _('Active') }}',
type: 'switch', cell: 'switch', cellHeaderClasses:'width_percent_10',
options: { 'onText': 'Yes', 'offText': 'No'},
editable: function(m) {
if(m instanceof Backbone.Collection) {
return true;
}
if (m.get("id") == {{user_id}}){
return false;
} else {
return true;
}
}
},{
id: 'newPassword', label: '{{ _('New password') }}',
type: 'password', disabled: false, control: 'input',
cell: 'password', cellHeaderClasses:'width_percent_20'
},{
id: 'confirmPassword', label: '{{ _('Confirm password') }}',
type: 'password', disabled: false, control: 'input',
cell: 'password', cellHeaderClasses:'width_percent_20'
}],
validate: function() {
var err = {},
errmsg = null,
changedAttrs = this.changed || {},
email_filter = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
if (('email' in changedAttrs || !this.isNew()) && (_.isUndefined(this.get('email')) ||
_.isNull(this.get('email')) ||
String(this.get('email')).replace(/^\s+|\s+$/g, '') == '')) {
errmsg = '{{ _('Email address cannot be empty.')}}';
this.errorModel.set('email', errmsg);
return errmsg;
} else if (!!this.get('email') && !email_filter.test(this.get('email'))) {
errmsg = S("{{ _("Invalid Email id: %%s")}}").sprintf(
this.get('email')
).value();
this.errorModel.set('email', errmsg);
return errmsg;
} else if (!!this.get('email') && this.collection.where({"email":this.get('email')}).length > 1) {
errmsg = S("{{ _("This email id %%s already exist.")}}").sprintf(
this.get('email')
).value();
this.errorModel.set('email', errmsg);
return errmsg;
} else {
this.errorModel.unset('email');
}
if ('role' in changedAttrs && (_.isUndefined(this.get('role')) ||
_.isNull(this.get('role')) ||
String(this.get('role')).replace(/^\s+|\s+$/g, '') == '')) {
errmsg = S("{{ _("Role cannot be empty for user %%s")}}").sprintf(
(this.get('email') || '')
).value();
this.errorModel.set('role', errmsg);
return errmsg;
} else {
this.errorModel.unset('role');
}
if(this.isNew()){
// Password is compulsory for new user.
if ('newPassword' in changedAttrs && (_.isUndefined(this.get('newPassword')) ||
_.isNull(this.get('newPassword')) ||
this.get('newPassword') == '')) {
errmsg = S("{{ _("Password cannot be empty for user %%s")}}").sprintf(
(this.get('email') || '')
).value();
this.errorModel.set('newPassword', errmsg);
return errmsg;
} else if ('newPassword' in changedAttrs && !_.isUndefined(this.get('newPassword')) &&
!_.isNull(this.get('newPassword')) &&
this.get('newPassword').length < 6) {
errmsg = S("{{ _("Password must be at least 6 characters for user %%s")}}").sprintf(
(this.get('email') || '')
).value();
this.errorModel.set('newPassword', errmsg);
return errmsg;
} else {
this.errorModel.unset('newPassword');
}
if ('confirmPassword' in changedAttrs && (_.isUndefined(this.get('confirmPassword')) ||
_.isNull(this.get('confirmPassword')) ||
this.get('confirmPassword') == '')) {
errmsg = S("{{ _("Confirm Password cannot be empty for user %%s")}}").sprintf(
(this.get('email') || '')
).value();
this.errorModel.set('confirmPassword', errmsg);
return errmsg;
} else {
this.errorModel.unset('confirmPassword');
}
if(!!this.get('newPassword') && !!this.get('confirmPassword') &&
this.get('newPassword') != this.get('confirmPassword')) {
errmsg = S("{{ _("Passwords do not match for user %%s")}}").sprintf(
(this.get('email') || '')
).value();
this.errorModel.set('confirmPassword', errmsg);
return errmsg;
} else {
this.errorModel.unset('confirmPassword');
}
} else {
if ((_.isUndefined(this.get('newPassword')) || _.isNull(this.get('newPassword')) ||
this.get('newPassword') == '') &&
((_.isUndefined(this.get('confirmPassword')) || _.isNull(this.get('confirmPassword')) ||
this.get('confirmPassword') == ''))) {
this.errorModel.unset('newPassword');
if(this.get('newPassword') == ''){
this.set({'newPassword': undefined})
}
this.errorModel.unset('confirmPassword');
if(this.get('confirmPassword') == ''){
this.set({'confirmPassword': undefined})
}
} else if (!_.isUndefined(this.get('newPassword')) &&
!_.isNull(this.get('newPassword')) &&
!this.get('newPassword') == '' &&
this.get('newPassword').length < 6) {
errmsg = S("{{ _("Password must be at least 6 characters for user %%s")}}").sprintf(
(this.get('email') || '')
).value();
this.errorModel.set('newPassword', errmsg);
return errmsg;
} else if (_.isUndefined(this.get('confirmPassword')) ||
_.isNull(this.get('confirmPassword')) ||
this.get('confirmPassword') == '') {
errmsg = S("{{ _("Confirm password cannot be empty for user %%s")}}").sprintf(
(this.get('email') || '')
).value();
this.errorModel.set('confirmPassword', errmsg);
return errmsg;
} else if (!!this.get('newPassword') && !!this.get('confirmPassword') &&
this.get('newPassword') != this.get('confirmPassword')) {
errmsg = S("{{ _("Passwords do not match for user %%s")}}").sprintf(
(this.get('email') || '')
).value();
this.errorModel.set('confirmPassword', errmsg);
return errmsg;
} else {
this.errorModel.unset('newPassword');
this.errorModel.unset('confirmPassword');
}
}
return null;
}
}),
gridSchema = Backform.generateGridColumnsFromModel(
null, UserModel, 'edit'),
deleteUserCell = Backgrid.Extension.DeleteCell.extend({
deleteRow: function(e) {
self = this;
e.preventDefault();
if (self.model.get("id") == {{user_id}}) {
alertify.alert(
'{{_('Cannot delete user.') }}',
'{{_('Cannot delete currently logged in user.') }}',
function(){
return true;
}
);
return true;
}
// We will check if row is deletable or not
var canDeleteRow = (!_.isUndefined(this.column.get('canDeleteRow')) &&
_.isFunction(this.column.get('canDeleteRow')) ) ?
Backgrid.callByNeed(this.column.get('canDeleteRow'),
this.column, this.model) : true;
if (canDeleteRow) {
if(self.model.isNew()){
self.model.destroy();
} else {
alertify.confirm(
'Delete user?',
'Are you sure you wish to delete this user?',
function(evt) {
self.model.destroy({
wait: true,
success: function(res) {
alertify.success('{{_('User deleted.') }}');
},
error: function(m, jqxhr) {
alertify.error('{{_('Error during deleting user.') }}');
}
});
},
function(evt) {
return true;
}
);
}
} else {
alertify.alert("This user cannot be deleted.",
function(){
return true;
}
);
}
}
});
gridSchema.columns.unshift({
name: "pg-backform-delete", label: "",
cell: deleteUserCell,
editable: false, cell_priority: -1,
canDeleteRow: true
});
// Users Management dialog code here
if(!alertify.UserManagement) {
alertify.dialog('UserManagement' ,function factory() {
return {
main: function(title) {
this.set('title', title);
},
setup:function() {
return {
buttons: [{
text: '{{ _('Close') }}', key: 27, className: 'btn btn-danger fa fa-lg fa-times pg-alertify-button',
attrs:{name:'close'}
}],
// Set options for dialog
options: {
title: '{{ _('User Management') }}',
//disable both padding and overflow control.
padding : !1,
overflow: !1,
model: 0,
resizable: true,
maximizable: true,
pinnable: false,
closableByDimmer: false,
closable: false
}
};
},
hooks: {
// Triggered when the dialog is closed
onclose: function() {
if (this.view) {
// clear our backform model/view
this.view.remove({data: true, internal: true, silent: true});
this.$content.remove();
}
}
},
prepare: function() {
var self = this,
footerTpl = _.template([
'<div class="pg-prop-footer">',
'<div class="pg-prop-status-bar" style="visibility:hidden">',
'</div>',
'</div>'].join("\n")),
$footer = $(footerTpl()),
$statusBar = $footer.find('.pg-prop-status-bar'),
UserRow = Backgrid.Row.extend({
userInvalidColor: "lightYellow",
userValidColor: "#fff",
initialize: function() {
Backgrid.Row.prototype.initialize.apply(this, arguments);
this.listenTo(this.model, 'pgadmin:user:invalid', this.userInvalid);
this.listenTo(this.model, 'pgadmin:user:valid', this.userValid);
},
userInvalid: function() {
$(this.el).removeClass("new");
this.el.style.backgroundColor = this.userInvalidColor;
},
userValid: function() {
this.el.style.backgroundColor = this.userValidColor;
}
}),
UserCollection = Backbone.Collection.extend({
model: UserModel,
url: USERURL,
initialize: function() {
Backbone.Collection.prototype.initialize.apply(this, arguments);
var self = this;
self.changedUser = null;
self.invalidUsers = {};
self.on('add', self.onModelAdd);
self.on('remove', self.onModelRemove);
self.on('pgadmin-session:model:invalid', function(msg, m, c) {
self.invalidUsers[m.cid] = msg;
m.trigger('pgadmin:user:invalid', m);
$statusBar.html(msg).css("visibility", "visible");
});
self.on('pgadmin-session:model:valid', function(m, c) {
delete self.invalidUsers[m.cid];
m.trigger('pgadmin:user:valid', m);
this.updateErrorMsg();
this.saveUser(m);
});
},
onModelAdd: function(m) {
// Start tracking changes.
m.startNewSession();
},
onModelRemove: function(m) {
delete this.invalidUsers[m.cid];
this.updateErrorMsg();
},
updateErrorMsg: function() {
var self = this,
msg = null;
for (var key in self.invalidUsers) {
msg = self.invalidUsers [key];
if (msg) {
break;
}
}
if(msg){
$statusBar.html(msg).css("visibility", "visible");
} else {
$statusBar.empty().css("visibility", "hidden");
}
},
saveUser: function(m) {
d = m.toJSON(true);
if(m.isNew() && (!m.get('email') || !m.get('role') ||
!m.get('newPassword') || !m.get('confirmPassword') ||
m.get('newPassword') != m.get('confirmPassword'))
) {
// New user model is valid but partially filled so return without saving.
return false;
} else if (!m.isNew() && m.get('newPassword') != m.get('confirmPassword')) {
// For old user password change is in progress and user model is valid but admin has not added
// both the passwords so return without saving.
return false;
}
if (m.sessChanged() && d && !_.isEmpty(d)) {
m.stopSession();
m.save({}, {
attrs: d,
wait: true,
success: function(res) {
// User created/updated on server now start new session for this user.
m.set({'newPassword':undefined,
'confirmPassword':undefined});
m.startNewSession();
alertify.success(S("{{_("User '%%s' saved.")|safe }}").sprintf(
m.get('email')
).value());
},
error: function(res, jqxhr) {
m.startNewSession();
alertify.error(
S("{{_("Error during saving user: '%%s'")|safe }}").sprintf(
jqxhr.responseJSON.errormsg
).value()
);
}
});
}
}
}),
userCollection = this.userCollection = new UserCollection(),
header = [
'<div class="subnode-header">',
' <button class="btn-sm btn-default add" title="<%-add_title%>" <%=canAdd ? "" : "disabled=\'disabled\'"%> ><%=add_label ? add_label : "" %></button>',
' <div class="control-label search_users"></div>',
'</div>',].join("\n"),
headerTpl = _.template(header),
data = {
canAdd: true,
add_title: '{{ _("Add new user")}}',
add_label:'{{ _('ADD')}}'
},
$gridBody = $("<div></div>", {
class: "user_container"
});
$.ajax({
url: ROLEURL,
method: 'GET',
async: false,
success: function(res) {
Roles = res
},
error: function(e) {
setTimeout(function() {
alertify.alert(
'{{ _('Cannot load user roles.') }}'
);
},100);
}
});
var view = this.view = new Backgrid.Grid({
row: UserRow,
columns: gridSchema.columns,
collection: userCollection,
className: "backgrid table-bordered"
});
$gridBody.append(view.render().$el[0]);
this.$content = $("<div class='user_management object subnode'></div>").append(
headerTpl(data)).append($gridBody
).append($footer);
$(this.elements.body.childNodes[0]).addClass(
'alertify_tools_dialog_backgrid_properties');
this.elements.content.appendChild(this.$content[0]);
// Render Search Filter
$('.search_users').append(
userFilter(userCollection).render().el);
userCollection.fetch();
this.$content.find('button.add').first().click(function(e) {
e.preventDefault();
var canAddRow = true;
if (canAddRow) {
// There should be only one empty row.
var isEmpty = false,
unsavedModel = null;
userCollection.each(function(model) {
if(!isEmpty) {
isEmpty = model.isNew();
unsavedModel = model;
}
});
if(isEmpty) {
var idx = userCollection.indexOf(unsavedModel),
row = view.body.rows[idx].$el;
row.addClass("new");
$(row).pgMakeVisible('backform-tab');
return false;
}
$(view.body.$el.find($("tr.new"))).removeClass("new")
var m = new (UserModel) (null, {
handler: userCollection,
top: userCollection,
collection: userCollection
});
userCollection.add(m);
var idx = userCollection.indexOf(m),
newRow = view.body.rows[idx].$el;
newRow.addClass("new");
$(newRow).pgMakeVisible('backform-tab');
return false;
}
});
},
callback: function(e) {
if (e.button.element.name == "close") {
var self = this;
if (!_.all(this.userCollection.pluck('id')) || !_.isEmpty(this.userCollection.invalidUsers)) {
e.cancel = true;
alertify.confirm(
'{{ _('Discard unsaved changes?') }}',
'{{ _('Are you sure you want to close the dialog? Any unsaved changes will be lost.') }}',
function(e) {
self.close();
return true;
},
function(e) {
// Do nothing.
return true;
}
);
}
}
}
};
});
}
alertify.UserManagement(true).resizeTo('680px','400px');
}{% endif %}
};
return pgBrowser.UserManagement;
});

View File

@ -69,12 +69,17 @@ account:\n""")
db.create_all()
user_datastore.create_role(
name='Administrators',
description='pgAdmin Administrators Role'
name='Administrator',
description='pgAdmin Administrator Role'
)
user_datastore.create_role(
name='User',
description='pgAdmin User Role'
)
user_datastore.create_user(email=email, password=password)
db.session.flush()
user_datastore.add_role_to_user(email, 'Administrators')
user_datastore.add_role_to_user(email, 'Administrator')
# Get the user's ID and create the default server group
user = User.query.filter_by(email=email).first()
@ -249,6 +254,19 @@ CREATE TABLE process(
FOREIGN KEY(user_id) REFERENCES user (id)
)""")
if int(version.value) < 11:
db.engine.execute("""
UPDATE role
SET name = 'Administrator',
description = 'pgAdmin Administrator Role'
WHERE name = 'Administrators'
""")
db.engine.execute("""
INSERT INTO role ( name, description )
VALUES ('User', 'pgAdmin User Role')
""")
# Finally, update the schema version
version.value = config.SETTINGS_SCHEMA_VERSION
db.session.merge(version)