Remove Backgrid and Backform. Fixes #6134

This commit is contained in:
Khushboo Vashi
2022-09-10 14:22:49 +05:30
committed by Akshay Joshi
parent b128ba2f57
commit ca8b5c68fd
50 changed files with 77 additions and 14601 deletions

View File

@@ -14,7 +14,6 @@ import ForeignServerSchema from './foreign_server.ui';
define('pgadmin.node.foreign_server', [
'sources/gettext', 'sources/url_for', 'sources/pgadmin',
'pgadmin.browser', 'pgadmin.browser.collection',
'pgadmin.browser.server.privilege',
], function(gettext, url_for, pgAdmin, pgBrowser) {
// Extend the browser's collection class for foreign server collection

View File

@@ -14,8 +14,8 @@ import _ from 'lodash';
define('pgadmin.node.user_mapping', [
'sources/gettext', 'sources/url_for',
'sources/pgadmin', 'pgadmin.browser',
'pgadmin.backform', 'pgadmin.browser.collection',
], function(gettext, url_for, pgAdmin, pgBrowser, Backform) {
'pgadmin.browser.collection',
], function(gettext, url_for, pgAdmin, pgBrowser) {
// Extend the browser's collection class for user mapping collection
if (!pgBrowser.Nodes['coll-user_mapping']) {
@@ -105,34 +105,6 @@ define('pgadmin.node.user_mapping', [
}
pgAdmin.Browser.Node.Model.prototype.initialize.apply(this, arguments);
},
// Defining schema for the user mapping node
schema: [
{
id: 'name', label: gettext('User'), type: 'text',
control: Backform.NodeListByNameControl, node: 'role',
mode: ['edit', 'create', 'properties'], select2: { allowClear: false },
disabled: function(m) { return !m.isNew(); },
transform: function() {
let self = this,
node = self.field.get('schema_node');
let res =
Backform.NodeListByNameControl.prototype.defaults.transform.apply(
this, arguments
);
res.unshift({
label: 'CURRENT_USER', value: 'CURRENT_USER',
image: 'icon-' + node.type,
},{
label: 'PUBLIC', value: 'PUBLIC', image: 'icon-' + node.type,
});
return res;
},
}, {
id: 'oid', label: gettext('OID'), cell: 'string',
type: 'text', mode: ['properties'],
}
],
}),
});

View File

@@ -13,7 +13,7 @@ import ForeignDataWrapperSchema from './foreign_data_wrapper.ui';
define('pgadmin.node.foreign_data_wrapper', [
'sources/gettext', 'sources/url_for', 'pgadmin.browser',
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege',
'pgadmin.browser.collection',
], function(gettext, url_for, pgBrowser) {
// Extend the browser's collection class for foreign data wrapper collection

View File

@@ -13,7 +13,7 @@ import { getNodePrivilegeRoleSchema } from '../../../../static/js/privilege.ui';
define('pgadmin.node.language', [
'sources/gettext', 'sources/url_for', 'pgadmin.browser',
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege',
'pgadmin.browser.collection',
], function(gettext, url_for, pgBrowser) {
// Extend the browser's collection class for languages collection

View File

@@ -12,7 +12,7 @@ import PublicationSchema from './publication.ui';
define('pgadmin.node.publication', [
'sources/gettext', 'sources/url_for', 'pgadmin.browser',
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege',
'pgadmin.browser.collection',
], function(gettext, url_for, pgBrowser) {
// Extend the browser's collection class for publications collection

View File

@@ -16,7 +16,7 @@ import { getNodeVariableSchema } from '../../../../../static/js/variable.ui';
define('pgadmin.node.function', [
'sources/gettext', 'sources/url_for', 'pgadmin.browser',
'pgadmin.node.schema.dir/child', 'pgadmin.node.schema.dir/schema_child_tree_node',
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege',
'pgadmin.browser.collection',
], function(
gettext, url_for, pgBrowser, schemaChild, schemaChildTreeNode
) {

View File

@@ -17,7 +17,7 @@ define('pgadmin.node.procedure', [
'sources/gettext', 'sources/url_for',
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.node.schema.dir/child',
'pgadmin.node.schema.dir/schema_child_tree_node',
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege',
'pgadmin.browser.collection',
], function(gettext, url_for, pgAdmin, pgBrowser, schemaChild, schemaChildTreeNode) {
if (!pgBrowser.Nodes['coll-procedure']) {

View File

@@ -16,7 +16,7 @@ import _ from 'lodash';
define('pgadmin.node.trigger_function', [
'sources/gettext', 'sources/url_for', 'pgadmin.browser',
'pgadmin.node.schema.dir/child', 'pgadmin.node.schema.dir/schema_child_tree_node',
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege',
'pgadmin.browser.collection',
], function(
gettext, url_for, pgBrowser, schemaChild, schemaChildTreeNode
) {

View File

@@ -12,7 +12,7 @@ import EDBFuncSchema from './edbfunc.ui';
/* Create and Register Function Collection and Node. */
define('pgadmin.node.edbfunc', [
'sources/gettext', 'sources/url_for', 'pgadmin.browser',
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege',
'pgadmin.browser.collection',
], function(gettext, url_for, pgBrowser) {
if (!pgBrowser.Nodes['coll-edbfunc']) {

View File

@@ -15,7 +15,6 @@ define('pgadmin.node.edbproc', [
'sources/gettext', 'sources/url_for',
'sources/pgadmin', 'pgadmin.browser',
'pgadmin.node.edbfunc', 'pgadmin.browser.collection',
'pgadmin.browser.server.privilege',
], function(
gettext, url_for, pgAdmin, pgBrowser, EdbFunction
) {

View File

@@ -12,7 +12,7 @@ import EDBVarSchema from './edbvar.ui';
/* Create and Register Function Collection and Node. */
define('pgadmin.node.edbvar', [
'sources/gettext', 'sources/url_for', 'pgadmin.browser',
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege',
'pgadmin.browser.collection',
], function(gettext, url_for, pgBrowser) {
if (!pgBrowser.Nodes['coll-edbvar']) {

View File

@@ -10,297 +10,11 @@
import PGSchema from './schema.ui';
import { getNodePrivilegeRoleSchema } from '../../../../static/js/privilege.ui';
import { getNodeListByName } from '../../../../../../static/js/node_ajax';
import _ from 'lodash';
define('pgadmin.node.schema', [
'sources/gettext', 'sources/url_for', 'jquery',
'pgadmin.browser', 'pgadmin.backform', 'pgadmin.backgrid',
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege',
], function(gettext, url_for, $, pgBrowser, Backform, Backgrid) {
// VacuumSettings Collection to display all settings parameters as Grid
Backform.VacuumCollectionControl =
Backform.Control.extend({
grid_columns:undefined,
initialize: function() {
Backform.Control.prototype.initialize.apply(this, arguments);
let self = this,
m = this.model,
url = self.field.get('url');
if (url && m.isNew()) {
let node = self.field.get('node'),
node_data = self.field.get('node_data'),
node_info = self.field.get('node_info'),
full_url = node.generate_url.apply(
node, [
null, url, node_data, false, node_info,
]),
data;
m.trigger('pgadmin-view:fetching', m, self.field);
// fetch default values for autovacuum fields
$.ajax({
async: false,
url: full_url,
})
.done(function (res) {
data = res;
})
.fail(function() {
m.trigger('pgadmin-view:fetch:error', m, self.field);
});
m.trigger('pgadmin-view:fetched', m, self.field);
// Add fetched models into collection
if (data && _.isArray(data)) {
m.get(self.field.get('name')).reset(data, {silent: true});
}
}
},
events : {
'keydown': 'keyDownHandler',
},
keyDownHandler : function(event){
// move the focus to editable input
let lastButton = $(this.$el).find('button:last');
let firstEditableCell = $(this.$el).find('td.editable:first');
if (event.keyCode== 9 && !event.shiftKey){
if ($(firstEditableCell).is(':visible')
&& ($(event.target)).is($(lastButton))){
$(firstEditableCell).trigger('click');
event.preventDefault();
event.stopPropagation();
}
}
},
render: function() {
let self = this,
attributes = self.field.attributes;
// remove grid
if(self.grid) {
self.grid.remove();
delete self.grid;
self.grid = undefined;
}
self.$el.empty();
let gridHeader = _.template([
'<div class="subnode-header">',
'<% if (label && label != "") %> {',
' <span class="control-label col-sm-4"><%-label%></span>',
'}',
'</div>'].join('\n')),
gridBody = $('<div class="pgadmin-control-group backgrid form-group col-12 object subnode"></div>').append(
gridHeader(attributes)
);
// Initialize a new Grid instance
let grid = self.grid = new Backgrid.Grid({
columns: self.grid_columns,
collection: self.model.get(self.field.get('name')),
className: 'backgrid table table-bordered table-noouter-border table-hover',
});
// render grid
self.$el.addClass('mb-0');
self.$el.append($(gridBody).append(grid.render().$el));
return self;
},
});
// We will use this function in VacuumSettings Control
// to convert data type on the fly
Backform.cellFunction = function(model) {
let vartype = model.get('column_type');
switch(vartype) {
case 'integer':
return Backgrid.IntegerCell;
case 'number':
return Backgrid.NumberCell.extend({
decimals: this.get('decimals') || Backgrid.NumberFormatter.prototype.defaults.decimals,
});
case 'string':
return Backgrid.StringCell;
default:
return Backgrid.Cell;
}
};
// Define Security Model with fields and validation for VacuumSettings Control
Backform.VacuumTableModel = pgBrowser.Node.Model.extend({
defaults: {
name: undefined,
setting: undefined,
label:undefined,
value: undefined,
column_type: undefined,
},
toJSON: function(){
let d = pgBrowser.Node.Model.prototype.toJSON.apply(this);
delete d.label;
delete d.setting;
delete d.column_type;
return d;
},
});
/* As Backform.VacuumSettingsSchema is commonly used in table & partition, so keeping it as it is for now.
this and other supporting model can be removed after their migration to react. */
// Extend the browser's collection class for VacuumSettingsModel
Backform.VacuumSettingsSchema = [{
id: 'spacer_ctrl', group: gettext('Table'), mode: ['edit', 'create'], type: 'spacer',
},{
id: 'autovacuum_custom', label: gettext('Custom auto-vacuum?'),
group: gettext('Table'), mode: ['edit', 'create'],
type: 'switch', controlLabelClassName: 'control-label pg-el-sm-4 pg-el-12',
controlsClassName: 'pgadmin-controls pg-el-sm-8 pg-el-12',
disabled: function(m) {
// If table is partitioned table then disabled it.
if (m.top && m.top.get('is_partitioned')) {
// We also need to unset rest of all
setTimeout(function() {
m.set('autovacuum_custom', false);
}, 10);
return true;
}
return m.top.inSchema.apply(this, [m]);
},
},{
id: 'autovacuum_enabled', label: gettext('Autovacuum Enabled?'),
group: gettext('Table'), mode: ['edit', 'create'],
type: 'radioModern', controlLabelClassName: 'control-label pg-el-sm-4 pg-el-12',
controlsClassName: 'pgadmin-controls pg-el-sm-8 pg-el-12',
options: [
{'label': gettext('Not set'), 'value': 'x'},
{'label': gettext('Yes'), 'value': 't'},
{'label': gettext('No'), 'value': 'f'},
],
deps: ['autovacuum_custom'],
disabled: function(m) {
if(!m.top.inSchema.apply(this, [m]) && m.get('autovacuum_custom')) {
return false;
}
// We also need to unset rest of all
setTimeout(function() {
m.set('autovacuum_enabled', 'x');
}, 10);
return true;
},
},{
id: 'vacuum_table', label: '',
model: Backform.VacuumTableModel, editable: false, type: 'collection',
canEdit: true, group: gettext('Table'),
mode: ['edit', 'create'], url: 'get_table_vacuum',
control: Backform.VacuumCollectionControl.extend({
grid_columns :[
{
name: 'label', label: gettext('Label'),
headerCell: Backgrid.Extension.CustomHeaderCell,
cell: 'string', editable: false, cellHeaderClasses:'width_percent_40',
},
{
name: 'value', label: gettext('Value'),
cellHeaderClasses:'width_percent_30',
decimals: 5,
cellFunction: Backform.cellFunction, editable: function(m) {
return m.handler.get('autovacuum_custom');
}, headerCell: Backgrid.Extension.CustomHeaderCell,
},
{
name: 'setting', label: gettext('Default'),
cellHeaderClasses:'width_percent_30',
headerCell: Backgrid.Extension.CustomHeaderCell,
cellFunction: Backform.cellFunction, editable: false,
},
],
}),
deps: ['autovacuum_custom'],
},{
id: 'spacer_ctrl', group: gettext('TOAST table'), mode: ['edit', 'create'], type: 'spacer',
},{
id: 'toast_autovacuum', label: gettext('Custom auto-vacuum?'),
group: gettext('TOAST table'), mode: ['edit', 'create'],
type: 'switch', controlLabelClassName: 'control-label pg-el-sm-4 pg-el-12',
controlsClassName: 'pgadmin-controls pg-el-sm-8 pg-el-12',
disabled: function(m) {
// We need to check additional condition to toggle enable/disable
// for table auto-vacuum
return !(!m.top.inSchema.apply(this, [m]) &&
(m.isNew() || (m.get('toast_autovacuum_enabled') === true || m.top.get('hastoasttable') === true)));
},
},{
id: 'toast_autovacuum_enabled', label: gettext('Autovacuum Enabled?'),
group: gettext('TOAST table'), mode: ['edit', 'create'],
type: 'radioModern', controlLabelClassName: 'control-label pg-el-sm-4 pg-el-12',
controlsClassName: 'pgadmin-controls pg-el-sm-8 pg-el-12',
options: [
{'label': gettext('Not set'), 'value': 'x'},
{'label': gettext('Yes'), 'value': 't'},
{'label': gettext('No'), 'value': 'f'},
],
deps:['toast_autovacuum'],
disabled: function(m) {
// If in schema & in create mode then enable it
if(!m.top.inSchema.apply(this, [m]) &&
m.get('toast_autovacuum') === true) {
return false;
}
if (m.isNew() || m.get('hastoasttable')) {
// we also need to unset rest of all
setTimeout(function() {
m.set('toast_autovacuum_enabled', 'x');
}, 10);
}
return true;
},
},{
id: 'vacuum_toast', label: '',
model: Backform.VacuumTableModel, type: 'collection', editable: function(m) {
return m.isNew();
},
canEdit: true, group: gettext('TOAST table'),
mode: ['properties', 'edit', 'create'], url: 'get_toast_table_vacuum',
control: Backform.VacuumCollectionControl.extend({
grid_columns :[
{
name: 'label', label: gettext('Label'),
headerCell: Backgrid.Extension.CustomHeaderCell,
cell: 'string', editable: false, cellHeaderClasses:'width_percent_40',
},
{
name: 'value', label: gettext('Value'),
cellHeaderClasses:'width_percent_30',
headerCell: Backgrid.Extension.CustomHeaderCell,
decimals: 5,
cellFunction: Backform.cellFunction, editable: function(m) {
return m.handler.get('toast_autovacuum');
},
},
{
name: 'setting', label: gettext('Default'),
cellHeaderClasses:'width_percent_30',
headerCell: Backgrid.Extension.CustomHeaderCell,
cellFunction: Backform.cellFunction, editable: false,
},
],
}),
deps: ['toast_autovacuum'],
}];
'pgadmin.browser', 'pgadmin.browser.collection',
], function(gettext, url_for, $, pgBrowser) {
// Extend the browser's collection class for schema collection
if (!pgBrowser.Nodes['coll-schema']) {
@@ -376,38 +90,5 @@ define('pgadmin.node.schema', [
};
}
/* As TableChildSwitchCell is commonly used in index, column & TableDialog, so keeping it as it is for now.
this and other supporting model can be removed after their migration to react. */
// Switch Cell with Deps (specifically for table children)
Backgrid.Extension.TableChildSwitchCell = Backgrid.Extension.SwitchCell.extend({
initialize: function() {
Backgrid.Extension.SwitchCell.prototype.initialize.apply(this, arguments);
Backgrid.Extension.DependentCell.prototype.initialize.apply(this, arguments);
},
dependentChanged: function () {
let model = this.model,
column = this.column,
editable = this.column.get('editable'),
input = this.$el.find('input[type=checkbox]').first(),
self_name = column.get('name'),
is_editable;
is_editable = _.isFunction(editable) ? !!editable.apply(column, [model]) : !!editable;
if (is_editable) {
this.$el.addClass('editable');
input.bootstrapToggle('disabled',false);
} else {
this.$el.removeClass('editable');
input.bootstrapToggle('disabled',true);
// Set self value into model to false
setTimeout(function() { model.set(self_name, false); }, 10);
}
this.delegateEvents();
return this;
},
remove: Backgrid.Extension.DependentCell.prototype.remove,
});
return pgBrowser.Nodes['schema'];
});

View File

@@ -13,9 +13,9 @@ import _ from 'lodash';
define('pgadmin.node.primary_key', [
'sources/gettext', 'sources/url_for',
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform', 'pgadmin.backgrid',
'sources/pgadmin', 'pgadmin.browser',
'pgadmin.browser.collection',
], function(gettext, url_for, pgAdmin, pgBrowser, Backform, Backgrid) {
], function(gettext, url_for, pgAdmin, pgBrowser) {
// Extend the browser's node class for index constraint node
if (!pgBrowser.Nodes['primary_key']) {
@@ -166,447 +166,6 @@ define('pgadmin.node.primary_key', [
// We can't update columns of existing index constraint.
return !m.isNew();
},
// Define the schema for the index constraint node
schema: [{
id: 'name', label: gettext('Name'), type: 'text',
mode: ['properties', 'create', 'edit'], editable:true,
cellHeaderClasses:'width_percent_40',
},{
id: 'oid', label: gettext('OID'), cell: 'string',
type: 'text' , mode: ['properties'], editable: false,
cellHeaderClasses:'width_percent_20',
},{
id: 'is_sys_obj', label: gettext('System primary key?'),
cell:'boolean', type: 'switch', mode: ['properties'],
},{
id: 'comment', label: gettext('Comment'), cell: 'string',
type: 'multiline', mode: ['properties', 'create', 'edit'],
deps:['name'], disabled:function(m) {
let name = m.get('name');
if (!(name && name != '')) {
setTimeout(function(){
if(m.get('comment') && m.get('comment') !== '') {
m.set('comment', null);
}
},10);
return true;
} else {
return false;
}
},
},{
id: 'columns', label: gettext('Columns'),
type: 'collection', group: gettext('Definition'),
editable: false,
cell: Backgrid.StringCell.extend({
initialize: function() {
Backgrid.StringCell.prototype.initialize.apply(this, arguments);
let self = this,
collection = this.model.get('columns');
// Do not listen for any event(s) for existing constraint.
if (_.isUndefined(self.model.get('oid'))) {
let tableCols = self.model.top.get('columns');
self.listenTo(tableCols, 'remove' , self.removeColumn);
self.listenTo(tableCols, 'change:name', self.resetColOptions);
}
collection.on('pgadmin:multicolumn:updated', function() {
self.render.apply(self);
});
self.listenTo(collection, 'add', self.render);
self.listenTo(collection, 'remove', self.render);
},
removeColumn: function(m) {
let self = this,
removedCols = self.model.get('columns').where(
{column: m.get('name')}
);
self.model.get('columns').remove(removedCols);
setTimeout(function () {
self.render();
}, 10);
let key = 'primary_key';
setTimeout(function () {
let constraints = self.model.top.get(key),
removed = [];
constraints.each(function(constraint) {
if (constraint.get('columns').length == 0) {
removed.push(constraint);
}
});
constraints.remove(removed);
},100);
},
resetColOptions : function(m) {
let self = this,
updatedCols = self.model.get('columns').where(
{column: m.previous('name')}
);
if (updatedCols.length > 0) {
/*
* Table column name has changed so update
* column name in primary key as well.
*/
updatedCols[0].set(
{'column': m.get('name')},
{silent: true});
}
setTimeout(function () {
self.render();
}, 10);
},
formatter: {
fromRaw: function (rawValue) {
return rawValue.pluck('column').toString();
},
toRaw: function (val) { return val; },
},
render: function() {
return Backgrid.StringCell.prototype.render.apply(this, arguments);
},
remove: function() {
let tableCols = this.model.top.get('columns'),
primary_key_col = this.model.get('columns');
if (primary_key_col) {
primary_key_col.off('pgadmin:multicolumn:updated');
}
this.stopListening(tableCols, 'remove' , self.removeColumn);
this.stopListening(tableCols, 'change:name' , self.resetColOptions);
Backgrid.StringCell.prototype.remove.apply(this, arguments);
},
}),
canDelete: true, canAdd: true,
control: Backform.MultiSelectAjaxControl.extend({
defaults: _.extend(
{},
Backform.NodeListByNameControl.prototype.defaults,
{
select2: {
multiple: true,
allowClear: true,
width: 'style',
placeholder: gettext('Select the column(s)'),
},
}
),
keyPathAccessor: function(obj, path) {
let res = obj;
if(_.isArray(res)) {
return _.map(res, function(o) { return o['column'];
});
}
path = path.split('.');
for (let path_val of path) {
if (_.isNull(res)) return null;
if (_.isEmpty(path_val)) continue;
if (!_.isUndefined(res[path_val])) res = res[path_val];
}
return _.isObject(res) && !_.isArray(res) ? null : res;
},
initialize: function() {
// Here we will decide if we need to call URL
// Or fetch the data from parent columns collection
let self = this;
if(this.model.handler) {
Backform.Select2Control.prototype.initialize.apply(this, arguments);
// Do not listen for any event(s) for existing constraint.
if (_.isUndefined(self.model.get('oid'))) {
let tableCols = self.model.top.get('columns');
self.listenTo(tableCols, 'remove' , self.resetColOptions);
self.listenTo(tableCols, 'change:name', self.resetColOptions);
}
self.custom_options();
} else {
Backform.MultiSelectAjaxControl.prototype.initialize.apply(this, arguments);
}
self.model.get('columns').on('pgadmin:multicolumn:updated', function() {
self.render.apply(self);
});
},
resetColOptions: function() {
this.genResetColOptions();
},
custom_options: function() {
this.genCustomOptions();
},
onChange: function() {
let self = this,
model = this.model,
attrArr = this.field.get('name').split('.'),
name = attrArr.shift(),
vals = this.getValueFromDOM(),
collection = model.get(name),
removed = [];
this.stopListening(this.model, 'change:' + name, this.render);
/*
* Iterate through all the values, and find out how many are already
* present in the collection.
*/
collection.each(function(m) {
let column = m.get('column'),
idx = _.indexOf(vals, column);
if (idx > -1) {
vals.splice(idx, 1);
} else {
removed.push(column);
}
});
/*
* Adding new values
*/
_.each(vals, function(v) {
let m = new (self.field.get('model'))(
{column: v}, { silent: true,
top: self.model.top,
collection: collection,
handler: collection,
});
collection.add(m);
});
/*
* Removing unwanted!
*/
_.each(removed, function(v) {
collection.remove(collection.where({column: v}));
});
this.listenTo(this.model, 'change:' + name, this.render);
},
remove: function() {
if(this.model.handler) {
let self = this,
tableCols = self.model.top.get('columns');
self.stopListening(tableCols, 'remove' , self.resetColOptions);
self.stopListening(tableCols, 'change:name' , self.resetColOptions);
self.model.get('columns').off('pgadmin:multicolumn:updated');
Backform.Select2Control.prototype.remove.apply(this, arguments);
} else {
Backform.MultiSelectAjaxControl.prototype.remove.apply(this, arguments);
}
},
render: function() {
let index = this.model.get('index');
if(!_.isUndefined(index) && index != '') {
let col = this.model.get('columns');
col.reset([], {silent: true});
}
return Backform.Select2Control.prototype.render.apply(this, arguments);
},
}),
deps: ['index'], node: 'column',
model: pgBrowser.Node.Model.extend({
defaults: {
column: undefined,
},
validate: function() {
return null;
},
}),
transform : function(data){
let res = [];
if (data && _.isArray(data)) {
_.each(data, function(d) {
res.push({label: d.label, value: d.label, image:'icon-column'});
});
}
return res;
},
select2:{allowClear:false},
readonly: function(m) {
return this.checkReadOnly(m);
},
disabled: function(m) {
// Disable if index is selected.
let index = m.get('index');
return !(_.isUndefined(index) || index == '');
},
},{
id: 'include', label: gettext('Include columns'),
type: 'array', group: gettext('Definition'),
editable: false,
canDelete: true, canAdd: true, mode: ['properties', 'create', 'edit'],
visible: function(m) {
/* In table properties, m.node_info is not available */
m = m.top;
return (!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
&& !_.isUndefined(m.node_info.server.version) &&
m.node_info.server.version >= 110000);
},
control: Backform.MultiSelectAjaxControl.extend({
defaults: _.extend(
{},
Backform.NodeListByNameControl.prototype.defaults,
{
select2: {
allowClear: false,
width: 'style',
multiple: true,
placeholder: gettext('Select the column(s)'),
},
}
),
initialize: function() {
// Here we will decide if we need to call URL
// Or fetch the data from parent columns collection
let self = this;
if(this.model.handler) {
Backform.Select2Control.prototype.initialize.apply(this, arguments);
// Do not listen for any event(s) for existing constraint.
if (_.isUndefined(self.model.get('oid'))) {
let tableCols = self.model.top.get('columns');
self.listenTo(tableCols, 'remove' , self.resetColOptions);
self.listenTo(tableCols, 'change:name', self.resetColOptions);
}
self.custom_options();
} else {
Backform.MultiSelectAjaxControl.prototype.initialize.apply(this, arguments);
}
},
resetColOptions: function() {
this.genResetColOptions();
},
custom_options: function() {
this.genCustomOptions();
},
}),
deps: ['index'], node: 'column',
readonly: function(m) {
return this.checkReadOnly(m);
},
disabled: function(m) {
// Disable if index is selected.
let index = m.get('index');
if(_.isUndefined(index) || index == '') {
return false;
} else {
setTimeout(function(){
m.set('include', []);
},10);
return true;
}
},
},{
id: 'spcname', label: gettext('Tablespace'),
type: 'text', group: gettext('Definition'),
control: 'node-list-by-name', node: 'tablespace',
deps: ['index'],
select2:{allowClear:false},
filter: function(m) {
// Don't show pg_global tablespace in selection.
return (m.label != 'pg_global');
},
disabled: function(m) {
// Disable if index is selected.
m = m.top || m;
let index = m.get('index');
if(_.isUndefined(index) || index == '') {
return false;
} else {
setTimeout(function(){
m.set('spcname', '');
},10);
return true;
}
},
},{
id: 'index', label: gettext('Index'),
mode: ['create'],
type: 'text', group: gettext('Definition'),
control: Backform.NodeListByNameControl.extend({
initialize:function() {
Backform.NodeListByNameControl.prototype.initialize.apply(this, arguments);
},
}),
select2:{allowClear:true}, node: 'index',
readonly: function(m) {
// If we are in table edit mode then disable it
if (_.has(m, 'top') && !_.isUndefined(m.top)
&& !m.top.isNew()) {
return true;
}
// We can't update index of existing index constraint.
return !m.isNew();
},
// We will not show this field in Create Table mode
visible: function(m) {
return !_.isUndefined(m.top.node_info['table']);
},
},{
id: 'fillfactor', label: gettext('Fill factor'), deps: ['index'],
type: 'int', group: gettext('Definition'), allowNull: true,
disabled: function(m) {
// Disable if index is selected.
let index = m.get('index');
if(_.isUndefined(index) || index == '') {
return false;
} else {
setTimeout(function(){
m.set('fillfactor', null);
},10);
return true;
}
},
},{
id: 'condeferrable', label: gettext('Deferrable?'),
type: 'switch', group: gettext('Definition'), deps: ['index'],
readonly: function(m) {
return this.checkReadOnly(m);
},
disabled: function(m) {
// Disable if index is selected.
let index = m.get('index');
if(_.isUndefined(index) || index == '') {
return false;
} else {
setTimeout(function(){
if(m.get('condeferrable'))
m.set('condeferrable', false);
},10);
return true;
}
},
},{
id: 'condeferred', label: gettext('Deferred?'),
type: 'switch', group: gettext('Definition'),
deps: ['condeferrable'],
readonly: function(m) {
return this.checkReadOnly(m);
},
disabled: function(m) {
// Disable if condeferred is false or unselected.
if(m.get('condeferrable')) {
return false;
} else {
setTimeout(function(){
if(m.get('condeferred'))
m.set('condeferred', false);
},10);
return true;
}
},
},
],
validate: function() {
this.errorModel.clear();
// Clear parent's error as well

View File

@@ -19,7 +19,6 @@ define('pgadmin.node.mview', [
'sources/pgadmin', 'pgadmin.browser',
'pgadmin.node.schema.dir/child',
'pgadmin.node.schema.dir/schema_child_tree_node', 'sources/utils',
'pgadmin.browser.server.privilege',
], function(
gettext, url_for, $, pgAdmin, pgBrowser,
schemaChild, schemaChildTreeNode, commonUtils

View File

@@ -14,7 +14,7 @@ import ViewSchema from './view.ui';
define('pgadmin.node.view', [
'sources/gettext', 'sources/url_for', 'pgadmin.browser',
'pgadmin.node.schema.dir/child', 'pgadmin.node.schema.dir/schema_child_tree_node',
'pgadmin.browser.server.privilege', 'pgadmin.node.rule',
'pgadmin.node.rule',
], function(
gettext, url_for, pgBrowser, schemaChild, schemaChildTreeNode
) {

View File

@@ -19,7 +19,6 @@ define('pgadmin.node.database', [
'sources/gettext', 'sources/url_for', 'jquery',
'sources/pgadmin', 'pgadmin.browser.utils',
'pgadmin.authenticate.kerberos', 'pgadmin.browser.collection',
'pgadmin.browser.server.privilege', 'pgadmin.browser.server.variable',
], function(gettext, url_for, $, pgAdmin, pgBrowser, Kerberos) {
if (!pgBrowser.Nodes['coll-database']) {

View File

@@ -11,9 +11,8 @@ import { getNodePgaJobStepSchema } from './pga_jobstep.ui';
import _ from 'lodash';
define('pgadmin.node.pga_jobstep', [
'sources/gettext', 'sources/url_for', 'pgadmin.browser', 'backform',
'backgrid', 'pgadmin.backform',
], function(gettext, url_for, pgBrowser, Backform) {
'sources/gettext', 'sources/url_for', 'pgadmin.browser',
], function(gettext, url_for, pgBrowser) {
if (!pgBrowser.Nodes['coll-pga_jobstep']) {
pgBrowser.Nodes['coll-pga_jobstep'] =
@@ -93,37 +92,6 @@ define('pgadmin.node.pga_jobstep', [
}
},
idAttribute: 'jstid',
schema: [{
id: 'jstid', label: gettext('ID'), type: 'int',
cellHeaderClasses: 'width_percent_5', mode: ['properties'],
},{
id: 'jstname', label: gettext('Name'), type: 'text',
cellHeaderClasses: 'width_percent_60',
},{
id: 'jstenabled', label: gettext('Enabled?'),
type: 'switch',
},{
id: 'jstkind', label: gettext('Kind'), type: 'switch',
options: {
'onText': gettext('SQL'), 'offText': gettext('Batch'),
'onColor': 'primary', 'offColor': 'primary',
}, control: Backform.SwitchControl,
},{
id: 'jstconntype', label: gettext('Connection type'),
type: 'switch', deps: ['jstkind'], mode: ['properties'],
disabled: function(m) { return !m.get('jstkind'); },
options: {
'onText': gettext('Local'), 'offText': gettext('Remote'),
'onColor': 'primary', 'offColor': 'primary', width: '65',
},
},{
id: 'jstonerror', label: gettext('On error'), cell: 'select2',
control: 'select2', options: [
{'label': gettext('Fail'), 'value': 'f'},
{'label': gettext('Success'), 'value': 's'},
{'label': gettext('Ignore'), 'value': 'i'},
], select2: {allowClear: false},
}],
}),
});
}

View File

@@ -1,765 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import _ from 'lodash';
define(['sources/gettext', 'jquery', 'backbone',
'backgrid', 'pgadmin.browser.node', 'sources/utils', 'pgadmin.browser.node.ui',
], function(gettext, $, Backbone, Backgrid, pgNode, commonUtils) {
/**
* Each Privilege, supporeted by an database object, will be represented
* using this Model.
*
* Defaults:
* privilege_type -> Name of the permission
* i.e. CREATE, TEMPORARY, CONNECT, etc.
* privilege -> Has privilege? (true/false)
* with_grant -> Has privilege with grant option (true/false)
**/
let PrivilegeModel = pgNode.Model.extend({
idAttribute: 'privilege_type',
defaults: {
privilege_type: undefined,
privilege: false,
with_grant: false,
},
validate: function() {
return null;
},
});
/**
* A database object has privileges item list (aclitem[]).
*
* This model represents the individual privilege item (aclitem).
* It has basically three properties:
* + grantee - Role to which that privilege applies to.
* Empty value represents to PUBLIC.
* + grantor - Grantor who has given this permission.
* + privileges - Privileges for that role.
**/
let PrivilegeRoleModel = pgNode.PrivilegeRoleModel = pgNode.Model.extend({
idAttribute: 'grantee',
defaults: {
grantee: undefined,
grantor: undefined,
privileges: undefined,
},
keys: ['grantee', 'grantor'],
/*
* Each of the database object needs to extend this model, which should
* provide the type of privileges (it supports).
*/
privileges:[],
schema: [{
id: 'grantee', label: gettext('Grantee'), type:'text', group: null,
editable: true, cellHeaderClasses: 'width_percent_40',
node: 'role', options_cached: false,
disabled : function(m) {
if (!(m instanceof Backbone.Model)) {
// This has been called during generating the header cell
return false;
}
return !(
m.top && m.top.node_info &&
m.top.node_info.server.user.name == m.get('grantor')
);
},
transform: function() {
let res =
Backgrid.Extension.NodeListByNameCell.prototype.defaults.transform.apply(
this, arguments
);
res.unshift({label: 'PUBLIC', value: 'PUBLIC'});
return res;
},
cell: Backgrid.Extension.NodeListByNameCell.extend({
initialize: function(opts) {
let self = this,
override_opts = true;
// We would like to override the original options, because - we
// should omit the existing role/user from the privilege cell.
// Because - the column is shared among all the cell, we can only
// override only once.
if (opts && opts.column &&
opts.column instanceof Backbone.Model &&
opts.column.get('options_cached')) {
override_opts = false;
}
Backgrid.Extension.NodeListByNameCell.prototype.initialize.apply(
self, arguments
);
// Let's override the options
if (override_opts) {
opts = self.column.get('options');
self.column.set(
'options', self.omit_selected_roles.bind(self, opts)
);
}
let rerender = function (m) {
let _self = this;
if ('grantee' in m.changed && this.model.cid != m.cid) {
setTimeout(
function() {
_self.render();
}, 50
);
}
}.bind(this);
// We would like to rerender all the cells of this type for this
// collection, because - we need to omit the newly selected roles
// form the list. Also, the render will be automatically called for
// the model represented by this cell, we will not do that again.
this.listenTo(self.model.collection, 'change', rerender, this);
this.listenTo(self.model.collection, 'remove', rerender, this);
},
// Remove all the selected roles (though- not mine).
omit_selected_roles: function(opts, cell) {
let res = opts(cell),
selected = {},
model = cell.model,
cid = model.cid,
// We need to check node_info values in parent when object is nested.
// eg: column level privileges in table dialog
// In this case node_info will not be avilable to column node as
// it is not loaded yet
node_info = (_.has(model.top, 'node_info')
&& !_.isUndefined(model.top.node_info)) ?
model.top.node_info :
model.handler.top.node_info,
curr_user = node_info.server.user.name;
model.collection.each(function(m) {
let grantee = m.get('grantee');
if (m.cid != cid && !_.isUndefined(grantee) &&
curr_user == m.get('grantor')) {
selected[grantee] = m.cid;
}
});
res = _.filter(res, function(o) {
return !(o.value in selected);
});
return res;
},
}),
},{
id: 'privileges', label: gettext('Privileges'),
type: 'collection', model: PrivilegeModel, group: null,
cell: 'privilege', control: 'text', cellHeaderClasses: 'width_percent_40',
disabled : function(column) {
if (column instanceof Backbone.Collection) {
// This has been called during generating the header cell
return false;
}
return !(
this.node_info &&
this.node_info.server.user.name == column.get('grantor') ||
this.attributes.node_info.server.user.name == column.get('grantor')
);
},
},{
id: 'grantor', label: gettext('Grantor'), type: 'text', readonly: true,
cell: 'node-list-by-name', node: 'role',
}],
/*
* Initialize the model, which will transform the privileges string to
* collection of Privilege Model.
*/
initialize: function(attrs, opts) {
pgNode.Model.prototype.initialize.apply(this, arguments);
if (_.isNull(attrs)) {
this.set(
'grantor',
opts && opts.top && opts.top.node_info && opts.top.node_info.server.user.name,
{silent: true}
);
}
/*
* Define the collection of the privilege supported by this model
*/
let self = this,
models = self.get('privileges'),
privileges = this.get('privileges') || {};
if (_.isArray(privileges)) {
privileges = new (pgNode.Collection)(
models, {
model: PrivilegeModel,
top: this.top || this,
handler: this,
silent: true,
parse: false,
});
this.set('privileges', privileges, {silent: true});
}
let privs = {};
_.each(self.privileges, function(p) {
privs[p] = {
'privilege_type': p, 'privilege': false, 'with_grant': false,
};
});
privileges.each(function(m) {
delete privs[m.get('privilege_type')];
});
_.each(privs, function(p) {
privileges.add(p, {silent: true});
});
self.on('change:grantee', self.granteeChanged);
privileges.on('change', function() {
self.trigger('change:privileges', self);
});
return self;
},
granteeChanged: function() {
let privileges = this.get('privileges'),
grantee = this.get('grantee');
// Reset all with grant options if grantee is public.
if (grantee == 'PUBLIC') {
privileges.each(function(m) {
m.set('with_grant', false, {silent: true});
});
}
},
toJSON: function() {
let privileges = [];
if (this.attributes &&
!this.attributes['privileges']) {
return null;
}
this.attributes['privileges'].each(
function(p) {
if (p.get('privilege')) {
privileges.push(p.toJSON());
}
});
return {
'grantee': this.get('grantee'),
'grantor': this.get('grantor'),
'privileges': privileges,
};
},
validate: function() {
let errmsg = null,
msg;
if (_.isUndefined(this.get('grantee'))) {
msg = gettext('A grantee must be selected.');
this.errorModel.set('grantee', msg);
errmsg = msg;
} else {
this.errorModel.unset('grantee');
}
if (this.attributes &&
this.attributes['privileges']) {
let anyPrivSelected = false;
this.attributes['privileges'].each(
function(p) {
if (p.get('privilege')) {
anyPrivSelected = true;
}
});
if (!anyPrivSelected) {
msg = gettext('At least one privilege should be selected.');
this.errorModel.set('privileges', msg);
errmsg = errmsg || msg;
} else {
this.errorModel.unset('privileges');
}
}
return errmsg;
},
});
/**
Custom cell editor for editing privileges.
*/
let PrivilegeCellEditor = Backgrid.Extension.PrivilegeCellEditor =
Backgrid.CellEditor.extend({
tagName: 'div',
// All available privileges in the PostgreSQL database server for
// generating the label for the specific Control
Labels: {
'C': 'CREATE',
'T': 'TEMPORARY',
'c': 'CONNECT',
'a': 'INSERT',
'r': 'SELECT',
'w': 'UPDATE',
'd': 'DELETE',
'D': 'TRUNCATE',
'x': 'REFERENCES',
't': 'TRIGGER',
'U': 'USAGE',
'X': 'EXECUTE',
},
template: _.template([
'<tr class="<%= header ? "header" : "" %>">',
' <td class="renderable">',
' <div class="custom-control custom-checkbox privilege-checkbox">',
' <input tabindex="0" type="checkbox" class="custom-control-input" id="<%= checkbox_id %>" name="privilege" privilege="<%- privilege_type %>" target="<%- target %>" <%= privilege ? \'checked\' : "" %>/>',
' <label class="custom-control-label" for="<%= checkbox_id %>">',
' <%- privilege_label %>',
' </label>',
' </div>',
' </td>',
' <td class="renderable">',
' <div class="custom-control custom-checkbox privilege-checkbox">',
' <input tabindex="0" type="checkbox" class="custom-control-input" id="wgo_<%= checkbox_id %>" name="with_grant" privilege="<%- privilege_type %>" target="<%- target %>" <%= with_grant ? \'checked\' : "" %> <%= enable_with_grant ? "" : \'disabled\'%>/>',
' <label class="custom-control-label" for="wgo_<%= checkbox_id %>">',
' WITH GRANT OPTION',
' </label>',
' </div>',
' </td>',
'</tr>'].join(' '), null, {variable: null}),
events: {
'change': 'privilegeChanged',
'blur': 'lostFocus',
'keydown': 'lostFocus',
},
render: function () {
this.$el.empty();
this.$el.attr('tabindex', '1');
this.$el.attr('target', this.elId);
let collection = this.model.get(this.column.get('name')),
tbl = $('<table aria-label='+this.column.get('label')+'></table>').appendTo(this.$el),
self = this,
privilege = true, with_grant = true;
// For each privilege generate html template.
// List down all the Privilege model.
let checkbox_id = _.uniqueId();
collection.each(function(m) {
let d = m.toJSON();
privilege = (privilege && d.privilege);
with_grant = (with_grant && privilege && d.with_grant);
_.extend(
d, {
'target': self.cid,
'header': false,
'privilege_label': self.Labels[d.privilege_type],
'with_grant': (self.model.get('grantee') != 'PUBLIC' && d.with_grant),
'enable_with_grant': (self.model.get('grantee') != 'PUBLIC' && d.privilege),
'checkbox_id': d.privilege_type + '' + checkbox_id,
});
privilege = (privilege && d.privilege);
with_grant = (with_grant && privilege && d.with_grant);
tbl.append(self.template(d));
});
if (collection.length > 1) {
// Preprend the ALL controls on that table
tbl.prepend(
self.template({
'target': self.cid,
'privilege_label': 'ALL',
'privilege_type': 'ALL',
'privilege': privilege,
'with_grant': (self.model.get('grantee') != 'PUBLIC' && with_grant),
'enable_with_grant': (self.model.get('grantee') != 'PUBLIC' && privilege),
'header': true,
'checkbox_id': 'all' + '' + checkbox_id,
}));
}
self.$el.find('input[type=checkbox]').first().trigger('focus');
// Since blur event does not bubble we need to explicitly call parent's blur event.
$(self.$el.find('input[type=checkbox]')).on('blur',function() {
self.$el.trigger('blur');
});
// Make row visible in when entering in edit mode.
$(self.$el).pgMakeVisible('backform-tab');
self.delegateEvents();
return this;
},
/*
* Listen to the checkbox value change and update the model accordingly.
*/
privilegeChanged: function(ev) {
if (ev && ev.target) {
/*
* We're looking for checkboxes only.
*/
let $el = $(ev.target),
privilege_type = $el.attr('privilege'),
type = $el.attr('name'),
checked = $el.prop('checked'),
$tr = $el.closest('tr'),
$tbl = $tr.closest('table'),
collection = this.model.get('privileges'),
grantee = this.model.get('grantee'), $allGrants,
$allPrivileges, $elGrant;
this.undelegateEvents();
/*
* If the checkbox selected/deselected is for 'ALL', we will select all
* the checkbox for each privilege.
*/
if (privilege_type == 'ALL') {
let allPrivilege, allWithGrant;
$elGrant = $tr.find('input[name=with_grant]');
$allPrivileges = $tbl.find(
'input[name=privilege][privilege!=\'ALL\']'
);
$allGrants = $tbl.find(
'input[name=with_grant][privilege!=\'ALL\']'
);
if (type == 'privilege') {
/*
* We clicked the privilege checkbox, and not checkbox for with
* grant options.
*/
allPrivilege = checked;
allWithGrant = false;
if (checked) {
$allPrivileges.prop('checked', true);
/*
* We have clicked the ALL checkbox, we should be able to select
* the grant options too.
*/
if (grantee == 'PUBLIC') {
$allGrants.prop('disabled', true);
$elGrant.prop('disabled', true);
} else {
$allGrants.prop('disabled', false);
$elGrant.prop('disabled', false);
}
} else {
/*
* ALL checkbox has been deselected, hence - we need to make
* sure.
* 1. Deselect all the privileges checkboxes
* 2. Deselect and disable all with grant privilege checkboxes.
* 3. Deselect and disable the checkbox for ALL with grant privilege.
*/
$allPrivileges.prop('checked', false);
$elGrant.prop('checked', false);
$allGrants.prop('checked', false);
$elGrant.prop('disabled', true);
$allGrants.prop('disabled', true);
}
} else {
/*
* We were able to click the ALL with grant privilege checkbox,
* that means, privilege for Privileges are true.
*
* We need to select/deselect all the with grant options
* checkboxes, based on the current value of the ALL with grant
* privilege checkbox.
*/
allPrivilege = true;
allWithGrant = checked;
$allGrants.prop('checked', checked);
}
/*
* Set the values for each Privilege Model.
*/
collection.each(function(m) {
m.set(
{'privilege': allPrivilege, 'with_grant': allWithGrant}
);
});
} else {
/*
* Particular privilege has been selected/deselected, which can be
* identified using the privilege="X" attribute.
*/
let attrs = {};
$tbl = $tr.closest('table');
$allPrivileges = $tbl.find(
'input[name=privilege][privilege=\'ALL\']'
);
$allGrants = $tbl.find(
'input[name=with_grant][privilege=\'ALL\']'
);
attrs[type] = checked;
if (type == 'privilege') {
$elGrant = ($el.closest('tr')).find('input[name=with_grant]');
if (!checked) {
attrs['with_grant'] = false;
$elGrant.prop('checked', false).prop('disabled', true);
$allPrivileges.prop('checked', false);
$allGrants.prop('disabled', true);
$allGrants.prop('checked', false);
} else if (grantee != 'PUBLIC') {
$elGrant.prop('disabled', false);
}
} else if (!checked) {
$allGrants.prop('checked', false);
}
collection.get(privilege_type).set(attrs);
if (checked) {
$allPrivileges = $tbl.find(
'input[name=privilege][privilege!=\'ALL\']:checked'
);
if ($allPrivileges.length > 1 &&
$allPrivileges.length == collection.models.length) {
$allPrivileges.prop('checked', true);
if (type == 'with_grant') {
$allGrants = $tbl.find(
'input[name=with_grant][privilege!=\'ALL\']:checked'
);
if ($allGrants.length == collection.models.length) {
$allGrants.prop('disabled', false);
$allGrants.prop('checked', true);
}
} else if (grantee != 'PUBLIC') {
$allGrants.prop('disabled', false);
}
}
}
}
this.model.trigger('change', this.model);
let anySelected = false,
msg = null;
collection.each(function(m) {
anySelected = anySelected || m.get('privilege');
});
if (anySelected) {
this.model.errorModel.unset('privileges');
if (this.model.errorModel.has('grantee')) {
msg = this.model.errorModel.get('grantee');
}
} else {
this.model.errorModel.set(
'privileges', gettext('At least one privilege should be selected.')
);
msg = gettext('At least one privilege should be selected.');
}
if (msg) {
this.model.collection.trigger(
'pgadmin-session:model:invalid', msg, this.model
);
} else {
this.model.collection.trigger(
'pgadmin-session:model:valid', this.model
);
}
}
this.delegateEvents();
},
lostFocus: function(ev) {
/*
* We lost the focus, it's time for us to exit the editor.
*/
let self = this,
/*
* Function to determine whether one dom element is descendant of another
* dom element.
*/
isDescendant = function (parent, child) {
let node = child.parentNode;
while (node != null) {
if (node == parent) {
return true;
}
node = node.parentNode;
}
return false;
},
model = this.model,
column = this.column,
command = new Backgrid.Command(ev),
coll = this.model.get(this.column.get('name'));
if (ev.key == 'Tab'){
commonUtils.handleKeyNavigation(event);
}
if (command.moveUp() || command.moveDown() || command.save() || command.cancel() ||
(command.moveLeft() && ev.target.name === 'privilege' && $(ev.target).attr('privilege') === 'ALL')) {
// undo
ev.stopPropagation();
model.trigger('backgrid:edited', model, column, command);
return;
} else if (command.moveRight()) {
// If we are at the last privilege then we should move to next cell
if (coll.last().get('privilege_type') === $(ev.target).attr('privilege')) {
if ((ev.target.name === 'privilege' && !ev.target.checked ) ||
$(ev.target).attr('name') === 'with_grant') {
ev.stopPropagation();
model.trigger('backgrid:edited', model, column, command);
return;
}
}
}
/*
* Between leaving the old element focus and entering the new element focus the
* active element is the document/body itself so add timeout to get the proper
* focused active element.
*/
setTimeout(function() {
/*
* Do not close the control if user clicks outside dialog window,
* only close the row if user clicks on add button or on another row,
* if user clicks somewhere else then we will get tagName as 'BODY'
* or 'WINDOW'
*/
let is_active_element = document.activeElement.tagName == 'DIV' ||
document.activeElement.tagName == 'BUTTON';
if (is_active_element && self.$el[0] != document.activeElement &&
!isDescendant(self.$el[0], document.activeElement)) {
let m = self.model;
m.trigger('backgrid:edited', m, self.column, new Backgrid.Command(ev));
}},10);
},
});
/*
* This will help us transform the privileges value in proper format to be
* displayed in the cell.
*/
let PrivilegeCellFormatter = Backgrid.Extension.PrivilegeCellFormatter =
function () {/*This is intentional (SonarQube)*/};
_.extend(PrivilegeCellFormatter.prototype, {
notation: {
'CREATE' : 'C',
'TEMPORARY' : 'T',
'CONNECT' : 'c',
'INSERT' : 'a',
'SELECT' : 'r',
'UPDATE' : 'w',
'DELETE' : 'd',
'TRUNCATE' : 'D',
'REFERENCES' : 'x',
'TRIGGER' : 't',
'USAGE' : 'U',
'EXECUTE' : 'X',
},
/**
* Takes a raw value from a model and returns an optionally formatted
* string for display.
*/
fromRaw: function (rawData) {
let res = '';
if (rawData instanceof Backbone.Collection) {
rawData.each(function(m) {
if (m.get('privilege')) {
res += m.get('privilege_type');
if (m.get('with_grant')) {
res += '*';
}
}
});
}
return res;
},
});
/*
* PrivilegeCell for rendering and taking input for the privileges.
*/
Backgrid.Extension.PrivilegeCell = Backgrid.Cell.extend({
className: 'edit-cell',
formatter: PrivilegeCellFormatter,
editor: PrivilegeCellEditor,
initialize: function () {
let self = this;
Backgrid.Cell.prototype.initialize.apply(this, arguments);
self.model.on('change:grantee', function() {
if (!self.$el.hasClass('editor')) {
/*
* Add time out before render; As we might want to wait till model
* is updated by PrivilegeRoleModel:granteeChanged.
*/
setTimeout(function() {
self.render();
},10);
}
});
},
events: {
'click': 'enterEditMode',
'keydown': 'saveOrCancel',
},
saveOrCancel: function (e) {
let model = this.model;
let column = this.column;
let command = new Backgrid.Command(e);
if (command.moveUp() || command.moveDown() || command.moveLeft() || command.moveRight() ||
command.save()) {
e.preventDefault();
e.stopPropagation();
model.trigger('backgrid:edited', model, column, command);
}
// esc
else if (command.cancel()) {
// undo
e.stopPropagation();
model.trigger('backgrid:edited', model, column, command);
}
},
});
return PrivilegeRoleModel;
});

View File

@@ -18,7 +18,6 @@ define('pgadmin.node.server', [
'sources/pgadmin', 'pgadmin.browser',
'pgadmin.user_management.current_user',
'pgadmin.authenticate.kerberos',
'pgadmin.browser.server.privilege',
], function(
gettext, url_for, $, pgAdmin, pgBrowser,
current_user, Kerberos,

View File

@@ -1,490 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import _ from 'lodash';
define([
'sources/gettext', 'jquery', 'backbone', 'backform', 'backgrid',
'pgadmin.browser.node', 'pgadmin.browser.node.ui',
],
function(gettext, $, Backbone, Backform, Backgrid, pgNode) {
/*
* cellFunction for variable control.
* This function returns cell class depending on vartype.
*/
let cellFunction = function(model) {
let self = this,
name = model.get('name'),
availVariables = {};
self.collection.each(function(col) {
if (col.get('name') == 'name') {
availVariables = col.get('availVariables');
}
});
let variable = name ? availVariables[name]: undefined,
value = model.get('value');
switch(variable && variable.vartype) {
case 'bool':
/*
* bool cell and variable cannot be stateless (i.e undefined).
* It should be either true or false.
*/
model.set('value', !!model.get('value'), {silent: true});
return Backgrid.Extension.SwitchCell;
case 'enum':
model.set({'value': value}, {silent:true});
var options = [],
enumVals = variable && variable.enumvals;
_.each(enumVals, function(enumVal) {
options.push([enumVal, enumVal]);
});
return Backgrid.Extension.Select2Cell.extend({optionValues: options});
case 'integer':
if (!_.isNaN(parseInt(value))) {
model.set({'value': parseInt(value)}, {silent:true});
} else {
model.set({'value': undefined}, {silent:true});
}
return Backgrid.IntegerCell;
case 'real':
if (!_.isNaN(parseFloat(value))) {
model.set({'value': parseFloat(value)}, {silent:true});
} else {
model.set({'value': undefined}, {silent:true});
}
return Backgrid.NumberCell.extend({decimals: 0});
case 'string':
return Backgrid.StringCell;
default:
model.set({'value': undefined}, {silent:true});
return Backgrid.Cell;
}
};
/*
* This row will define behaviour or value column cell depending upon
* variable name.
*/
let VariableRow = Backgrid.Row.extend({
modelDuplicateClass: 'bg-model-duplicate',
initialize: function () {
Backgrid.Row.prototype.initialize.apply(this, arguments);
let self = this;
self.model.on('change:name', function() {
setTimeout(function() {
self.columns.each(function(col) {
if (col.get('name') == 'value') {
// Reset old value
self.model.set({'value': undefined}, {silent:true});
let idx = self.columns.indexOf(col),
cf = col.get('cellFunction'),
cell = new (cf.apply(col, [self.model]))({
column: col,
model: self.model,
}),
oldCell = self.cells[idx];
oldCell.remove();
self.cells[idx] = cell;
self.render();
}
});
}, 10);
});
self.listenTo(self.model, 'pgadmin-session:model:duplicate', self.modelDuplicate);
self.listenTo(self.model, 'pgadmin-session:model:unique', self.modelUnique);
},
modelDuplicate: function() {
$(this.el).removeClass('new');
$(this.el).addClass(this.modelDuplicateClass);
},
modelUnique: function() {
$(this.el).removeClass(this.modelDuplicateClass);
},
});
/**
* VariableModel used to represent configuration parameters (variables tab)
* for database objects.
**/
let VariableModel = pgNode.VariableModel = pgNode.Model.extend({
keys: ['name'],
defaults: {
name: undefined,
value: undefined,
role: null,
database: null,
},
schema: [
{
id: 'name', label: gettext('Name'), type:'text', cellHeaderClasses: 'width_percent_30',
editable: function(m) {
return (m instanceof Backbone.Collection) ? true : m.isNew();
},
cell: Backgrid.Extension.NodeAjaxOptionsCell.extend({
initialize: function() {
Backgrid.Extension.NodeAjaxOptionsCell.prototype.initialize.apply(this, arguments);
// Immediately process options as we need them before render.
let opVals = _.clone(this.optionValues ||
(_.isFunction(this.column.get('options')) ?
(this.column.get('options'))(this) :
this.column.get('options')));
this.column.set('options', opVals);
},
}),
url: 'vopts',
select2: { allowClear: false },
transform: function(vars, cell) {
let res = [],
availVariables = {};
_.each(vars, function(v) {
res.push({
'value': v.name,
'image': undefined,
'label': v.name,
});
availVariables[v.name] = v;
});
cell.column.set('availVariables', availVariables);
return res;
},
},
{
id: 'value', label: gettext('Value'), type: 'text', editable: true,
cellFunction: cellFunction, cellHeaderClasses: 'width_percent_40',
},
{id: 'database', label: gettext('Database'), type: 'text', editable: true,
node: 'database', cell: Backgrid.Extension.NodeListByNameCell,
},
{id: 'role', label: gettext('Role'), type: 'text', editable: true,
node: 'role', cell: Backgrid.Extension.NodeListByNameCell},
],
toJSON: function() {
let d = Backbone.Model.prototype.toJSON.apply(this);
// Remove not defined values from model values.
// i.e.
// role, database
if (_.isUndefined(d.database) || _.isNull(d.database)) {
delete d.database;
}
if (_.isUndefined(d.role) || _.isNull(d.role)) {
delete d.role;
}
return d;
},
validate: function() {
let msg = null;
if (_.isUndefined(this.get('name')) ||
_.isNull(this.get('name')) ||
String(this.get('name')).replace(/^\s+|\s+$/g, '') == '') {
msg = gettext('Please select a parameter name.');
this.errorModel.set('name', msg);
} else if (_.isUndefined(this.get('value')) ||
_.isNull(this.get('value')) ||
String(this.get('value')).replace(/^\s+|\s+$/g, '') == '') {
msg = gettext('Please enter a value for the parameter.');
this.errorModel.set('value', msg);
this.errorModel.unset('name');
} else {
this.errorModel.unset('name');
this.errorModel.unset('value');
}
return msg;
},
});
/**
* Variable Tab Control to set/update configuration values for database object.
*
**/
Backform.VariableCollectionControl =
Backform.UniqueColCollectionControl.extend({
hasDatabase: false,
hasRole: false,
initialize: function(opts) {
let self = this,
keys = ['name'];
/*
* Read from field schema whether user wants to use database and role
* fields in Variable control.
*/
self.hasDatabase = opts.field.get('hasDatabase');
self.hasRole = opts.field.get('hasRole');
// Update unique coll field based on above flag status.
if (self.hasDatabase) {
keys.push('database');
} else if (self.hasRole) {
keys.push('role');
}
// Overriding the uniqueCol in the field
if (opts && opts.field) {
if (opts.field instanceof Backform.Field) {
opts.field.set({
model: pgNode.VariableModel.extend({keys:keys}),
},
{
silent: true,
});
} else {
opts.field.extend({
model: pgNode.VariableModel.extend({keys:keys}),
});
}
}
Backform.UniqueColCollectionControl.prototype.initialize.apply(
self, arguments
);
self.availVariables = {};
let gridCols = ['name', 'value'];
if (self.hasDatabase) {
gridCols.push('database');
}
if (self.hasRole) {
gridCols.push('role');
}
self.gridSchema = Backform.generateGridColumnsFromModel(
self.field.get('node_info'), VariableModel.extend({keys:keys}), 'edit', gridCols, self.field.get('schema_node')
);
// Make sure - we do have the data for variables
self.getVariables();
},
/*
* Get the variable data for this node.
*/
getVariables: function() {
let self = this,
url = this.field.get('url'),
m = self.model;
if (!this.field.get('version_compatible'))
return;
if (url && !m.isNew()) {
let node = self.field.get('node'),
node_data = self.field.get('node_data'),
node_info = self.field.get('node_info'),
full_url = node.generate_url.apply(
node, [
null, url, node_data, true, node_info,
]),
data,
isTracking = self.collection.trackChanges;
if (isTracking) {
self.collection.stopSession();
}
m.trigger('pgadmin-view:fetching', m, self.field);
$.ajax({
async: false,
url: full_url,
})
.done(function (res) {
data = res.data;
})
.fail(function() {
m.trigger('pgadmin-view:fetch:error', m, self.field);
});
m.trigger('pgadmin-view:fetched', m, self.field);
if (data && _.isArray(data)) {
self.collection.reset(data, {silent: true});
}
/*
* Make sure - new data will be taken care by the session management
*/
if (isTracking) {
self.collection.startNewSession();
}
}
},
showGridControl: function(data) {
let self = this,
titleTmpl = _.template([
'<div class=\'subnode-header\'>',
'<span class=\'control-label\'><%-label%></span>',
'<button class=\'btn btn-sm-sq btn-primary-icon add fa fa-plus\' title=\'' + gettext('Add new row') + '\' <%=canAdd ? \'\' : \'disabled="disabled"\'%>><span class="sr-only">' + gettext('Add new row') + '</span></button>',
'</div>'].join('\n')),
$gridBody =
$('<div class=\'pgadmin-control-group backgrid form-group col-12 object subnode\'></div>').append(
titleTmpl(data)
);
// Clean up existing grid if any (in case of re-render)
if (self.grid) {
self.grid.remove();
}
let gridSchema = _.clone(this.gridSchema);
_.each(gridSchema.columns, function(col) {
if (col.name == 'value') {
col.availVariables = self.availVariables;
}
});
// Insert Delete Cell into Grid
if (data.disabled && data.canDelete) {
gridSchema.columns.unshift({
name: 'pg-backform-delete width_percent_5', label: '',
cell: Backgrid.Extension.DeleteCell,
editable: false, cell_priority: -1,
});
}
// Change format of each of the data
// Because - data coming from the server is in string format
self.collection.each(function(model) {
let name = model.get('name'), val;
if (name in self.availVariables) {
switch(self.availVariables[name].vartype) {
case 'real':
val = parseFloat(model.get('value'));
model.set('value', (isNaN(val) ? undefined : val), {silent: true});
break;
case 'integer':
val = parseInt(model.get('value'));
model.set('value', (isNaN(val) ? undefined : val), {silent: true});
break;
default:
break;
}
}
});
// Initialize a new Grid instance
let grid = self.grid = new Backgrid.Grid({
columns: gridSchema.columns,
collection: self.collection,
row: VariableRow,
className: 'backgrid table presentation table-bordered table-noouter-border table-hover',
});
self.$grid = grid.render().$el;
$gridBody.append(self.$grid);
// Add button callback
if (!(data.disabled || !data.canAdd)) {
$gridBody.find('button.add').first().on('click',(e) => {
e.preventDefault();
let canAddRow = _.isFunction(data.canAddRow) ?
data.canAddRow.apply(self, [self.model]) : true;
if (canAddRow) {
let allowMultipleEmptyRows = !!self.field.get('allowMultipleEmptyRows');
// If allowMultipleEmptyRows is not set or is false then don't allow second new empty row.
// There should be only one empty row.
if (!allowMultipleEmptyRows && self.collection) {
let isEmpty = false;
self.collection.each(function(model) {
let modelValues = [];
_.each(model.attributes, function(val) {
modelValues.push(val);
});
if(!_.some(modelValues, _.identity)) {
isEmpty = true;
}
});
if(isEmpty) {
return false;
}
}
$(grid.body.$el.find($('tr.new'))).removeClass('new');
let m = new (data.model) (null, {
silent: true,
handler: self.collection,
top: self.model.top || self.model,
collection: self.collection,
node_info: self.model.node_info,
});
self.collection.add(m);
let idx = self.collection.indexOf(m),
newRow = grid.body.rows[idx].$el;
newRow.addClass('new');
$(newRow).pgMakeVisible('backform-tab');
return false;
}
});
}
// Render node grid
return $gridBody;
},
addVariable: function(ev) {
ev.preventDefault();
let self = this,
m = new (self.field.get('model'))(
self.headerData.toJSON(), {
silent: true, top: self.collection.top,
handler: self.collection,
}),
coll = self.model.get(self.field.get('name'));
coll.add(m);
let idx = coll.indexOf(m);
// idx may not be always > -1 because our UniqueColCollection may
// remove 'm' if duplicate value found.
if (idx > -1) {
self.$grid.find('.new').removeClass('new');
let newRow = self.grid.body.rows[idx].$el;
newRow.addClass('new');
$(newRow).pgMakeVisible('backform-tab');
}
return false;
},
});
return VariableModel;
});

View File

@@ -15,8 +15,7 @@ import _ from 'lodash';
define('pgadmin.node.tablespace', [
'sources/gettext', 'sources/url_for',
'pgadmin.browser', 'pgadmin.browser.collection', 'pgadmin.browser.node.ui',
'pgadmin.browser.server.privilege',
'pgadmin.browser', 'pgadmin.browser.collection',
], function(
gettext, url_for, pgBrowser
) {

View File

@@ -12,8 +12,7 @@ import _ from 'lodash';
define([
'sources/gettext', 'sources/pgadmin',
'sources/browser/generate_url',
'pgadmin.backform', 'pgadmin.backgrid',
'pgadmin.browser.node', 'backgrid.select.all',
'pgadmin.browser.node',
], function(gettext, pgAdmin, generateUrl) {
let pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};

View File

@@ -10,17 +10,16 @@
import {getNodeView, removeNodeView} from './node_view';
import Notify from '../../../static/js/helpers/Notifier';
import _ from 'lodash';
import { pgHandleItemError } from '../../../static/js/utils';
define('pgadmin.browser.node', [
'sources/gettext', 'jquery', 'sources/pgadmin',
'backbone', 'pgadmin.browser.datamodel',
'backform', 'sources/browser/generate_url', 'pgadmin.help', 'sources/utils',
'pgadmin.browser.utils', 'pgadmin.backform',
'sources/browser/generate_url', 'pgadmin.help', 'sources/utils',
'pgadmin.browser.utils',
], function(
gettext, $, pgAdmin,
Backbone, pgBrowser,
Backform, generateUrl, help,
generateUrl, help,
commonUtils
) {
@@ -294,210 +293,6 @@ define('pgadmin.browser.node', [
return true;
}
},
///////
// Generate a Backform view using the node's model type
//
// Used to generate view for the particular node properties, edit,
// creation.
getView: function(item, type, el, node, formType, callback, ctx, cancelFunc) {
let that = this;
if (!this.type || this.type == '')
// We have no information, how to generate view for this type.
return null;
if (this.model) {
// This will be the URL, used for object manipulation.
// i.e. Create, Update in these cases
let urlBase = this.generate_url(item, type, node, false, null, that.url_jump_after_node);
if (!urlBase)
// Ashamed of myself, I don't know how to manipulate this
// node.
return null;
let attrs = {};
// In order to get the object data from the server, we must set
// object-id in the model (except in the create mode).
if (type !== 'create') {
attrs[this.model.idAttribute || this.model.prototype.idAttribute ||
'id'] = node._id;
}
// We know - which data model to be used for this object.
let info = pgBrowser.tree.getTreeNodeHierarchy(item),
newModel = new(this.model.extend({
urlRoot: urlBase,
}))(
attrs, {
node_info: info,
}
),
fields = Backform.generateViewSchema(
info, newModel, type, this, node
);
if (type == 'create' || type == 'edit') {
if (callback && ctx) {
callback = callback.bind(ctx);
} else {
callback = function() {
console.warn(
'Broke something!!! Why we don\'t have the callback or the context???'
);
};
}
let onSessionInvalid = function(msg) {
let alertMessage = `
<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" role="img"></i>
</div>
<div role="alert" class="alert-text">${msg}</div>
<div class="ml-auto close-error-bar">
<a class="close-error fa fa-times text-danger"></a>
</div>
</div>
</div>`;
if (!_.isUndefined(that.statusBar)) {
that.statusBar.html(alertMessage).css('visibility', 'visible');
that.statusBar.find('a.close-error').bind('click', function() {
this.empty().css('visibility', 'hidden');
}.bind(that.statusBar));
}
let sessHasChanged = false;
if(this.sessChanged && this.sessChanged()){
sessHasChanged = true;
}
callback(true, sessHasChanged);
return true;
};
let onSessionValidated = function(sessHasChanged) {
if (!_.isUndefined(that.statusBar)) {
that.statusBar.empty().css('visibility', 'hidden');
}
callback(false, sessHasChanged);
};
callback(false, false);
newModel.on('pgadmin-session:valid', onSessionValidated);
newModel.on('pgadmin-session:invalid', onSessionInvalid);
}
let view;
// 'schema' has the information about how to generate the form.
if (_.size(fields)) {
// This will contain the actual view
if (formType == 'fieldset') {
// It is used to show, edit, create the object in the
// properties tab.
view = new Backform.Accordian({
el: el,
model: newModel,
schema: fields,
});
} else {
// This generates a view to be used by the node dialog
// (for create/edit operation).
view = new Backform.Dialog({
el: el,
model: newModel,
schema: fields,
});
}
let setFocusOnEl = function() {
let container = $(el).find('.tab-content:first > .tab-pane.active:first');
commonUtils.findAndSetFocus(container);
};
if (!newModel.isNew()) {
// This is definetely not in create mode
let msgDiv = '<div role="status" class="pg-panel-message pg-panel-properties-message">' +
gettext('Retrieving data from the server...') + '</div>',
$msgDiv = $(msgDiv);
let timer = setTimeout(function(_ctx) {
// notify user if request is taking longer than 1 second
if (!_.isUndefined(_ctx)) {
$msgDiv.appendTo(_ctx);
}
}, 1000, ctx);
let fetchAjaxHook = function() {
newModel.fetch({
success: function() {
// Clear timeout and remove message
clearTimeout(timer);
$msgDiv.addClass('d-none');
// We got the latest attributes of the object. Render the view
// now.
view.render();
setFocusOnEl();
newModel.startNewSession();
},
error: function(model, xhr, options) {
let _label = that && item ?
pgBrowser.tree.getTreeNodeHierarchy(
item
)[that.type].label : '';
pgBrowser.Events.trigger(
'pgadmin:node:retrieval:error', 'properties',
xhr, options.textStatus, options.errorThrown, item
);
if (!pgHandleItemError(xhr, {
item: item,
info: info,
}
)) {
Notify.pgNotifier(
options.textStatus, xhr,
gettext('Error retrieving properties - %s', options.errorThrown || _label),
function(msg) {
if(msg === 'CRYPTKEY_SET') {
fetchAjaxHook();
} else {
console.warn(arguments);
}
}
);
}
// Close the panel (if could not fetch properties)
if (cancelFunc) {
cancelFunc();
}
},
});
};
fetchAjaxHook();
} else {
// Yay - render the view now!
view.render();
setFocusOnEl();
newModel.startNewSession();
}
}
return view;
}
return null;
},
addUtilityPanel: function(width, height, docker) {
let body = window.document.body,
el = document.createElement('div'),
@@ -1182,8 +977,6 @@ define('pgadmin.browser.node', [
tree = pgAdmin.Browser.tree,
j = panel.$container.find('.obj_properties').first(),
view = j.data('obj-view'),
content = $('<div></div>')
.addClass('pg-prop-content col-12'),
confirm_close = true;
// Handle key press events for Cancel, save and help button
@@ -1220,70 +1013,11 @@ define('pgadmin.browser.node', [
});
}, 200); // wait for panel tab to render
// Template function to create the status bar
let createStatusBar = function(location) {
let statusBar = $('<div role="status"></div>').addClass(
'pg-prop-status-bar'
).appendTo(j);
statusBar.css('visibility', 'hidden');
if (location == 'header') {
statusBar.appendTo(that.header);
} else {
statusBar.prependTo(that.footer);
}
that.statusBar = statusBar;
return statusBar;
}.bind(panel),
// Template function to create the button-group
createButtons = function(buttons, location, extraClasses) {
// Arguments must be non-zero length array of type
// object, which contains following attributes:
// label, type, extraClasses, register
if (buttons && _.isArray(buttons) && buttons.length > 0) {
// All buttons will be created within a single
// div area.
let btnGroup =
$('<div class="pg-prop-btn-group"></div>'),
// Template used for creating a button
tmpl = _.template([
'<button tabindex="0" type="<%= type %>" ',
'class="btn <%=extraClasses.join(\' \')%>"',
'<% if (disabled) { %> disabled="disabled"<% } %> title="<%-tooltip%>"',
'<% if (label != "") {} else { %> aria-label="<%-tooltip%>"<% } %> >',
'<span class="<%= icon %>"></span><% if (label != "") { %>&nbsp;<%-label%><% } %></button>',
].join(' '));
if (location == 'header') {
btnGroup.appendTo(that.header);
} else {
btnGroup.appendTo(that.footer);
}
if (extraClasses) {
btnGroup.addClass(extraClasses);
}
_.each(buttons, function(btn) {
// Create the actual button, and append to
// the group div
// icon may not present for this button
if (!btn.icon) {
btn.icon = '';
}
let b = $(tmpl(btn));
btnGroup.append(b);
// Register is a callback to set callback
// for certain operation for this button.
btn.register(b);
});
return btnGroup;
}
return null;
}.bind(panel),
// Callback to show object properties
properties = function() {
// Callback to show object properties
let properties = function() {
// Avoid unnecessary reloads
let i = tree.selected(),
d = i && tree.itemData(i),
treeHierarchy = tree.getTreeNodeHierarchy(i);
// Cache the current IDs for next time
@@ -1291,106 +1025,11 @@ define('pgadmin.browser.node', [
/* Remove any dom rendered by getNodeView */
removeNodeView(j[0]);
if(that.getSchema) {
let treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item);
getNodeView(
that.type, treeNodeInfo, 'properties', data, 'tab', j[0], this, onEdit
);
return;
}
if (!content.hasClass('has-pg-prop-btn-group'))
content.addClass('has-pg-prop-btn-group');
// We need to release any existing view, before
// creating new view.
if (view) {
// Release the view
view.remove({
data: true,
internal: true,
silent: true,
});
// Deallocate the view
// delete view;
view = null;
// Reset the data object
j.data('obj-view', null);
}
// Make sure the HTML element is empty.
j.empty();
that.header = $('<div></div>').addClass(
'pg-prop-header'
).appendTo(j);
that.footer = $('<div></div>').addClass(
'pg-prop-footer'
).appendTo(j);
// Create a view to show the properties in fieldsets
view = that.getView(item, 'properties', content, data, 'fieldset', undefined, j);
if (view) {
// Save it for release it later
j.data('obj-view', view);
// Create proper buttons
let buttons = [];
buttons.push({
label: gettext('Edit'),
type: 'edit',
tooltip: gettext('Edit'),
extraClasses: ['btn', 'btn-primary', 'pull-right', 'm-1'],
icon: 'fa fa-sm fa-pencil-alt',
disabled: _.isFunction(that.canEdit) ? !that.canEdit.apply(that, [d, i]) : !that.canEdit,
register: function(btn) {
btn.on('click',() => {
onEdit();
});
},
});
buttons.push({
label: '',
type: 'help',
tooltip: gettext('SQL help for this object type.'),
extraClasses: ['btn-primary-icon', 'btn-primary-icon', 'm-1'],
icon: 'fa fa-info',
disabled: (that.sqlAlterHelp == '' && that.sqlCreateHelp == '' && !that.epasHelp) ? true : false,
register: function(btn) {
btn.on('click',() => {
onSqlHelp();
});
},
});
createButtons(buttons, 'header', 'pg-prop-btn-group-above');
}
j.append(content);
}.bind(panel),
onSqlHelp = function() {
// Construct the URL
let server = pgBrowser.tree.getTreeNodeHierarchy(item).server;
let url = pgBrowser.utils.pg_help_path;
let fullUrl = '';
if (server.server_type == 'ppas' && that.epasHelp) {
fullUrl = help.getEPASHelpUrl(server.version);
} else {
if (that.sqlCreateHelp == '' && that.sqlAlterHelp != '') {
fullUrl = help.getHelpUrl(url, that.sqlAlterHelp, server.version);
} else if (that.sqlCreateHelp != '' && that.sqlAlterHelp == '') {
fullUrl = help.getHelpUrl(url, that.sqlCreateHelp, server.version);
} else {
if (view.model.isNew()) {
fullUrl = help.getHelpUrl(url, that.sqlCreateHelp, server.version);
} else {
fullUrl = help.getHelpUrl(url, that.sqlAlterHelp, server.version);
}
}
}
window.open(fullUrl, 'postgres_help');
let treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item);
getNodeView(
that.type, treeNodeInfo, 'properties', data, 'tab', j[0], this, onEdit
);
return;
}.bind(panel),
onDialogHelp = function() {
@@ -1427,52 +1066,6 @@ define('pgadmin.browser.node', [
}
}.bind(panel),
warnBeforeAttributeChange = function(yes_callback) {
let $props = this.$container.find('.obj_properties').first(),
objview = $props && $props.data('obj-view'),
self = this;
if (objview && objview.model && !_.isUndefined(objview.model.warn_text) && !_.isNull(objview.model.warn_text)) {
let warn_text;
warn_text = gettext(objview.model.warn_text);
if(objview.model.sessChanged()){
Notify.confirm(
gettext('Warning'),
warn_text,
function() {
setTimeout(function(){
yes_callback();
}.bind(self), 50);
return true;
},
function() {
return true;
}
);
} else {
return true;
}
} else {
yes_callback();
return true;
}
}.bind(panel),
informBeforeAttributeChange = function(ok_callback) {
let $props = this.$container.find('.obj_properties').first(),
objview = $props && $props.data('obj-view');
if (objview && objview.model && !_.isUndefined(objview.model.inform_text) && !_.isNull(objview.model.inform_text)) {
Notify.alert(
gettext('Warning'),
gettext(objview.model.inform_text)
);
}
ok_callback();
return true;
}.bind(panel),
onSave = function(_view, saveBtn) {
let m = _view.model,
d = m.toJSON(true),
@@ -1547,229 +1140,41 @@ define('pgadmin.browser.node', [
/* Remove any dom rendered by getNodeView */
removeNodeView(j[0]);
/* getSchema is a schema for React. Get the react node view */
if(that.getSchema) {
let treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item);
getNodeView(
that.type, treeNodeInfo, action, data, 'dialog', j[0], this, onEdit,
(nodeData)=>{
if(nodeData.node) {
onSaveFunc(nodeData.node, treeNodeInfo);
// Removing the node-prop property of panel
// so that we show updated data on panel
let pnlProperties = pgBrowser.docker.findPanels('properties')[0],
pnlSql = pgBrowser.docker.findPanels('sql')[0],
pnlStats = pgBrowser.docker.findPanels('statistics')[0],
pnlDependencies = pgBrowser.docker.findPanels('dependencies')[0],
pnlDependents = pgBrowser.docker.findPanels('dependents')[0];
let treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item);
getNodeView(
that.type, treeNodeInfo, action, data, 'dialog', j[0], this, onEdit,
(nodeData)=>{
if(nodeData.node) {
onSaveFunc(nodeData.node, treeNodeInfo);
// Removing the node-prop property of panel
// so that we show updated data on panel
let pnlProperties = pgBrowser.docker.findPanels('properties')[0],
pnlSql = pgBrowser.docker.findPanels('sql')[0],
pnlStats = pgBrowser.docker.findPanels('statistics')[0],
pnlDependencies = pgBrowser.docker.findPanels('dependencies')[0],
pnlDependents = pgBrowser.docker.findPanels('dependents')[0];
if (pnlProperties)
$(pnlProperties).removeData('node-prop');
if (pnlSql)
$(pnlSql).removeData('node-prop');
if (pnlStats)
$(pnlStats).removeData('node-prop');
if (pnlDependencies)
$(pnlDependencies).removeData('node-prop');
if (pnlDependents)
$(pnlDependents).removeData('node-prop');
if (pnlProperties)
$(pnlProperties).removeData('node-prop');
if (pnlSql)
$(pnlSql).removeData('node-prop');
if (pnlStats)
$(pnlStats).removeData('node-prop');
if (pnlDependencies)
$(pnlDependencies).removeData('node-prop');
if (pnlDependents)
$(pnlDependents).removeData('node-prop');
if(nodeData.success === 0) {
Notify.alert(gettext('Error'),
gettext(nodeData.errormsg)
);
}
if(nodeData.success === 0) {
Notify.alert(gettext('Error'),
gettext(nodeData.errormsg)
);
}
}
);
return;
}
// We need to release any existing view, before
// creating the new view.
if (view) {
// Release the view
view.remove({
data: true,
internal: true,
silent: true,
});
// Deallocate the view
view = null;
// Reset the data object
j.data('obj-view', null);
}
// Make sure the HTML element is empty.
j.empty();
that.header = $('<div></div>').addClass(
'pg-prop-header'
).appendTo(j);
that.footer = $('<div></div>').addClass(
'pg-prop-footer'
).appendTo(j);
let updateButtons = function(hasError, modified) {
let btnGroup = this.find('.pg-prop-btn-group'),
btnSave = btnGroup.find('button.btn-primary'),
btnReset = btnGroup.find('button.btn-secondary[type="reset"]');
if (hasError || !modified) {
btnSave.prop('disabled', true);
btnSave.attr('disabled', 'disabled');
} else {
btnSave.prop('disabled', false);
btnSave.removeAttr('disabled');
}
);
return;
if (!modified) {
btnReset.prop('disabled', true);
btnReset.attr('disabled', 'disabled');
} else {
btnReset.prop('disabled', false);
btnReset.removeAttr('disabled');
}
};
// Create a view to edit/create the properties in fieldsets
view = that.getView(item, action, content, data, 'dialog', updateButtons, j, onCancelFunc);
if (view) {
// Save it to release it later
j.data('obj-view', view);
self.icon(
_.isFunction(that['node_image']) ?
(that['node_image']).apply(that, [data, view.model]) :
(that['node_image'] || ('icon-' + that.type))
);
// Create proper buttons
let btn_grp = createButtons([{
label: '',
type: 'help',
tooltip: gettext('SQL help for this object type.'),
extraClasses: ['btn-primary-icon', 'pull-left', 'mx-1'],
icon: 'fa fa-info',
disabled: (that.sqlAlterHelp == '' && that.sqlCreateHelp == '' && !that.epasHelp) ? true : false,
register: function(btn) {
btn.on('click',() => {
onSqlHelp();
});
},
}, {
label: '',
type: 'help',
tooltip: gettext('Help for this dialog.'),
extraClasses: ['btn-primary-icon', 'pull-left', 'mx-1'],
icon: 'fa fa-question',
disabled: (that.dialogHelp == '') ? true : false,
register: function(btn) {
btn.on('click',() => {
onDialogHelp();
});
},
}, {
label: gettext('Cancel'),
type: 'cancel',
tooltip: gettext('Cancel changes to this object.'),
extraClasses: ['btn-secondary', 'mx-1'],
icon: 'fa fa-times',
disabled: false,
register: function(btn) {
btn.on('click',() => {
// Removing the action-mode
self.$container.removeAttr('action-mode');
onCancelFunc.call(true);
});
},
}, {
label: gettext('Reset'),
type: 'reset',
tooltip: gettext('Reset the fields on this dialog.'),
extraClasses: ['btn-secondary', 'mx-1'],
icon: 'fa fa-recycle',
disabled: true,
register: function(btn) {
btn.on('click',() => {
warnBeforeChangesLost.call(
self,
gettext('Changes will be lost. Are you sure you want to reset?'),
function() {
setTimeout(function() {
editFunc.call();
}, 0);
}
);
});
},
}, {
label: gettext('Save'),
type: 'save',
tooltip: gettext('Save this object.'),
extraClasses: ['btn-primary', 'mx-1'],
icon: 'fa fa-save',
disabled: true,
register: function(btn) {
// Save the changes
btn.on('click',() => {
warnBeforeAttributeChange.call(
self,
function() {
informBeforeAttributeChange.call(self, function(){
setTimeout(function() {
onSave.call(this, view, btn);
}, 0);
});
}
);
});
},
}], 'footer', 'pg-prop-btn-group-below');
btn_grp.on('keydown', 'button', function(event) {
if (!event.shiftKey && event.keyCode == 9 && $(this).nextAll('button:not([disabled])').length == 0) {
// set focus back to first focusable element on dialog
view.$el.closest('.wcFloating').find('[tabindex]:not([tabindex="-1"]').first().focus();
return false;
}
let btnGroup = $(self.$container.find('.pg-prop-btn-group'));
let el = $(btnGroup).find('button:first');
if (self.$container.find('.number-cell.editable:last').is(':visible')){
if (event.keyCode === 9 && event.shiftKey) {
if ($(el).is($(event.target))){
$(self.$container.find('td.editable:last').trigger('click'));
event.preventDefault();
event.stopPropagation();
}
}
}
});
setTimeout(function() {
pgBrowser.keyboardNavigation.getDialogTabNavigator(self.pgElContainer);
}, 200);
}
// Create status bar.
createStatusBar('footer');
// Add some space, so that - button group does not override the
// space
content.addClass('pg-prop-has-btn-group-below');
// Show contents before buttons
j.prepend(content);
// add required attributes to select2 input to resolve accessibility issue.
$('.select2-search__field').attr('aria-label', 'select2');
view.$el.closest('.wcFloating').find('.wcFrameButtonBar > .wcFrameButton[style!="display: none;"]').on('keydown', function(e) {
if(e.shiftKey && e.keyCode === 9) {
e.stopPropagation();
setTimeout(() => {
view.$el.closest('.wcFloating').find('[tabindex]:not([tabindex="-1"]):not([disabled])').last().focus();
}, 10);
}
});
}.bind(panel),
closePanel = function(confirm_close_flag) {
if(!_.isUndefined(confirm_close_flag)) {
@@ -1832,7 +1237,6 @@ define('pgadmin.browser.node', [
}]);
}, 0);
},
onCancelFunc = closePanel,
onSaveFunc = updateTreeItem.bind(panel, that),
onEdit = editFunc.bind(panel);

View File

@@ -1,564 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import _ from 'lodash';
define([
'sources/gettext', 'jquery', 'sources/pgadmin', 'backform',
'backgrid', 'select2', 'pgadmin.browser.node',
], function(gettext, $, pgAdmin, Backform, Backgrid) {
/*
* Define the selectAll adapter for select2.
*
* Reference:
* https://github.com/select2/select2/issues/195#issuecomment-240130634
*/
$.fn.select2.amd.define('select2/selectAllAdapter', [
'select2/utils',
'select2/dropdown',
'select2/dropdown/attachBody',
], function(Utils, Dropdown, AttachBody) {
function SelectAll() {/*This is intentional (SonarQube)*/}
SelectAll.prototype.render = function(decorated) {
let self = this;
let $rendered = decorated.call(this);
let $selectAll = $([
'<button class="btn btn-secondary btn-sm" type="button"',
' style="width: 49%;margin: 0 0.5%;">',
'<i class="fa fa-check-square" role="img"></i>',
'<span style="padding: 0px 5px;">',
gettext('Select All'),
'</span></button>',
].join(''));
let $unselectAll = $([
'<button class="btn btn-secondary btn-sm" type="button"',
' style="width: 49%;margin: 0 0.5%;">',
'<i class="fa fa-square"></i><span style="padding: 0px 5px;" role="img">',
gettext('Unselect All'),
'</span></button>',
].join(''));
let $btnContainer = $(
'<div class="select2-select-all-adapter-container">'
).append($selectAll).append($unselectAll);
if (!this.$element.prop('multiple')) {
// this isn't a multi-select -> don't add the buttons!
return $rendered;
}
$rendered.find('.select2-dropdown').prepend($btnContainer);
// Select All button click
$selectAll.on('click', function() {
$rendered.find('.select2-results__option[aria-selected=false]').each(
function() {
// Note: With latest version we do not get data in the data attribute of a element
// Hence as per new logic we will fetch the data from the cache created by Select2.
let data = Utils.GetData($(this)[0], 'data');
self.trigger('select', {
data: data,
});
}
);
self.trigger('close');
});
// Unselect All button click
$unselectAll.on('click', function() {
$rendered.find('.select2-results__option[aria-selected=true]').each(
function() {
let data = Utils.GetData($(this)[0], 'data');
self.trigger('unselect', {
data: data,
});
}
);
self.trigger('close');
});
return $rendered;
};
return Utils.Decorate(
Utils.Decorate(
Dropdown,
AttachBody
),
SelectAll
);
});
/*
* NodeAjaxOptionsControl
* This control will fetch the options required to render the select
* control, from the url specific to the pgAdmin.Browser node object.
*
* In order to use this properly, schema require to set the 'url' property,
* which exposes the data for this node.
*
* In case the url is not providing the data in proper format, we can
* specify the 'transform' function too, which will convert the fetched
* data to proper 'label', 'value' format.
*/
let NodeAjaxOptionsControl = Backform.NodeAjaxOptionsControl =
Backform.Select2Control.extend({
defaults: _.extend(Backform.Select2Control.prototype.defaults, {
url: undefined,
transform: undefined,
url_with_id: false,
select2: {
allowClear: true,
placeholder: gettext('Select an item...'),
width: 'style',
},
}),
initialize: function() {
/*
* Initialization from the original control.
*/
Backform.Select2Control.prototype.initialize.apply(this, arguments);
/*
* We're about to fetch the options required for this control.
*/
let self = this,
url = self.field.get('url') || self.defaults.url,
m = self.model.top || self.model,
url_jump_after_node = self.field.get('url_jump_after_node') || null;
// Hmm - we found the url option.
// That means - we needs to fetch the options from that node.
if (url) {
let node = this.field.get('schema_node'),
node_info = this.field.get('node_info'),
with_id = this.field.get('url_with_id') || false,
full_url = node.generate_url.apply(
node, [
null, url, this.field.get('node_data'), with_id, node_info, url_jump_after_node,
]),
cache_level,
cache_node = this.field.get('cache_node');
cache_node = (cache_node && pgAdmin.Browser.Nodes[cache_node]) || node;
if (this.field.has('cache_level')) {
cache_level = this.field.get('cache_level');
} else {
cache_level = cache_node.cache_level(node_info, with_id);
}
/*
* We needs to check, if we have already cached data for this url.
* If yes - use that, and do not bother about fetching it again,
* and use it.
*/
let data = cache_node.cache(node.type + '#' + url, node_info, cache_level);
if (this.field.get('version_compatible') &&
(_.isUndefined(data) || _.isNull(data))) {
m.trigger('pgadmin:view:fetching', m, self.field);
$.ajax({
async: false,
url: full_url,
})
.done(function(res) {
/*
* We will cache this data for short period of time for avoiding
* same calls.
*/
data = cache_node.cache(node.type + '#' + url, node_info, cache_level, res.data);
})
.fail(function() {
m.trigger('pgadmin:view:fetch:error', m, self.field);
});
m.trigger('pgadmin:view:fetched', m, self.field);
}
// To fetch only options from cache, we do not need time from 'at'
// attribute but only options.
//
// It is feasible that the data may not have been fetched.
data = (data && data.data) || [];
/*
* Transform the data
*/
let transform = this.field.get('transform') || self.defaults.transform;
if (transform && _.isFunction(transform)) {
// We will transform the data later, when rendering.
// It will allow us to generate different data based on the
// dependencies.
self.field.set('options', transform.bind(self, data));
} else {
self.field.set('options', data);
}
}
},
});
let formatNode = function(opt) {
if (!opt.id) {
return opt.text;
}
let optimage = $(opt.element).data('image');
if (!optimage) {
return opt.text;
} else {
return $('<span></span>').append(
$('<span></span>', {
class: 'wcTabIcon ' + optimage,
})
).append($('<span></span>').text(opt.text));
}
};
let filterRows = function(self, filter, rows, node) {
let res = [];
_.each(rows, function(r) {
if (filter(r)) {
let l = (_.isFunction(node['node_label']) ?
(node['node_label']).apply(node, [r, self.model, self]) :
r.label),
image = (_.isFunction(node['node_image']) ?
(node['node_image']).apply(
node, [r, self.model, self]
) :
(node['node_image'] || ('icon-' + node.type)));
res.push({
'value': r._id,
'image': image,
'label': l,
});
}
});
return res;
};
let NodeListByIdControl = Backform.NodeListByIdControl = NodeAjaxOptionsControl.extend({
controlClassName: 'pgadmin-node-select form-control',
defaults: _.extend({}, NodeAjaxOptionsControl.prototype.defaults, {
url: 'nodes',
filter: undefined,
transform: function(rows) {
let self = this,
node = self.field.get('schema_node'),
filter = self.field.get('filter') || function() {
return true;
};
filter = filter.bind(self);
return filterRows(self, filter, rows, node);
},
select2: {
allowClear: true,
placeholder: gettext('Select an item...'),
width: 'style',
templateResult: formatNode,
templateSelection: formatNode,
},
}),
});
Backform.NodeListByNameControl = NodeListByIdControl.extend({
defaults: _.extend({}, NodeListByIdControl.prototype.defaults, {
transform: function(rows) {
let self = this,
node = self.field.get('schema_node'),
res = [],
filter = self.field.get('filter') || function() {
return true;
};
filter = filter.bind(self);
_.each(rows, function(r) {
if (filter(r)) {
let l = (_.isFunction(node['node_label']) ?
(node['node_label']).apply(node, [r, self.model, self]) :
r.label),
image = (_.isFunction(node['node_image']) ?
(node['node_image']).apply(
node, [r, self.model, self]
) :
(node['node_image'] || ('icon-' + node.type)));
res.push({
'value': r.label,
'image': image,
'label': l,
});
}
});
return res;
},
}),
});
/*
* Global function to make visible particular dom element in it's parent
* with given class.
*/
$.fn.pgMakeVisible = function(cls) {
return this.each(function() {
if (!this || !$(this.length))
return;
let top, p = $(this),
hasScrollbar = function(j) {
if (j && j.length > 0) {
return j.get(0).scrollHeight > j.height();
}
return false;
};
// check if p is not empty
while (p && p.length > 0) {
top = p.get(0).offsetTop + p.height();
p = p.parent();
if (hasScrollbar(p)) {
p.scrollTop(top);
}
if (p.hasClass(cls)) //'backform-tab'
return;
}
});
};
/*
* Global function to make visible backgrid new row
*/
$.fn.pgMakeBackgridVisible = function(cls) {
return this.each(function() {
if (!this || !$(this.length))
return;
let elem = $(this),
backgridDiv = $(this).offsetParent().parent(), // Backgrid div.subnode
backgridDivTop = backgridDiv.offset().top,
backgridDivHeight = backgridDiv.height(),
backformTab = $(this).closest(cls), // Backform-tab
gridScroll = null;
if(backformTab.length == 0) {
return false;
}
gridScroll = backformTab[0].offsetHeight - backgridDivTop;
if (backgridDivHeight > gridScroll) {
let top = elem.get(0).offsetTop + elem.height();
backformTab.find('.tab-content').scrollTop(top);
}
return true;
});
};
/*
* NodeAjaxOptionsCell
* This cell will fetch the options required to render the select
* cell, from the url specific to the pgAdmin.Browser node object.
*
* In order to use this properly, schema require to set the 'url' property,
* which exposes the data for this node.
*
* In case the url is not providing the data in proper format, we can
* specify the 'transform' function too, which will convert the fetched
* data to proper 'label', 'value' format.
*/
let NodeAjaxOptionsCell = Backgrid.Extension.NodeAjaxOptionsCell = Backgrid.Extension.Select2Cell.extend({
defaults: _.extend({}, Backgrid.Extension.Select2Cell.prototype.defaults, {
url: undefined,
transform: undefined,
url_with_id: false,
select2: {
allowClear: true,
placeholder: gettext('Select an item...'),
width: 'style',
},
opt: {
label: null,
value: null,
image: null,
selected: false,
},
}),
template: _.template(
'<option <% if (image) { %> data-image=<%= image %> <% } %> value="<%- value %>" <%= selected ? \'selected="selected"\' : "" %>><%- label %></option>'
),
initialize: function() {
Backgrid.Extension.Select2Cell.prototype.initialize.apply(this, arguments);
let url = this.column.get('url') || this.defaults.url,
is_options_cached = _.has(this.column.attributes, 'options_cached'),
options_cached = is_options_cached && this.column.get('options_cached');
// Hmm - we found the url option.
// That means - we needs to fetch the options from that node.
if (url && !options_cached) {
let self = this,
m = this.model,
column = this.column,
eventHandler = m.top || m,
node = column.get('schema_node'),
node_info = column.get('node_info'),
with_id = column.get('url_with_id') || false,
url_jump_after_node = this.column.get('url_jump_after_node') || null,
full_url = node.generate_url.apply(
node, [
null, url, column.get('node_data'), with_id, node_info, url_jump_after_node,
]),
cache_level,
cache_node = column.get('cache_node');
cache_node = (cache_node && pgAdmin.Browser.Nodes[cache_node]) || node;
if (column.has('cache_level')) {
cache_level = column.get('cache_level');
} else {
cache_level = cache_node.cache_level(node_info, with_id);
}
/*
* We needs to check, if we have already cached data for this url.
* If yes - use that, and do not bother about fetching it again,
* and use it.
*/
let data = cache_node.cache(node.type + '#' + url, node_info, cache_level);
if (column.get('version_compatible') &&
(_.isUndefined(data) || _.isNull(data))) {
eventHandler.trigger('pgadmin:view:fetching', m, column);
$.ajax({
async: false,
url: full_url,
})
.done(function(res) {
/*
* We will cache this data for short period of time for avoiding
* same calls.
*/
data = cache_node.cache(node.type + '#' + url, node_info, cache_level, res.data);
})
.fail(function() {
eventHandler.trigger('pgadmin:view:fetch:error', m, column);
});
eventHandler.trigger('pgadmin:view:fetched', m, column);
}
// To fetch only options from cache, we do not need time from 'at'
// attribute but only options.
//
// It is feasible that the data may not have been fetched.
data = (data && data.data) || [];
/*
* Transform the data
*/
let transform = column.get('transform') || self.defaults.transform;
if (transform && _.isFunction(transform)) {
// We will transform the data later, when rendering.
// It will allow us to generate different data based on the
// dependencies.
column.set('options', transform.bind(column, data));
} else {
column.set('options', data);
}
if (is_options_cached) {
column.set('options_cached', true);
}
}
},
});
let transformFunc = function(rows, control) {
let self = control || this,
node = self.column.get('schema_node'),
filter = self.column.get('filter') || function() {
return true;
};
filter = filter.bind(self);
return filterRows(self, filter, rows, node);
};
Backgrid.Extension.NodeListByIdCell = NodeAjaxOptionsCell.extend({
controlClassName: 'pgadmin-node-select backgrid-cell',
defaults: _.extend({}, NodeAjaxOptionsCell.prototype.defaults, {
url: 'nodes',
filter: undefined,
transform: transformFunc,
select2: {
placeholder: gettext('Select an item...'),
width: 'style',
templateResult: formatNode,
templateSelection: formatNode,
},
}),
});
Backgrid.Extension.NodeListByNameCell = NodeAjaxOptionsCell.extend({
controlClassName: 'pgadmin-node-select backgrid-cell',
defaults: _.extend({}, NodeAjaxOptionsCell.prototype.defaults, {
url: 'nodes',
filter: undefined,
transform: transformFunc,
select2: {
placeholder: gettext('Select an item...'),
width: 'style',
templateResult: formatNode,
templateSelection: formatNode,
},
}),
});
// Extend the browser's node model class to create a option/value pair
Backgrid.Extension.MultiSelectAjaxCell = Backgrid.Extension.NodeAjaxOptionsCell.extend({
defaults: _.extend({}, NodeAjaxOptionsCell.prototype.defaults, {
transform: undefined,
url_with_id: false,
select2: {
allowClear: true,
placeholder: gettext('Select an item...'),
width: 'style',
multiple: true,
},
opt: {
label: null,
value: null,
image: null,
selected: false,
},
}),
getValueFromDOM: function() {
let res = [];
this.$el.find('select').find(':selected').each(function() {
res.push($(this).attr('value'));
});
return res;
},
});
/*
* Control to select multiple columns.
*/
Backform.MultiSelectAjaxControl = NodeAjaxOptionsControl.extend({
defaults: _.extend({}, NodeAjaxOptionsControl.prototype.defaults, {
select2: {
multiple: true,
allowClear: true,
width: 'style',
},
}),
});
return Backform;
});