pgadmin4/web/pgadmin/tools/user_management/static/js/user_management.js

1238 lines
46 KiB
JavaScript

/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
define([
'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'pgadmin.alertifyjs',
'pgadmin.browser', 'backbone', 'backgrid', 'backform', 'pgadmin.browser.node', 'pgadmin.backform',
'pgadmin.user_management.current_user', 'sources/utils',
'backgrid.select.all', 'backgrid.filter',
], function(
gettext, url_for, $, _, alertify, pgBrowser, Backbone, Backgrid, Backform,
pgNode, pgBackform, userInfo, commonUtils,
) {
// if module is already initialized, refer to that.
if (pgBrowser.UserManagement) {
return pgBrowser.UserManagement;
}
var USERURL = url_for('user_management.users'),
ROLEURL = url_for('user_management.roles'),
SOURCEURL = url_for('user_management.auth_sources'),
DEFAULT_AUTH_SOURCE = 'internal',
AUTH_ONLY_INTERNAL = (userInfo['auth_sources'].length == 1 && userInfo['auth_sources'].includes(DEFAULT_AUTH_SOURCE)) ? true : false,
userFilter = function(collection) {
return (new Backgrid.Extension.ClientSideFilter({
collection: collection,
placeholder: gettext('Filter by email'),
// How long to wait after typing has stopped before searching can start
wait: 150,
}));
};
// Integer Cell for Columns Length and Precision
var PasswordDepCell = Backgrid.Extension.PasswordDepCell =
Backgrid.Extension.PasswordCell.extend({
initialize: function() {
Backgrid.Extension.PasswordCell.prototype.initialize.apply(this, arguments);
Backgrid.Extension.DependentCell.prototype.initialize.apply(this, arguments);
},
dependentChanged: function () {
this.$el.empty();
this.render();
var model = this.model,
column = this.column,
editable = this.column.get('editable'),
is_editable = _.isFunction(editable) ? !!editable.apply(column, [model]) : !!editable;
if (is_editable){ this.$el.addClass('editable'); }
else { this.$el.removeClass('editable'); }
this.delegateEvents();
return this;
},
render: function() {
Backgrid.NumberCell.prototype.render.apply(this, arguments);
var model = this.model,
column = this.column,
editable = this.column.get('editable'),
is_editable = _.isFunction(editable) ? !!editable.apply(column, [model]) : !!editable;
if (is_editable){ this.$el.addClass('editable'); }
else { this.$el.removeClass('editable'); }
return this;
},
remove: Backgrid.Extension.DependentCell.prototype.remove,
});
pgBrowser.UserManagement = {
init: function() {
if (this.initialized)
return;
this.initialized = true;
return this;
},
// Callback to draw change password Dialog.
change_password: function(url) {
var title = gettext('Change Password');
if (!alertify.ChangePassword) {
alertify.dialog('ChangePassword', function factory() {
return {
main: function(alertTitle, alertUrl) {
this.set({
'title': alertTitle,
'url': alertUrl,
});
},
build: function() {
alertify.pgDialogBuild.apply(this);
},
settings: {
url: undefined,
},
setup: function() {
return {
buttons: [{
text: '',
key: 112,
className: 'btn btn-primary-icon pull-left fa fa-question pg-alertify-icon-button',
attrs: {
name: 'dialog_help',
type: 'button',
label: gettext('Change Password'),
url: url_for(
'help.static', {
'filename': 'change_user_password.html',
}),
},
}, {
text: gettext('Close'),
key: 27,
className: 'btn btn-secondary fa fa-lg fa-times pg-alertify-button',
attrs: {
name: 'close',
type: 'button',
},
}],
// Set options for dialog
options: {
//disable both padding and overflow control.
padding: !1,
overflow: !1,
modal: false,
resizable: true,
maximizable: true,
pinnable: false,
closableByDimmer: false,
closable: false,
},
};
},
hooks: {
// Triggered when the dialog is closed
onclose: function() {
// Clear the view
return setTimeout((function() {
return alertify.ChangePassword().destroy();
}), 500);
},
},
prepare: function() {
// create the iframe element
var iframe = document.createElement('iframe');
iframe.frameBorder = 'no';
iframe.width = '100%';
iframe.height = '100%';
iframe.src = this.setting('url');
// add it to the dialog
this.elements.content.appendChild(iframe);
},
callback: function(e) {
if (e.button.element.name == 'dialog_help') {
e.cancel = true;
pgBrowser.showHelp(e.button.element.name, e.button.element.getAttribute('url'),
null, null);
return;
}
},
};
});
}
alertify.ChangePassword(title, url).resizeTo(pgBrowser.stdW.lg, pgBrowser.stdH.md);
},
isPgaLoginRequired(xhr) {
/* If responseJSON is undefined then it could be object of
* axios(Promise HTTP) response, so we should check accordingly.
*/
if (xhr.responseJSON === undefined && xhr.data !== undefined) {
return xhr.status === 401 && xhr.data &&
xhr.data.info &&
xhr.data.info === 'PGADMIN_LOGIN_REQUIRED';
}
return xhr.status === 401 && xhr.responseJSON &&
xhr.responseJSON.info &&
xhr.responseJSON.info === 'PGADMIN_LOGIN_REQUIRED';
},
// Callback to draw pgAdmin4 login dialog.
pgaLogin: function(url) {
var title = gettext('pgAdmin 4 login');
url = url || url_for('security.login');
if(!alertify.PgaLogin) {
alertify.dialog('PgaLogin' ,function factory() {
return {
main: function(alertTitle, alertUrl) {
this.set({
'title': alertTitle,
'url': alertUrl,
});
},
build: function() {
alertify.pgDialogBuild.apply(this);
},
settings:{
url: undefined,
},
setup:function() {
return {
buttons: [{
text: gettext('Close'), key: 27,
className: 'btn btn-secondary fa fa-lg fa-times pg-alertify-button',
attrs:{name:'close', type:'button'},
}],
// Set options for dialog
options: {
//disable both padding and overflow control.
padding : !1,
overflow: !1,
modal: true,
resizable: true,
maximizable: true,
pinnable: false,
closableByDimmer: false,
closable: false,
},
};
},
hooks: {
// Triggered when the dialog is closed
onclose: function() {
// Clear the view
return setTimeout((function() {
return alertify.PgaLogin().destroy();
}));
},
},
prepare: function() {
// create the iframe element
var self = this,
iframe = document.createElement('iframe'),
frameUrl = this.setting('url');
iframe.onload = function() {
var doc = this.contentDocument || this.contentWindow.document;
if (doc.location.href.indexOf(frameUrl) == -1) {
// login successful.
this.contentWindow.stop();
this.onload = null;
// close the dialog.
self.close();
pgBrowser.Events.trigger('pgadmin:user:logged-in');
}
};
iframe.frameBorder = 'no';
iframe.width = '100%';
iframe.height = '100%';
iframe.src = frameUrl;
// add it to the dialog
self.elements.content.appendChild(iframe);
},
};
});
}
alertify.PgaLogin(title, url).resizeTo(pgBrowser.stdW.md, pgBrowser.stdH.md);
},
// Callback to draw User Management Dialog.
show_users: function() {
if (!userInfo['is_admin']) return;
var Roles = [],
Sources = [];
var UserModel = pgBrowser.Node.Model.extend({
idAttribute: 'id',
urlRoot: USERURL,
defaults: {
id: undefined,
username: undefined,
email: undefined,
active: true,
role: '2',
newPassword: undefined,
confirmPassword: undefined,
auth_source: DEFAULT_AUTH_SOURCE,
authOnlyInternal: AUTH_ONLY_INTERNAL,
},
schema: [{
id: 'auth_source',
label: gettext('Authentication source'),
type: 'text',
control: 'Select2',
url: url_for('user_management.auth_sources'),
cellHeaderClasses: 'width_percent_30',
visible: function(m) {
if (m.get('authOnlyInternal')) return false;
return true;
},
disabled: false,
cell: 'Select2',
select2: {
allowClear: false,
openOnEnter: false,
first_empty: false,
},
options: function() {
return Sources;
},
editable: function(m) {
if (m instanceof Backbone.Collection) {
return true;
}
return (m.isNew() && !m.get('authOnlyInternal'));
},
}, {
id: 'username',
label: gettext('Username'),
type: 'text',
cell: Backgrid.Extension.StringDepCell,
cellHeaderClasses: 'width_percent_30',
deps: ['auth_source'],
editable: function(m) {
if (m.get('authOnlyInternal') || m.get('auth_source') == DEFAULT_AUTH_SOURCE) {
if (m.isNew() && m.get('username') != undefined && m.get('username') != '') {
setTimeout( function() {
m.set('username', undefined);
}, 10);
}
return false;
}
return true;
},
disabled: false,
}, {
id: 'email',
label: gettext('Email'),
type: 'text',
cell: Backgrid.Extension.StringDepCell,
cellHeaderClasses: 'width_percent_30',
deps: ['id'],
editable: function(m) {
if (!m.get('authOnlyInternal')) return true;
if (m instanceof Backbone.Collection) {
return false;
}
// Disable email edit for existing user.
if (m.isNew()) {
return true;
}
return false;
},
}, {
id: 'role',
label: gettext('Role'),
type: 'text',
control: 'Select2',
cellHeaderClasses: 'width_percent_20',
cell: 'select2',
select2: {
allowClear: false,
openOnEnter: false,
first_empty: 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;
}
return (m.get('id') != userInfo['id']);
},
}, {
id: 'active',
label: gettext('Active'),
type: 'switch',
cell: 'switch',
cellHeaderClasses: 'width_percent_10',
sortable: false,
editable: function(m) {
if (m instanceof Backbone.Collection) {
return true;
}
return (m.get('id') != userInfo['id']);
},
}, {
id: 'newPassword',
label: gettext('New password'),
type: 'password',
disabled: false,
control: 'input',
cell: PasswordDepCell,
cellHeaderClasses: 'width_percent_20',
deps: ['auth_source'],
sortable: false,
editable: function(m) {
return (m.get('auth_source') == DEFAULT_AUTH_SOURCE);
},
}, {
id: 'confirmPassword',
label: gettext('Confirm password'),
type: 'password',
disabled: false,
control: 'input',
cell: PasswordDepCell,
cellHeaderClasses: 'width_percent_20',
deps: ['auth_source'],
sortable: false,
editable: function(m) {
return (m.get('auth_source') == DEFAULT_AUTH_SOURCE);
},
}],
validate: function() {
var errmsg = null,
changedAttrs = this.changed || {},
email_filter = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
if (this.get('auth_source') == DEFAULT_AUTH_SOURCE && ('email' in changedAttrs || !this.isNew()) && (_.isUndefined(this.get('email')) ||
_.isNull(this.get('email')) ||
String(this.get('email')).replace(/^\s+|\s+$/g, '') == '')) {
errmsg = gettext('Email address cannot be empty.');
this.errorModel.set('email', errmsg);
return errmsg;
} else if (!!this.get('email') && !email_filter.test(this.get('email'))) {
errmsg = gettext('Invalid email address: %s.',
this.get('email')
);
this.errorModel.set('email', errmsg);
return errmsg;
} else if (!!this.get('email') && this.collection.nonFilter.where({
'email': this.get('email'), 'auth_source': DEFAULT_AUTH_SOURCE,
}).length > 1) {
errmsg = gettext('The email address %s already exists.',
this.get('email')
);
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 = gettext('Role cannot be empty for user %s.',
(this.get('email') || '')
);
this.errorModel.set('role', errmsg);
return errmsg;
} else {
this.errorModel.unset('role');
}
if (this.get('auth_source') == DEFAULT_AUTH_SOURCE) {
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 = gettext('Password cannot be empty for user %s.',
(this.get('email') || '')
);
this.errorModel.set('newPassword', errmsg);
return errmsg;
} else if (!_.isUndefined(this.get('newPassword')) &&
!_.isNull(this.get('newPassword')) &&
this.get('newPassword').length < 6) {
errmsg = gettext('Password must be at least 6 characters for user %s.',
(this.get('email') || '')
);
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 = gettext('Confirm Password cannot be empty for user %s.',
(this.get('email') || '')
);
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 = gettext('Passwords do not match for user %s.',
(this.get('email') || '')
);
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 = gettext('Password must be at least 6 characters for user %s.',
(this.get('email') || '')
);
this.errorModel.set('newPassword', errmsg);
return errmsg;
} else if (_.isUndefined(this.get('confirmPassword')) ||
_.isNull(this.get('confirmPassword')) ||
this.get('confirmPassword') == '') {
errmsg = gettext('Confirm Password cannot be empty for user %s.',
(this.get('email') || '')
);
this.errorModel.set('confirmPassword', errmsg);
return errmsg;
} else if (!!this.get('newPassword') && !!this.get('confirmPassword') &&
this.get('newPassword') != this.get('confirmPassword')) {
errmsg = gettext('Passwords do not match for user %s.',
(this.get('email') || '')
);
this.errorModel.set('confirmPassword', errmsg);
return errmsg;
} else {
this.errorModel.unset('newPassword');
this.errorModel.unset('confirmPassword');
}
}
} else {
if (!!this.get('username') && this.collection.nonFilter.where({
'username': this.get('username'), 'auth_source': 'ldap',
}).length > 1) {
errmsg = gettext('The username %s already exists.',
this.get('username')
);
this.errorModel.set('username', errmsg);
return errmsg;
}
}
return null;
},
}),
gridSchema = Backform.generateGridColumnsFromModel(
null, UserModel, 'edit'),
deleteUserCell = Backgrid.Extension.DeleteCell.extend({
changeOwnership: function(res, uid) {
let self = this;
let ownershipSelect2Control = Backform.Select2Control.extend({
fetchData: function(){
let self = this;
let url = self.field.get('url');
url = url_for(url, {'uid': uid});
$.ajax({
url: url,
headers: {
'Cache-Control' : 'no-cache',
},
}).done(function (res) {
var transform = self.field.get('transform');
if(res.data.status){
let data = res.data.result.data;
if (transform && _.isFunction(transform)) {
self.field.set('options', transform.bind(self, data));
} else {
self.field.set('options', data);
}
} else {
if (transform && _.isFunction(transform)) {
self.field.set('options', transform.bind(self, []));
} else {
self.field.set('options', []);
}
}
Backform.Select2Control.prototype.render.apply(self, arguments);
}).fail(function(e){
let msg = '';
if(e.status == 404) {
msg = 'Unable to find url.';
} else {
msg = e.responseJSON.errormsg;
}
alertify.error(msg);
});
},
render: function() {
this.fetchData();
return Backform.Select2Control.prototype.render.apply(this, arguments);
},
onChange: function() {
Backform.Select2Control.prototype.onChange.apply(this, arguments);
},
});
let ownershipModel = pgBrowser.DataModel.extend({
schema: [
{
id: 'note_text_ch_owner',
control: Backform.NoteControl,
text: 'Select the user that will take ownership of the shared servers created by <b>' + self.model.get('username') + '</b>. <b>' + res['data'].shared_servers + '</b> shared servers are currently owned by this user.',
group: gettext('General'),
},
{
id: 'user',
name: 'user',
label: gettext('User'),
type: 'text',
editable: true,
select2: {
allowClear: true,
width: '100%',
first_empty: true,
},
control: ownershipSelect2Control,
url: 'user_management.admin_users',
helpMessage: gettext('Note: If no user is selected, the shared servers will be deleted.'),
}],
});
// Change shared server ownership before deleting the admin user
if (!alertify.changeOwnershipDialog) {
alertify.dialog('changeOwnershipDialog', function factory() {
let $container = $('<div class=\'change-ownership\'></div>');
return {
main: function(message) {
this.msg = message;
},
build: function() {
this.elements.content.appendChild($container.get(0));
alertify.pgDialogBuild.apply(this);
},
setup: function(){
return {
buttons: [
{
text: gettext('Cancel'),
key: 27,
className: 'btn btn-secondary fa fa-times pg-alertify-button',
'data-btn-name': 'cancel',
}, {
text: gettext('OK'),
key: 13,
className: 'btn btn-primary fa fa-check pg-alertify-button',
'data-btn-name': 'ok',
},
],
// Set options for dialog
options: {
title: 'Change ownership',
//disable both padding and overflow control.
padding: !1,
overflow: !1,
model: 0,
resizable: true,
maximizable: false,
pinnable: false,
closableByDimmer: false,
modal: false,
autoReset: false,
closable: true,
},
};
},
prepare: function() {
let self = this;
$container.html('');
self.ownershipModel = new ownershipModel();
let fields = pgBackform.generateViewSchema(null, self.ownershipModel, 'create', null, null, true, null);
let view = this.view = new pgBackform.Dialog({
el: '<div></div>',
model: self.ownershipModel,
schema: fields,
});
//Render change ownership dialog.
$container.append(view.render().$el[0]);
},
callback: function(e) {
if(e.button['data-btn-name'] === 'ok') {
e.cancel = true; // Do not close dialog
let ownershipModel = this.ownershipModel.toJSON();
if (ownershipModel.user == '' || ownershipModel.user == undefined) {
alertify.confirm(
gettext('Delete user?'),
gettext('The shared servers owned by <b>'+ self.model.get('username') +'</b> will be deleted. Do you wish to continue?'),
function() {
self.model.destroy({
wait: true,
success: function() {
alertify.success(gettext('User deleted.'));
alertify.changeOwnershipDialog().destroy();
alertify.UserManagement().destroy();
},
error: function() {
alertify.error(
gettext('Error during deleting user.')
);
},
});
alertify.changeOwnershipDialog().destroy();
},
function() {
return true;
}
);
} else {
self.changeOwner(ownershipModel.user, uid);
}
} else {
alertify.changeOwnershipDialog().destroy();
}
},
};
});
}
alertify.changeOwnershipDialog('Change ownership').resizeTo(pgBrowser.stdW.md, pgBrowser.stdH.md);
},
changeOwner: function(user_id, old_user) {
$.ajax({
url: url_for('user_management.change_owner'),
method: 'POST',
data:{'new_owner': user_id, 'old_owner': old_user},
})
.done(function(res) {
alertify.changeOwnershipDialog().destroy();
alertify.UserManagement().destroy();
alertify.success(gettext(res.info));
})
.fail(function() {
alertify.error(gettext('Unable to change owner.'));
});
},
deleteUser: function() {
let self = this;
alertify.confirm(
gettext('Delete user?'),
gettext('Are you sure you wish to delete this user?'),
function() {
self.model.destroy({
wait: true,
success: function() {
alertify.success(gettext('User deleted.'));
},
error: function() {
alertify.error(
gettext('Error during deleting user.')
);
},
});
},
function() {
return true;
}
);
},
deleteRow: function(e) {
var self = this;
e.preventDefault();
if (self.model.get('id') == userInfo['id']) {
alertify.alert(
gettext('Cannot delete user.'),
gettext('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 {
if(self.model.get('role') == 1){
$.ajax({
url: url_for('user_management.shared_servers', {'uid': self.model.get('id'),
}),
method: 'GET',
async: false,
})
.done(function(res) {
if(res['data'].shared_servers > 0) {
self.changeOwnership(res, self.model.get('id'));
} else {
self.deleteUser();
}
})
.fail(function() {
self.deleteUser();
});
} else {
self.deleteUser();
}
}
} else {
alertify.alert(
gettext('Error'),
gettext('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);
},
build: function() {
alertify.pgDialogBuild.apply(this);
},
setup: function() {
return {
buttons: [{
text: '',
key: 112,
className: 'btn btn-primary-icon pull-left fa fa-question pg-alertify-icon-button',
attrs: {
name: 'dialog_help',
type: 'button',
label: gettext('Users'),
url: url_for(
'help.static', {
'filename': 'user_management.html',
}),
},
}, {
text: gettext('Close'),
key: 27,
className: 'btn btn-secondary fa fa-lg fa-times pg-alertify-button user_management_pg-alertify-button',
attrs: {
name: 'close',
type: 'button',
},
}],
// Set options for dialog
options: {
title: gettext('User Management'),
//disable both padding and overflow control.
padding: !1,
overflow: !1,
modal: false,
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 footerTpl = _.template([
'<div class="pg-prop-footer" style="visibility:hidden;">',
' <div class="pg-prop-status-bar">',
' <div class="error-in-footer"> ',
' <div class="d-flex px-2 py-1"> ',
' <div class="pr-2"> ',
' <i class="fa fa-exclamation-triangle text-danger" aria-hidden="true"></i> ',
' </div> ',
' <div class="alert-text" role="status"></div> ',
' <div class="ml-auto close-error-bar"> ',
' <a class="close-error fa fa-times text-danger"></a> ',
' </div> ',
' </div> ',
' </div> ',
' </div>',
'</div>',
].join('\n')),
$statusBar = $(footerTpl()),
UserRow = Backgrid.Row.extend({
userInvalidClass: 'bg-user-invalid',
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).addClass(this.userInvalidClass);
},
userValid: function() {
$(this.el).removeClass(this.userInvalidClass);
},
}),
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.nonFilter = this;
self.on('add', self.onModelAdd);
self.on('remove', self.onModelRemove);
self.on('pgadmin-session:model:invalid', function(msg, m) {
self.invalidUsers[m.cid] = msg;
m.trigger('pgadmin:user:invalid', m);
$statusBar.find('.alert-text').html(msg);
$statusBar.css('visibility', 'visible');
});
self.on('pgadmin-session:model:valid', function(m) {
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.find('.alert-text').html(msg);
$statusBar.css('visibility', 'visible');
} else {
$statusBar.find('.alert-text').empty();
$statusBar.css('visibility', 'hidden');
}
},
saveUser: function(m) {
var d = m.toJSON(true);
if((m.isNew() && m.get('auth_source') == 'ldap' && (!m.get('username') || !m.get('auth_source') || !m.get('role')))
|| (m.isNew() && m.get('auth_source') == DEFAULT_AUTH_SOURCE && (!m.get('email') || !m.get('role') ||
!m.get('newPassword') || !m.get('confirmPassword') || m.get('newPassword') != m.get('confirmPassword')))
|| (!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() {
// User created/updated on server now start new session for this user.
let temp_auth_sources = m.get('auth_source');
m.set({
'newPassword': undefined,
'confirmPassword': undefined,
'auth_source': undefined,
});
// It's a heck to re-render the Auth Source control.
m.set({
'auth_source': temp_auth_sources,
});
m.startNewSession();
alertify.success(gettext('User \'%s\' saved.',
m.get('username')
));
},
error: function(res, jqxhr) {
m.startNewSession();
alertify.error(
gettext('Error saving user: \'%s\'',
jqxhr.responseJSON.errormsg
)
);
},
});
}
},
}),
userCollection = this.userCollection = new UserCollection(),
header =
`<div class="navtab-inline-controls pgadmin-controls">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text fa fa-search" id="labelSearch"></span>
</div>
<input type="search" class="form-control" id="txtGridSearch" placeholder="` + gettext('Search') + '" aria-label="' + gettext('Search') + `" aria-describedby="labelSearch" />
</div>
<button id="btn_add" type="button" class="btn btn-secondary btn-navtab-inline add" title="` + gettext('Add') + `">
<span class="fa fa-plus "></span>
</button>
</div>
</div>
</div>`,
headerTpl = _.template(header),
data = {
canAdd: true,
add_title: gettext('Add new user'),
},
$gridBody = $('<div></div>', {
class: 'user_container flex-grow-1',
});
$.ajax({
url: ROLEURL,
method: 'GET',
async: false,
})
.done(function(res) {
Roles = res;
})
.fail(function() {
setTimeout(function() {
alertify.alert(
gettext('Error'),
gettext('Cannot load user roles.')
);
}, 100);
});
$.ajax({
url: SOURCEURL,
method: 'GET',
async: false,
})
.done(function(res) {
Sources = res;
})
.fail(function() {
setTimeout(function() {
alertify.alert(
gettext('Error'),
gettext('Cannot load user Sources.')
);
}, 100);
});
var view = this.view = new Backgrid.Grid({
row: UserRow,
columns: gridSchema.columns,
collection: userCollection,
className: 'backgrid table table-bordered table-noouter-border table-bottom-border table-hover',
});
$gridBody.append(view.render().$el[0]);
this.$content = $('<div class=\'user_management object subnode subnode-noouter-border d-flex flex-column\'></div>').append(
headerTpl(data)).append($gridBody).append($statusBar);
this.elements.content.appendChild(this.$content[0]);
// Render Search Filter
userCollection.nonFilter = userFilter(userCollection).setCustomSearchBox($('#txtGridSearch')).shadowCollection;
userCollection.fetch();
this.$content.find('a.close-error').on('click',() => {
$statusBar.find('.alert-text').empty();
$statusBar.css('visibility', 'hidden');
});
this.$content.find('button.add').first().on('click',(e) => {
e.preventDefault();
// There should be only one empty row.
let anyNew = false;
for(const [idx, model] of userCollection.models.entries()) {
if(model.isNew()) {
let row = view.body.rows[idx].$el;
row.addClass('new');
$(row).pgMakeVisible('backgrid');
$(row).find('.email').trigger('click');
anyNew = true;
}
}
if(!anyNew) {
$(view.body.$el.find($('tr.new'))).removeClass('new');
var m = new(UserModel)(null, {
handler: userCollection,
top: userCollection,
collection: userCollection,
});
userCollection.add(m);
var newRow = view.body.rows[userCollection.indexOf(m)].$el;
newRow.addClass('new');
$(newRow).pgMakeVisible('backgrid');
$(newRow).find('.email').trigger('click');
}
return false;
});
commonUtils.findAndSetFocus(this.$content);
},
callback: function(e) {
if (e.button.element.name == 'dialog_help') {
e.cancel = true;
pgBrowser.showHelp(e.button.element.name, e.button.element.getAttribute('url'),
null, null);
return;
}
if (e.button.element.name == 'close') {
var self = this;
if (!_.all(this.userCollection.pluck('id')) || !_.isEmpty(this.userCollection.invalidUsers)) {
e.cancel = true;
alertify.confirm(
gettext('Discard unsaved changes?'),
gettext('Are you sure you want to close the dialog? Any unsaved changes will be lost.'),
function() {
self.close();
return true;
},
function() {
// Do nothing.
return true;
}
);
}
}
},
};
});
}
alertify.UserManagement(true).resizeTo(pgBrowser.stdW.md, pgBrowser.stdH.md);
},
};
return pgBrowser.UserManagement;
});