mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-10 23:45:58 -06:00
1238 lines
46 KiB
JavaScript
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;
|
|
});
|