Hi,
PFA patch for user management functionality.
Major changes in this patch are:
1. In-place editing of user and saving on server.
2. Removed dependency on marshmallow python package.
3. UI improvements.
4. Search by email.
--
*Harshal Dhumal*
*Software Engineer*
EnterpriseDB India: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Tue, May 31, 2016 at 4:24 PM, Ashesh Vashi <[email protected]
> wrote:
> On Tue, May 31, 2016 at 2:45 PM, Dave Page <[email protected]> wrote:
>
>>
>>
>> On Tue, May 31, 2016 at 9:52 AM, Ashesh Vashi <
>> [email protected]> wrote:
>>
>>>
>>>
>>>
>>> On Tue, May 31, 2016 at 1:26 PM, Dave Page <[email protected]> wrote:
>>>
>>>> Yeah, I was about to reply on this too;
>>>>
>>>> - Why do we need Marshmallow?
>>>>
>>>> - Why do we need a role system? Perhaps in the future we might have
>>>> shared servers, and roles that cannot modify them, but not now.
>>>>
>>> Hmm..
>>> He just added one more role - 'Standard'.
>>> 'Administrator' role was already presents in it.
>>>
>>
>> Oh yes, sorry, misread that. 'pgAdmin User Role' would be a better
>> description though I think.
>>
> Yeah - that can be done. :-)
>
> --
>
> Thanks & Regards,
>
> Ashesh Vashi
> EnterpriseDB INDIA: Enterprise PostgreSQL Company
> <http://www.enterprisedb.com/>
>
>
> *http://www.linkedin.com/in/asheshvashi*
> <http://www.linkedin.com/in/asheshvashi>
>
>>
>>
>> --
>> Dave Page
>> Blog: http://pgsnake.blogspot.com
>> Twitter: @pgsnake
>>
>> EnterpriseDB UK: http://www.enterprisedb.com
>> The Enterprise PostgreSQL Company
>>
>
>
diff --git a/TODO.txt b/TODO.txt
index 93abba0..1df1305 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -45,3 +45,11 @@ Graphical Explain
* Explanation on the statistic for the graphical explain plan.
* Arrow colouring based on the percentage of the cost, and time taken on each
explain node.
+
+User management
+---------------------------------------
+1. Pagination should be there if there are large number of users.
+2. There should be a way of notifying the user once its password is changed by any administrator.
+3. We can add 'created by' column.
+4. User creation and last modified time.
+5. If current user changes its own name/email, main page should reflect new changes.
\ No newline at end of file
diff --git a/web/config.py b/web/config.py
index 4dd6c76..8f043b2 100644
--- a/web/config.py
+++ b/web/config.py
@@ -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
diff --git a/web/pgadmin/static/css/overrides.css b/web/pgadmin/static/css/overrides.css
index 55d83e0..d746a10 100755
--- a/web/pgadmin/static/css/overrides.css
+++ b/web/pgadmin/static/css/overrides.css
@@ -1232,3 +1232,37 @@ form[name="change_password_form"] .help-block {
visibility: hidden;
}
}
+
+.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;
+ border: 1px solid #ccc;
+ width: calc(100% - 20px);
+ height: 100%;
+ overflow: hidden;
+}
+
+.user_management .search_users form {
+ margin: 0;
+}
+
+.user_management table {
+ display: block;
+ height: 100%;
+ overflow: auto;
+}
+
+.user_management .backform-tab {
+ height: calc(100% - 75px);
+}
\ No newline at end of file
diff --git a/web/pgadmin/tools/user_management/__init__.py b/web/pgadmin/tools/user_management/__init__.py
new file mode 100644
index 0000000..324ea4d
--- /dev/null
+++ b/web/pgadmin/tools/user_management/__init__.py
@@ -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 id."))
+
+ 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
+
+
[email protected]("/")
+@login_required
+def index():
+ return bad_request(errormsg=_("This URL can not be called directly!"))
+
+
[email protected]("/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("Administrators"),
+ user_id=current_user.id
+
+ ),
+ status=200,
+ mimetype="application/javascript"
+ )
+
+
[email protected]('/user/', methods=['GET'], defaults={'uid': None})
[email protected]('/user/<int:uid>', methods=['GET'])
+@roles_required('Administrators')
+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
+ )
+
+
[email protected]('/user/', methods=['POST'])
+@roles_required('Administrators')
+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
+ )
+
+
[email protected]('/user/<int:uid>', methods=['DELETE'])
+@roles_required('Administrators')
+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))
+
+
[email protected]('/user/<int:uid>', methods=['PUT'])
+@roles_required('Administrators')
+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))
+
+
[email protected]('/role/', methods=['GET'], defaults={'rid': None})
[email protected]('/role/<int:rid>', methods=['GET'])
+@roles_required('Administrators')
+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
+ )
diff --git a/web/pgadmin/tools/user_management/templates/user_management/js/user_management.js b/web/pgadmin/tools/user_management/templates/user_management/js/user_management.js
new file mode 100644
index 0000000..08c0fc5
--- /dev/null
+++ b/web/pgadmin/tools/user_management/templates/user_management/js/user_management.js
@@ -0,0 +1,596 @@
+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;
+ }
+
+ /**
+ Create new Filter which will filter the
+ rendered grid for Select Type Tabular Data
+ @param {Backbone.PageableCollection} coll
+ */
+ var userFilter = function(collection) {
+ return (new Backgrid.Extension.ClientSideFilter({
+ collection: collection,
+ placeholder: _('Search 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
+ }));
+ }
+
+ var BASEURL = '{{ url_for('user_management.index')}}',
+ USERURL = BASEURL + 'user/',
+ ROLEURL = BASEURL + 'role/';
+
+ pgBrowser.UserManagement = {
+ init: function() {
+ if (this.initialized)
+ return;
+
+ this.initialized = true;
+
+{% if is_admin %}
+ // Define the nodes on which the menus to be appear
+ var menus = [{
+ name: 'users', module: this,
+ applies: ['file'], callback: 'show_users',
+ priority: 2, label: '{{_("Users...") }}',
+ icon: 'fa fa-users'
+ }];
+
+ pgAdmin.Browser.add_menus(menus);
+{% endif %}
+
+ 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') }}', editable: true,
+ type: 'text', cell:'string', cellHeaderClasses:'width_percent_30'
+ },{
+ id: 'role', label: '{{ _('Role') }}',
+ type: 'text', control: "Select2",
+ cell: 'select2', select2: {allowClear: false},
+ cellHeaderClasses:'width_percent_20',
+ 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,
+ email_filter = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
+
+ if (_.isUndefined(this.get('email')) || _.isNull(this.get('email')) ||
+ String(this.get('email')).replace(/^\s+|\s+$/g, '') == '') {
+ errmsg = '{{ _('Email id cannot be empty.')}}';
+ this.errorModel.set('email', errmsg);
+ return errmsg;
+ } else if (!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.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 (_.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 ((_.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 (!_.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 ((_.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')) {
+
+ 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 (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'
+ }],
+ // 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
+ }
+ };
+ },
+ 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.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 = new UserCollection(),
+ header = [
+ '<div class="subnode-header-form">',
+ ' <div class="container-fluid">',
+ ' <div class="row">',
+ ' <div class="col-md-4 search_users"></div>',
+ ' <div class="col-md-4" ></div>',
+ ' <div class="col-md-4">',
+ ' <button class="fa fa-user-plus btn btn-primary pg-alertify-button add" title="<%-add_title%>" <%=canAdd ? "" : "disabled=\'disabled\'"%> ><%=add_label ? add_label : "" %></buttton>',
+ ' </div>',
+ ' </div>',
+ ' </div>',
+ '</div>',].join("\n"),
+ headerTpl = _.template(header),
+ data = {
+ canAdd: true,
+ add_title: '{{ _("Add new user")}}',
+ add_label:'{{ _('Add')}}'
+ },
+ $gridBody = $("<div></div>", {
+ class: "pgadmin-control-group backgrid form-group \
+ col-xs-12 object subnode backform-tab"
+ });
+
+ $.ajax({
+ url: ROLEURL,
+ method: 'GET',
+ async: false,
+ success: function(res) {
+ Roles = res
+ },
+ error: function(e) {
+ setTimeout(function() {
+ alertify.alert(
+ '{{ _('Cannot load pgadmin4 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'></div>").append(
+ headerTpl(data)).append($gridBody
+ ).append($footer);
+
+ 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) {
+ // Close any existing expanded row before adding new one.
+ _.each(view.body.rows, function(row){
+ var editCell = row.$el.find(".subnode-edit-in-process").parent();
+ // Only close row if it's open.
+ if (editCell.length > 0){
+ var event = new Event('click');
+ editCell[0].dispatchEvent(event);
+ }
+ });
+
+ // 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;
+ }
+ });
+ }
+ };
+ });
+ }
+ alertify.UserManagement(true).resizeTo('60%','60%');
+ }{% endif %}
+
+ };
+ return pgBrowser.UserManagement;
+ });
diff --git a/web/setup.py b/web/setup.py
index 185d02a..8ec1051 100644
--- a/web/setup.py
+++ b/web/setup.py
@@ -72,6 +72,11 @@ account:\n""")
name='Administrators',
description='pgAdmin Administrators Role'
)
+ user_datastore.create_role(
+ name='Standard',
+ description='pgAdmin Standard Role'
+ )
+
user_datastore.create_user(email=email, password=password)
db.session.flush()
user_datastore.add_role_to_user(email, 'Administrators')
@@ -249,6 +254,12 @@ CREATE TABLE process(
FOREIGN KEY(user_id) REFERENCES user (id)
)""")
+ if int(version.value) < 11:
+ db.engine.execute("""
+INSERT INTO role ( name, description )
+ VALUES ('Standard', 'pgAdmin Standard Role')
+ """)
+
# Finally, update the schema version
version.value = config.SETTINGS_SCHEMA_VERSION
db.session.merge(version)
--
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers