Remove Backgrid and Backform. Fixes #6134

pull/5349/head
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

@ -208,7 +208,7 @@ Front End
pgAdmin uses javascript extensively for the front-end implementation. It uses pgAdmin uses javascript extensively for the front-end implementation. It uses
require.js to allow the lazy loading (or, say load only when required), require.js to allow the lazy loading (or, say load only when required),
bootstrap for UI look and feel, Backbone for data manipulation of a node, bootstrap for UI look and feel, Backbone for data manipulation of a node,
Backform for generating properties/create dialog for selected node. We have React for generating properties/create dialog for selected node. We have
divided each module in small chunks as much as possible. Not all javascript divided each module in small chunks as much as possible. Not all javascript
modules are required to be loaded (i.e. loading a javascript module for modules are required to be loaded (i.e. loading a javascript module for
database will make sense only when a server node is loaded completely.) Please database will make sense only when a server node is loaded completely.) Please

View File

@ -20,6 +20,7 @@ Housekeeping
************ ************
| `Issue #6133 <https://redmine.postgresql.org/issues/6133>`_ - Port schema diff to React. | `Issue #6133 <https://redmine.postgresql.org/issues/6133>`_ - Port schema diff to React.
| `Issue #6134 <https://redmine.postgresql.org/issues/6134>`_ - Remove Backgrid and Backform.
| `Issue #7343 <https://redmine.postgresql.org/issues/7343>`_ - Port the remaining components of the ERD Tool to React. | `Issue #7343 <https://redmine.postgresql.org/issues/7343>`_ - Port the remaining components of the ERD Tool to React.
| `Issue #7619 <https://redmine.postgresql.org/issues/7619>`_ - Remove Alertify from pgAdmin completely. | `Issue #7619 <https://redmine.postgresql.org/issues/7619>`_ - Remove Alertify from pgAdmin completely.
| `Issue #7622 <https://redmine.postgresql.org/issues/7622>`_ - Port search object dialog to React. | `Issue #7622 <https://redmine.postgresql.org/issues/7622>`_ - Port search object dialog to React.

View File

@ -97,9 +97,6 @@
"axios": "^0.21.1", "axios": "^0.21.1",
"babelify": "~10.0.0", "babelify": "~10.0.0",
"backbone": "1.4.0", "backbone": "1.4.0",
"backform": "^0.2.0",
"backgrid-filter": "^0.3.7",
"backgrid-select-all": "^0.3.5",
"bignumber.js": "^9.0.1", "bignumber.js": "^9.0.1",
"bootstrap": "^4.3.1", "bootstrap": "^4.3.1",
"bootstrap-datepicker": "^1.8.0", "bootstrap-datepicker": "^1.8.0",

View File

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

View File

@ -14,8 +14,8 @@ import _ from 'lodash';
define('pgadmin.node.user_mapping', [ define('pgadmin.node.user_mapping', [
'sources/gettext', 'sources/url_for', 'sources/gettext', 'sources/url_for',
'sources/pgadmin', 'pgadmin.browser', 'sources/pgadmin', 'pgadmin.browser',
'pgadmin.backform', 'pgadmin.browser.collection', 'pgadmin.browser.collection',
], function(gettext, url_for, pgAdmin, pgBrowser, Backform) { ], function(gettext, url_for, pgAdmin, pgBrowser) {
// Extend the browser's collection class for user mapping collection // Extend the browser's collection class for user mapping collection
if (!pgBrowser.Nodes['coll-user_mapping']) { if (!pgBrowser.Nodes['coll-user_mapping']) {
@ -105,34 +105,6 @@ define('pgadmin.node.user_mapping', [
} }
pgAdmin.Browser.Node.Model.prototype.initialize.apply(this, arguments); 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', [ define('pgadmin.node.foreign_data_wrapper', [
'sources/gettext', 'sources/url_for', 'pgadmin.browser', 'sources/gettext', 'sources/url_for', 'pgadmin.browser',
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege', 'pgadmin.browser.collection',
], function(gettext, url_for, pgBrowser) { ], function(gettext, url_for, pgBrowser) {
// Extend the browser's collection class for foreign data wrapper collection // 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', [ define('pgadmin.node.language', [
'sources/gettext', 'sources/url_for', 'pgadmin.browser', 'sources/gettext', 'sources/url_for', 'pgadmin.browser',
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege', 'pgadmin.browser.collection',
], function(gettext, url_for, pgBrowser) { ], function(gettext, url_for, pgBrowser) {
// Extend the browser's collection class for languages collection // Extend the browser's collection class for languages collection

View File

@ -12,7 +12,7 @@ import PublicationSchema from './publication.ui';
define('pgadmin.node.publication', [ define('pgadmin.node.publication', [
'sources/gettext', 'sources/url_for', 'pgadmin.browser', 'sources/gettext', 'sources/url_for', 'pgadmin.browser',
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege', 'pgadmin.browser.collection',
], function(gettext, url_for, pgBrowser) { ], function(gettext, url_for, pgBrowser) {
// Extend the browser's collection class for publications collection // 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', [ define('pgadmin.node.function', [
'sources/gettext', 'sources/url_for', 'pgadmin.browser', 'sources/gettext', 'sources/url_for', 'pgadmin.browser',
'pgadmin.node.schema.dir/child', 'pgadmin.node.schema.dir/schema_child_tree_node', 'pgadmin.node.schema.dir/child', 'pgadmin.node.schema.dir/schema_child_tree_node',
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege', 'pgadmin.browser.collection',
], function( ], function(
gettext, url_for, pgBrowser, schemaChild, schemaChildTreeNode gettext, url_for, pgBrowser, schemaChild, schemaChildTreeNode
) { ) {

View File

@ -17,7 +17,7 @@ define('pgadmin.node.procedure', [
'sources/gettext', 'sources/url_for', 'sources/gettext', 'sources/url_for',
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.node.schema.dir/child', 'sources/pgadmin', 'pgadmin.browser', 'pgadmin.node.schema.dir/child',
'pgadmin.node.schema.dir/schema_child_tree_node', '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) { ], function(gettext, url_for, pgAdmin, pgBrowser, schemaChild, schemaChildTreeNode) {
if (!pgBrowser.Nodes['coll-procedure']) { if (!pgBrowser.Nodes['coll-procedure']) {

View File

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

View File

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

View File

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

View File

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

View File

@ -10,297 +10,11 @@
import PGSchema from './schema.ui'; import PGSchema from './schema.ui';
import { getNodePrivilegeRoleSchema } from '../../../../static/js/privilege.ui'; import { getNodePrivilegeRoleSchema } from '../../../../static/js/privilege.ui';
import { getNodeListByName } from '../../../../../../static/js/node_ajax'; import { getNodeListByName } from '../../../../../../static/js/node_ajax';
import _ from 'lodash';
define('pgadmin.node.schema', [ define('pgadmin.node.schema', [
'sources/gettext', 'sources/url_for', 'jquery', 'sources/gettext', 'sources/url_for', 'jquery',
'pgadmin.browser', 'pgadmin.backform', 'pgadmin.backgrid', 'pgadmin.browser', 'pgadmin.browser.collection',
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege', ], function(gettext, url_for, $, pgBrowser) {
], 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'],
}];
// Extend the browser's collection class for schema collection // Extend the browser's collection class for schema collection
if (!pgBrowser.Nodes['coll-schema']) { 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']; return pgBrowser.Nodes['schema'];
}); });

View File

@ -13,9 +13,9 @@ import _ from 'lodash';
define('pgadmin.node.primary_key', [ define('pgadmin.node.primary_key', [
'sources/gettext', 'sources/url_for', 'sources/gettext', 'sources/url_for',
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform', 'pgadmin.backgrid', 'sources/pgadmin', 'pgadmin.browser',
'pgadmin.browser.collection', '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 // Extend the browser's node class for index constraint node
if (!pgBrowser.Nodes['primary_key']) { if (!pgBrowser.Nodes['primary_key']) {
@ -166,447 +166,6 @@ define('pgadmin.node.primary_key', [
// We can't update columns of existing index constraint. // We can't update columns of existing index constraint.
return !m.isNew(); 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() { validate: function() {
this.errorModel.clear(); this.errorModel.clear();
// Clear parent's error as well // Clear parent's error as well

View File

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

View File

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

View File

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

View File

@ -11,9 +11,8 @@ import { getNodePgaJobStepSchema } from './pga_jobstep.ui';
import _ from 'lodash'; import _ from 'lodash';
define('pgadmin.node.pga_jobstep', [ define('pgadmin.node.pga_jobstep', [
'sources/gettext', 'sources/url_for', 'pgadmin.browser', 'backform', 'sources/gettext', 'sources/url_for', 'pgadmin.browser',
'backgrid', 'pgadmin.backform', ], function(gettext, url_for, pgBrowser) {
], function(gettext, url_for, pgBrowser, Backform) {
if (!pgBrowser.Nodes['coll-pga_jobstep']) { if (!pgBrowser.Nodes['coll-pga_jobstep']) {
pgBrowser.Nodes['coll-pga_jobstep'] = pgBrowser.Nodes['coll-pga_jobstep'] =
@ -93,37 +92,6 @@ define('pgadmin.node.pga_jobstep', [
} }
}, },
idAttribute: 'jstid', 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', 'sources/pgadmin', 'pgadmin.browser',
'pgadmin.user_management.current_user', 'pgadmin.user_management.current_user',
'pgadmin.authenticate.kerberos', 'pgadmin.authenticate.kerberos',
'pgadmin.browser.server.privilege',
], function( ], function(
gettext, url_for, $, pgAdmin, pgBrowser, gettext, url_for, $, pgAdmin, pgBrowser,
current_user, Kerberos, 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', [ define('pgadmin.node.tablespace', [
'sources/gettext', 'sources/url_for', 'sources/gettext', 'sources/url_for',
'pgadmin.browser', 'pgadmin.browser.collection', 'pgadmin.browser.node.ui', 'pgadmin.browser', 'pgadmin.browser.collection',
'pgadmin.browser.server.privilege',
], function( ], function(
gettext, url_for, pgBrowser gettext, url_for, pgBrowser
) { ) {

View File

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

View File

@ -10,17 +10,16 @@
import {getNodeView, removeNodeView} from './node_view'; import {getNodeView, removeNodeView} from './node_view';
import Notify from '../../../static/js/helpers/Notifier'; import Notify from '../../../static/js/helpers/Notifier';
import _ from 'lodash'; import _ from 'lodash';
import { pgHandleItemError } from '../../../static/js/utils';
define('pgadmin.browser.node', [ define('pgadmin.browser.node', [
'sources/gettext', 'jquery', 'sources/pgadmin', 'sources/gettext', 'jquery', 'sources/pgadmin',
'backbone', 'pgadmin.browser.datamodel', 'backbone', 'pgadmin.browser.datamodel',
'backform', 'sources/browser/generate_url', 'pgadmin.help', 'sources/utils', 'sources/browser/generate_url', 'pgadmin.help', 'sources/utils',
'pgadmin.browser.utils', 'pgadmin.backform', 'pgadmin.browser.utils',
], function( ], function(
gettext, $, pgAdmin, gettext, $, pgAdmin,
Backbone, pgBrowser, Backbone, pgBrowser,
Backform, generateUrl, help, generateUrl, help,
commonUtils commonUtils
) { ) {
@ -294,210 +293,6 @@ define('pgadmin.browser.node', [
return true; 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) { addUtilityPanel: function(width, height, docker) {
let body = window.document.body, let body = window.document.body,
el = document.createElement('div'), el = document.createElement('div'),
@ -1182,8 +977,6 @@ define('pgadmin.browser.node', [
tree = pgAdmin.Browser.tree, tree = pgAdmin.Browser.tree,
j = panel.$container.find('.obj_properties').first(), j = panel.$container.find('.obj_properties').first(),
view = j.data('obj-view'), view = j.data('obj-view'),
content = $('<div></div>')
.addClass('pg-prop-content col-12'),
confirm_close = true; confirm_close = true;
// Handle key press events for Cancel, save and help button // 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 }, 200); // wait for panel tab to render
// Template function to create the status bar // Callback to show object properties
let createStatusBar = function(location) { let properties = function() {
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() {
// Avoid unnecessary reloads // Avoid unnecessary reloads
let i = tree.selected(), let i = tree.selected(),
d = i && tree.itemData(i),
treeHierarchy = tree.getTreeNodeHierarchy(i); treeHierarchy = tree.getTreeNodeHierarchy(i);
// Cache the current IDs for next time // Cache the current IDs for next time
@ -1291,106 +1025,11 @@ define('pgadmin.browser.node', [
/* Remove any dom rendered by getNodeView */ /* Remove any dom rendered by getNodeView */
removeNodeView(j[0]); removeNodeView(j[0]);
if(that.getSchema) { let treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item);
let treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item); getNodeView(
getNodeView( that.type, treeNodeInfo, 'properties', data, 'tab', j[0], this, onEdit
that.type, treeNodeInfo, 'properties', data, 'tab', j[0], this, onEdit );
); return;
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');
}.bind(panel), }.bind(panel),
onDialogHelp = function() { onDialogHelp = function() {
@ -1427,52 +1066,6 @@ define('pgadmin.browser.node', [
} }
}.bind(panel), }.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) { onSave = function(_view, saveBtn) {
let m = _view.model, let m = _view.model,
d = m.toJSON(true), d = m.toJSON(true),
@ -1547,229 +1140,41 @@ define('pgadmin.browser.node', [
/* Remove any dom rendered by getNodeView */ /* Remove any dom rendered by getNodeView */
removeNodeView(j[0]); removeNodeView(j[0]);
/* getSchema is a schema for React. Get the react node view */ /* getSchema is a schema for React. Get the react node view */
if(that.getSchema) { let treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item);
let treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item); getNodeView(
getNodeView( that.type, treeNodeInfo, action, data, 'dialog', j[0], this, onEdit,
that.type, treeNodeInfo, action, data, 'dialog', j[0], this, onEdit, (nodeData)=>{
(nodeData)=>{ if(nodeData.node) {
if(nodeData.node) { onSaveFunc(nodeData.node, treeNodeInfo);
onSaveFunc(nodeData.node, treeNodeInfo); // Removing the node-prop property of panel
// Removing the node-prop property of panel // so that we show updated data on panel
// so that we show updated data on panel let pnlProperties = pgBrowser.docker.findPanels('properties')[0],
let pnlProperties = pgBrowser.docker.findPanels('properties')[0], pnlSql = pgBrowser.docker.findPanels('sql')[0],
pnlSql = pgBrowser.docker.findPanels('sql')[0], pnlStats = pgBrowser.docker.findPanels('statistics')[0],
pnlStats = pgBrowser.docker.findPanels('statistics')[0], pnlDependencies = pgBrowser.docker.findPanels('dependencies')[0],
pnlDependencies = pgBrowser.docker.findPanels('dependencies')[0], pnlDependents = pgBrowser.docker.findPanels('dependents')[0];
pnlDependents = pgBrowser.docker.findPanels('dependents')[0];
if (pnlProperties) if (pnlProperties)
$(pnlProperties).removeData('node-prop'); $(pnlProperties).removeData('node-prop');
if (pnlSql) if (pnlSql)
$(pnlSql).removeData('node-prop'); $(pnlSql).removeData('node-prop');
if (pnlStats) if (pnlStats)
$(pnlStats).removeData('node-prop'); $(pnlStats).removeData('node-prop');
if (pnlDependencies) if (pnlDependencies)
$(pnlDependencies).removeData('node-prop'); $(pnlDependencies).removeData('node-prop');
if (pnlDependents) if (pnlDependents)
$(pnlDependents).removeData('node-prop'); $(pnlDependents).removeData('node-prop');
if(nodeData.success === 0) { if(nodeData.success === 0) {
Notify.alert(gettext('Error'), Notify.alert(gettext('Error'),
gettext(nodeData.errormsg) 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), }.bind(panel),
closePanel = function(confirm_close_flag) { closePanel = function(confirm_close_flag) {
if(!_.isUndefined(confirm_close_flag)) { if(!_.isUndefined(confirm_close_flag)) {
@ -1832,7 +1237,6 @@ define('pgadmin.browser.node', [
}]); }]);
}, 0); }, 0);
}, },
onCancelFunc = closePanel,
onSaveFunc = updateTreeItem.bind(panel, that), onSaveFunc = updateTreeItem.bind(panel, that),
onEdit = editFunc.bind(panel); 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;
});

View File

@ -30,7 +30,7 @@
display: block; display: block;
} }
/* CSS to make subnode control look preety in backgrid - START */ /* CSS to make subnode control look pretty - START */
.dashboard-tab-container .subnode-dialog .form-control { .dashboard-tab-container .subnode-dialog .form-control {
font-size: inherit; font-size: inherit;
} }

View File

@ -11,7 +11,7 @@ import Notify from '../../../static/js/helpers/Notifier';
define('pgadmin.settings', [ define('pgadmin.settings', [
'jquery', 'sources/pgadmin', 'jquery', 'sources/pgadmin',
'sources/gettext', 'sources/url_for', 'pgadmin.backform', 'sources/gettext', 'sources/url_for',
], function($, pgAdmin, gettext, url_for) { ], function($, pgAdmin, gettext, url_for) {
// This defines the Preference/Options Dialog for pgAdmin IV. // This defines the Preference/Options Dialog for pgAdmin IV.

View File

@ -2,7 +2,6 @@
@import 'node_modules/bootstrap-datepicker/dist/css/bootstrap-datepicker3.css'; @import 'node_modules/bootstrap-datepicker/dist/css/bootstrap-datepicker3.css';
@import 'node_modules/tempusdominus-bootstrap-4/build/css/tempusdominus-bootstrap-4.css'; @import 'node_modules/tempusdominus-bootstrap-4/build/css/tempusdominus-bootstrap-4.css';
@import 'node_modules/bootstrap4-toggle/css/bootstrap4-toggle.min.css'; @import 'node_modules/bootstrap4-toggle/css/bootstrap4-toggle.min.css';
@import 'node_modules/backgrid-filter/backgrid-filter.css';
@import 'node_modules/jquery-contextmenu/dist/jquery.contextMenu.css'; @import 'node_modules/jquery-contextmenu/dist/jquery.contextMenu.css';
@import 'node_modules/webcabin-docker/Build/wcDocker.css'; @import 'node_modules/webcabin-docker/Build/wcDocker.css';
@import 'node_modules/select2/dist/css/select2.min.css'; @import 'node_modules/select2/dist/css/select2.min.css';
@ -12,9 +11,6 @@
@import 'node_modules/codemirror/addon/dialog/dialog.css'; @import 'node_modules/codemirror/addon/dialog/dialog.css';
@import 'node_modules/codemirror/addon/scroll/simplescrollbars.css'; @import 'node_modules/codemirror/addon/scroll/simplescrollbars.css';
@import '../vendor/backgrid/backgrid.css';
@import '../vendor/backgrid/backgrid-select-all.css';
@import 'node_modules/xterm/css/xterm.css'; @import 'node_modules/xterm/css/xterm.css';
@import 'node_modules/jsoneditor/dist/jsoneditor.min.css'; @import 'node_modules/jsoneditor/dist/jsoneditor.min.css';

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -89,8 +89,8 @@ var initBrowserTree = async (pgBrowser) => {
await treeModelX.root.ensureLoaded(); await treeModelX.root.ensureLoaded();
var _height = undefined; var _height = undefined;
document.getElementsByClassName('wcLayoutPane').forEach((item, index) => { [...document.getElementsByClassName('wcLayoutPane')].forEach((item, index) => {
if (index > 0 && $(item).find('#tree').length == 1) { if ($(item).find('#tree').length == 1) {
_height = item.clientHeight - 30; _height = item.clientHeight - 30;
} }
}); });

View File

@ -1,65 +0,0 @@
.has-error {
.pgadmin-controls .form-control {
border-color: $color-danger-light;
}
.form-control {
border-color: $color-danger;
}
.control-label {
color: $color-danger;
}
.control-label::before {
font: normal normal normal 16px/1 $font-family-icon;
content: "\f071";
font-weight: 900;
text-decoration: inherit;
position: absolute;
color: $color-danger;
right: 0.5rem;
z-index: 1;
line-height: inherit;
}
}
.pgadmin-file-has-error {
.pgadmin-controls:before {
right: 40px !important;
}
}
.pgadmin-datepicker-has-error {
.pgadmin-controls:before {
right: 50px !important;
z-index: 3;
}
}
.backform-tab .tab-pane {
padding: 0.5rem;
&.SQL, &.sql-code-control {
height: 100%;
padding: 0px;
& .pgadmin-controls.SQL {
padding: 0px;
}
}
}
.backform-note {
border-radius: $border-radius;
padding: .25rem;
word-wrap: break-word;
background: $border-color;
color: $color-fg;
}
.subnode-header label {
max-width: 90% !important;
}
.set-group, .accordian-group {
margin-bottom: $form-group-margin-bottom;
}

View File

@ -1,397 +0,0 @@
.backgrid th, .backgrid td {
text-align: left;
line-height: $line-height-base;
}
.backgrid td {
font-weight: normal!important;
&.editable {
& > .display-text {
overflow: hidden;
text-overflow: ellipsis;
@extend .form-control;
}
}
}
.backgrid.table th.sortable > button {
width: 100%;
text-align: left;
cursor: pointer;
}
.backgrid .ascending .sort-caret {
border-bottom-color: $color-fg;
}
.backgrid .descending .sort-caret {
border-top-color: $color-fg;
}
.backgrid.backgrid-striped tbody {
& tr:nth-child(even) {
background: $table-bg;
& td.editor {
background: $table-bg;
outline: none;
}
}
& tr:nth-child(odd) {
background: $table-bg;
& td.editor {
background: $table-bg;
outline: none;
}
}
}
.backgrid tbody tr.empty td {
display: table-cell;
font-style: normal;
color: $text-muted;
}
.backgrid .textarea-cell {
text-align: left;
}
.backgrid .textarea-cell.editor textarea {
width: 100%;
height: auto;
}
/* Override Backgrid's default z-index */
.dashboard-tab-container .backgrid-filter .search {
z-index: 10 !important;
}
.dashboard-tab-container .backgrid-filter .clear {
z-index: 10 !important;
}
.backgrid td.editor {
& input[type=text], & input[type=password] {
@extend .form-control;
}
}
.backgrid .string-cell.editor input[type=password] {
text-align: left;
}
.backgrid > tbody > td.editor input[type=password] {
display: block;
width: 100%;
height: 100%;
padding: 0 5px;
margin: 0;
background-color: transparent;
border: 0;
outline: 0;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
-webkit-appearance: none;
-moz-appearance: none;
}
.backgrid {
border-radius: inherit;
}
.backgrid thead td,
.backgrid thead th{
background: $table-bg;
background-color: $table-bg !important;
text-align: left;
}
.backgrid:not(.presentation) td.renderable:not(.editable):not(.delete-cell) {
// if transparent border then setting color will help
border-bottom-color: $color-gray-lighter;
}
.backgrid tr.header td.renderable:not(.editable):not(.delete-cell) {
background-color: $color-gray-light;
}
.sql-editor-grid-container .backgrid > thead > th.renderable,
.sql-editor-grid-container .backgrid > tbody > td.renderable {
white-space: pre-wrap;
}
.backgrid > thead > th.renderable {
font-weight: bold;
}
table.backgrid {
position: initial;
overflow: auto;
}
.backgrid td.editor input[type=password] {
display: block;
width: 100%;
height: 100%;
padding: 0 5px;
margin: 0;
background-color: transparent;
border: 0;
outline: 0;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
-webkit-appearance: none;
-moz-appearance: none;
}
.backgrid td.editor input[type=password]::-ms-clear {
display: none;
}
.enable-selection, .form-control, .backgrid td, .ajs-content {
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
}
/* Latest backgrid adds column name like `label` to td element, override color*/
.backgrid td.label {
color: $color-fg;
font-size: $font-size-base;
font-weight: normal;
}
.backgrid tr:hover {
.label {
color: $grid-hover-fg-color;
}
}
.subnode {
border: $panel-border;
background: $color-bg;
}
.subnode-noouter-border {
border: none !important;
}
.subnode > table.backgrid > thead > tr > th:last-child {
border-right-color: $color-primary;
}
.subnode > table.backgrid {
width: 100%;
margin: 0px;
padding: 0;
}
.subnode-header button {
border-left: 1px solid $border-color;
border-bottom-width: 0px !important;
border-top-width: 0px !important;
border-right-width: 0px !important;
border-radius: 0px !important;
text-align: center !important;
padding: 8px 8px !important;
min-height: 31px !important;
margin: 0px !important;
}
.subnode-header .control-label {
min-height: 31px;
font-weight: bold;
}
.subnode-footer {
text-align: right;
border-color: $color-gray-light;
border-style: inset inset inset solid;
border-width: 2px 1px 0;
margin: 0px, 15px;
margin-top: -10px;
height: 38px;
min-height: 40px;
vertical-align: bottom;
}
.subnode-footer .ajs-button {
margin: 2px 2px 0;
}
/* Sub-Node */
.edit-cell, .delete-cell {
text-align:center !important;
width:25px;
height:29px !important;
}
.subnode-header {
background-color: $header-bg;
color: $color-fg;
border-bottom: $panel-border;
}
.subnode-header > button.add {
float: right;
margin-right: 3px;
margin-bottom: 3px;
margin-top: 5px;
}
.subnode-dialog {
bottom: 0;
left: 0;
overflow-x: auto;
overflow-y: auto;
right: 0;
height: auto;
margin-top: 0px;
}
.subnode-body {
height: auto;
overflow: inherit;
background-color: $color-bg;
border: 1px solid $border-color;
border-radius: $panel-border-radius;
& .tab-pane {
padding: 0.5rem;
}
& .tab-content {
padding: 0px;
}
fieldset.inline-fieldset {
background: $color-bg;
}
}
.sub-node-form {
height: auto;
padding-left: 0rem !important;
padding-right: 0rem !important;
}
.sub-node-form > ul.tab-content{
background-color: $color-bg;
padding-left: 15px;
left: 1px;
}
.subnode-header-form {
background-color: $color-gray-lighter;
padding: 7px;
border-bottom: $panel-border;
}
.subnode-header-form button.add {
float: right;
margin-right: 0px;
}
.subnode-error .help-block {
color: $color-danger;
}
.backgrid tbody {
& td.edit-cell.editor {
background-color: $color-gray-light !important;
border-bottom-color: $color-gray-light !important;
outline: none !important;
}
& td.editor {
background-color: $color-bg !important;
color: $color-fg !important;
& input {
color: $color-fg !important;
}
}
& td.edit-cell.editor:focus {
outline: $input-focus-border-color auto 5px !important;
}
& td.select-cell.editor select{
outline: none !important;
height: 100% !important;
width: 100% !important;
}
tr.editor-row {
background-color: $color-gray-light !important;
& > td {
padding: 15px !important;
background-color: $color-gray-light !important;
border-top: none !important;
border-bottom-color: $color-gray-light !important;
}
}
tr.warning {
background-color: $color-warning !important;
}
tr.alert {
background-color: $color-danger !important;
}
}
table tr th button {
font-weight: bold;
}
.custom-control.custom-checkbox.custom-checkbox-no-label {
padding-left: 0;
.custom-control-label {
// set same width/height as before and after
width: 1em;
height: 1em;
&::before,
&::after {
left: 0;
}
}
}
.backgrid .sql-cell .CodeMirror-scroll {
overflow: hidden !important;
}
.backgrid .sql-cell .cm-s-default {
height: 50px !important;
}
.backgrid .sql-cell .CodeMirror-hscrollbar > div {
width: 100% !important;
}
.backgrid .sql-cell .CodeMirror-hscrollbar {
overflow-x: hidden !important;
}
.backgrid .sql-cell .CodeMirror-vscrollbar {
overflow-y: hidden !important;
}
.backgrid .sql-cell .CodeMirror-sizer {
padding-bottom: 0 !important;
}
.backgrid .sql-cell .CodeMirror-scrollbar-filler {
display: none !important;
}

View File

@ -20,12 +20,10 @@ $theme-colors: (
@import "bootstrap/scss/bootstrap"; @import "bootstrap/scss/bootstrap";
@import 'webcabin.pgadmin'; @import 'webcabin.pgadmin';
@import 'bootstrap.overrides'; @import 'bootstrap.overrides';
@import 'backgrid.overrides';
@import 'tree.overrides'; @import 'tree.overrides';
@import 'select2.overrides'; @import 'select2.overrides';
@import 'codemirror.overrides'; @import 'codemirror.overrides';
@import 'alert'; @import 'alert';
@import 'backform.overrides';
@import 'pgadmin.grid'; @import 'pgadmin.grid';
@import 'pgadmin.style'; @import 'pgadmin.style';
@import 'bootstrap4-toggle.overrides'; @import 'bootstrap4-toggle.overrides';

View File

@ -1,658 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
/*
Backform
http://github.com/amiliaapp/backform
Copyright (c) 2014 Amilia Inc.
Written by Martin Drapeau
Licensed under the MIT @license
*/
(function(root, factory) {
// Set up Backform appropriately for the environment. Start with AMD.
if (typeof define === 'function' && define.amd) {
define(['underscore', 'jquery', 'backbone'], function(underscore, $, Backbone) {
// Export global even in AMD case in case this script is loaded with
// others that may still expect a global Backform.
return factory(root, underscore, $, Backbone);
});
// Next for Node.js or CommonJS. jQuery may not be needed as a module.
} else if (typeof exports !== 'undefined') {
var _ = require('underscore');
factory(root, _, (root.jQuery || root.$ || root.Zepto || root.ender), root.Backbone);
// Finally, as a browser global.
} else {
factory(root, root._, (root.jQuery || root.Zepto || root.ender || root.$), root.Backbone);
}
} (this, function(root, _, $, Backbone) {
// Backform namespace and global options
Backform = root.Backform = {
// HTML markup global class names. More can be added by individual controls
// using _.extend. Look at RadioControl as an example.
formClassName: "backform form-inline",
groupClassName: "form-group row",
controlLabelClassName: "col-sm-2 col-form-label",
controlContainerClassName: "col-sm-10",
controlsClassName: "col-sm-8",
controlClassName: "form-control",
controlFormInlineClassName: "form-inline",
helpClassName: "form-text",
errorClassName: "has-error",
helpMessageClassName: "form-text text-muted",
hiddenClassName: "d-none",
requiredInputClassName: undefined,
// https://github.com/wyuenho/backgrid/blob/master/lib/backgrid.js
resolveNameToClass: function(name, suffix) {
if (_.isString(name)) {
var key = _.map(name.split('-'), function(e) {
return e.slice(0, 1).toUpperCase() + e.slice(1);
}).join('') + suffix;
var klass = Backform[key];
if (_.isUndefined(klass)) {
throw new ReferenceError("Class '" + key + "' not found");
}
return klass;
}
return name;
}
};
// Backform Form view
// A collection of field models.
Backform.Form = Backbone.View.extend({
fields: undefined,
errorModel: undefined,
tagName: "form",
className: function() {
return Backform.formClassName;
},
initialize: function(options) {
if (!(options.fields instanceof Backbone.Collection))
options.fields = new Fields(options.fields || this.fields);
this.fields = options.fields;
this.model.errorModel = options.errorModel || this.model.errorModel || new Backbone.Model();
this.controls = [];
},
cleanup: function() {
_.each(this.controls, function(c) {
c.remove();
});
this.controls.length = 0;
},
remove: function() {
/* First do the clean up */
this.cleanup();
Backbone.View.prototype.remove.apply(this, arguments);
},
render: function() {
this.cleanup();
this.$el.empty();
var $form = this.$el,
model = this.model,
controls = this.controls;
this.fields.each(function(field) {
var control = new (field.get("control"))({
field: field,
model: model
});
$form.append(control.render().$el);
controls.push(control);
});
return this;
}
});
// Converting data to/from Model/DOM.
// Stolen directly from Backgrid's CellFormatter.
// Source: http://backgridjs.com/ref/formatter.html
/**
Just a convenient class for interested parties to subclass.
The default Cell classes don't require the formatter to be a subclass of
Formatter as long as the fromRaw(rawData) and toRaw(formattedData) methods
are defined.
@abstract
@class Backform.ControlFormatter
@constructor
*/
var ControlFormatter = Backform.ControlFormatter = function() {/*This is intentional (SonarQube)*/};
_.extend(ControlFormatter.prototype, {
/**
Takes a raw value from a model and returns an optionally formatted string
for display. The default implementation simply returns the supplied value
as is without any type conversion.
@member Backform.ControlFormatter
@param {*} rawData
@param {Backbone.Model} model Used for more complicated formatting
@return {*}
*/
fromRaw: function (rawData, model) {
return rawData;
},
/**
Takes a formatted string, usually from user input, and returns a
appropriately typed value for persistence in the model.
If the user input is invalid or unable to be converted to a raw value
suitable for persistence in the model, toRaw must return `undefined`.
@member Backform.ControlFormatter
@param {string} formattedData
@param {Backbone.Model} model Used for more complicated formatting
@return {*|undefined}
*/
toRaw: function (formattedData, model) {
return formattedData;
}
});
// Store value in DOM as stringified JSON.
var JSONFormatter = Backform.JSONFormatter = function() {/*This is intentional (SonarQube)*/};
_.extend(JSONFormatter.prototype, {
fromRaw: function(rawData, model) {
return JSON.stringify(rawData);
},
toRaw: function(formattedData, model) {
return JSON.parse(formattedData);
}
});
// Field model and collection
//
// A field maps a model attriute to a control for rendering and capturing
// user input.
var Field = Backform.Field = Backbone.Model.extend({
defaults: {
// Name of the model attribute
// - It accepts "." nested path (e.g. x.y.z)
name: "",
// Placeholder for the input
placeholder: "",
// Disable the input control
// (Optional - true/false/function returning boolean)
// (Default Value: false)
disabled: false,
// Make the input control readonly
// readonly control can receive focus whereas disabled cannot
// (Optional - true/false/function returning boolean)
// (Default Value: false)
readonly: false,
// Visible
// (Optional - true/false/function returning boolean)
// (Default Value: true)
visible: true,
// Value Required (validation)
// (Optional - true/false/function returning boolean)
// (Default Value: true)
required: false,
// Default value for the field
// (Optional)
value: undefined,
// Control or class name for the control representing this field
control: undefined,
formatter: undefined
},
initialize: function(attributes, options) {
var control = Backform.resolveNameToClass(this.get("control"), "Control");
this.set({control: control}, {silent: true});
}
});
var Fields = Backform.Fields = Backbone.Collection.extend({
model: Field
});
// Base Control class
var Control = Backform.Control = Backbone.View.extend({
// Additional field defaults
defaults: {},
className: function() {
return Backform.groupClassName;
},
events: {
'keydown :input': 'processTab'
},
template: _.template([
'<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
'<div class="<%=Backform.controlContainerClassName%>">',
' <span readonly class="<%=Backform.controlClassName%>" value="<%=value%>">',
'</div>'
].join("\n")),
initialize: function(options) {
// Back-reference to the field
this.field = options.field;
var formatter = Backform.resolveNameToClass(this.field.get("formatter") || this.formatter, "Formatter");
if (!_.isFunction(formatter.fromRaw) && !_.isFunction(formatter.toRaw)) {
formatter = new formatter();
}
this.formatter = formatter;
var attrArr = this.field.get('name').split('.');
var name = attrArr.shift();
// Listen to the field in the model for any change
this.listenTo(this.model, "change:" + name, this.render);
// Listen for the field in the error model for any change
if (this.model.errorModel instanceof Backbone.Model)
this.listenTo(this.model.errorModel, "change:" + name, this.updateInvalid);
},
formatter: ControlFormatter,
getValueFromDOM: function() {
return this.formatter.toRaw(this.$el.find(".uneditable-input").text(), this.model);
},
onChange: function(e) {
var model = this.model,
attrArr = this.field.get("name").split('.'),
name = attrArr.shift(),
path = attrArr.join('.'),
value = this.getValueFromDOM(),
changes = {};
if (this.model.errorModel instanceof Backbone.Model) {
if (_.isEmpty(path)) {
this.model.errorModel.unset(name);
} else {
var nestedError = this.model.errorModel.get(name);
if (nestedError) {
this.keyPathSetter(nestedError, path, null);
this.model.errorModel.set(name, nestedError);
}
}
}
changes[name] = _.isEmpty(path) ? value : _.clone(model.get(name)) || {};
if (!_.isEmpty(path)) this.keyPathSetter(changes[name], path, value);
this.stopListening(this.model, "change:" + name, this.render);
model.set(changes);
this.listenTo(this.model, "change:" + name, this.render);
},
render: function() {
var field = _.defaults(this.field.toJSON(), this.defaults),
attributes = this.model.toJSON(),
attrArr = field.name.split('.'),
name = attrArr.shift(),
path = attrArr.join('.'),
rawValue = this.keyPathAccessor(attributes[name], path),
data = _.extend(field, {
rawValue: rawValue,
value: this.formatter.fromRaw(rawValue, this.model),
attributes: attributes,
formatter: this.formatter
}),
evalF = function(f, m) {
return (_.isFunction(f) ? !!f(m) : !!f);
};
// Evaluate the disabled, visible, and required option
_.extend(data, {
disabled: evalF(data.disabled, this.model),
readonly: evalF(data.readonly, this.model),
visible: evalF(data.visible, this.model),
required: evalF(data.required, this.model)
});
// Clean up first
this.$el.removeClass(Backform.hiddenClassName);
if (!data.visible)
this.$el.addClass(Backform.hiddenClassName);
if(Backform.requiredInputClassName) {
this.$el.removeClass(Backform.requiredInputClassName);
}
if (data.required) {
this.$el.addClass(Backform.requiredInputClassName);
}
this.$el.html(this.template(data)).addClass(field.name);
this.updateInvalid();
return this;
},
clearInvalid: function() {
this.$el.removeClass(Backform.errorClassName)
.find("." + Backform.helpClassName + ".error").remove();
return this;
},
updateInvalid: function() {
var self = this;
var errorModel = this.model.errorModel;
if (!(errorModel instanceof Backbone.Model)) return this;
this.clearInvalid();
this.$el.find(':input').not('button').each(function(ix, el) {
var error = self.keyPathAccessor(errorModel.toJSON(), $(el).attr('name'));
if (_.isEmpty(error)) return;
self.$el.addClass(Backform.errorClassName);
if (ix === 0) {
self.$el
.find("." + Backform.controlsClassName)
.append('<span class="' + Backform.helpClassName + ' error">' + (_.isArray(error) ? error.join(", ") : error) + '</span>');
}
});
return this;
},
keyPathAccessor: function(obj, path) {
var res = obj;
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;
},
keyPathSetter: function(obj, path, value) {
path = path.split('.');
while (path.length > 1) {
if (!obj[path[0]]) obj[path[0]] = {};
obj = obj[path.shift()];
}
return obj[path.shift()] = value;
},
processTab: function(e) {
if (e.which == 9) {
var $target = $(e.currentTarget);
setTimeout(function() {
var $nextFocus;
if (e.shiftKey) {
$nextFocus = !!$target.prevAll(':input:visible').length ?
$target.prevAll(':input:visible').first() :
$target.closest('.control-group:visible').prev('.control-group:visible').find(':input:visible');
} else {
$nextFocus = !!$target.nextAll(':input:visible').length ?
$target.nextAll(':input:visible').first() :
$target.closest('.control-group:visible').next('.control-group:visible').find(':input:visible');
}
if ($nextFocus.length) $nextFocus.first().focus();
}, 0);
}
}
});
// Built-in controls
Backform.UneditableInputControl = Control;
Backform.HelpControl = Control.extend({
template: _.template([
'<label class="<%=Backform.controlLabelClassName%>">&nbsp;</label>',
'<div class="<%=Backform.controlsClassName%>">',
' <span class="<%=Backform.helpMessageClassName%> help-block"><%=label%></span>',
'</div>'
].join("\n"))
});
Backform.SpacerControl = Control.extend({
template: _.template([
'<div class="<%=Backform.controlsClassName%>"></div>'
].join("\n"))
});
Backform.TextareaControl = Control.extend({
defaults: {
label: "",
maxlength: 4000,
extraClasses: [],
helpMessage: null
},
template: _.template([
'<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
'<div class="<%=Backform.controlContainerClassName%>">',
' <textarea class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>" name="<%=name%>" maxlength="<%=maxlength%>" placeholder="<%-placeholder%>" <%=disabled ? "disabled" : ""%> <%=readonly ? "readonly" : ""%> <%=required ? "required" : ""%>><%-value%></textarea>',
' <% if (helpMessage && helpMessage.length) { %>',
' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
' <% } %>',
'</div>'
].join("\n")),
events: _.extend({}, Control.prototype.events, {
"change textarea": "onChange",
"focus textarea": "clearInvalid"
}),
getValueFromDOM: function() {
return this.formatter.toRaw(this.$el.find("textarea").val(), this.model);
}
});
var SelectControl = Backform.SelectControl = Control.extend({
defaults: {
label: "",
options: [], // List of options as [{label:<label>, value:<value>}, ...]
extraClasses: []
},
template: _.template([
'<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
'<div class="<%=Backform.controlContainerClassName%>">',
' <select class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>" name="<%=name%>" value="<%-value%>" <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%> >',
' <% for (var i=0; i < options.length; i++) { %>',
' <% var option = options[i]; %>',
' <option value="<%-formatter.fromRaw(option.value)%>" <%=option.value === rawValue ? "selected=\'selected\'" : ""%> <%=option.disabled ? "disabled=\'disabled\'" : ""%>><%-option.label%></option>',
' <% } %>',
' </select>',
'</div>'
].join("\n")),
events: _.extend({}, Control.prototype.events, {
"change select": "onChange",
"focus select": "clearInvalid"
}),
formatter: JSONFormatter,
getValueFromDOM: function() {
return this.formatter.toRaw(this.$el.find("select").val(), this.model);
}
});
// Note: Value here is null or an array. Since jQuery val() returns either.
Backform.MultiSelectControl = SelectControl.extend({
defaults: {
label: "",
options: [], // List of options as [{label:<label>, value:<value>}, ...]
extraClasses: [],
height: '78px'
},
template: _.template([
'<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
'<div class="<%=Backform.controlsClassName%>">',
' <select multiple="multiple" class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>" name="<%=name%>" value="<%-JSON.stringify(value)%>" <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%> style="height:<%=height%>">',
' <% for (var i=0; i < options.length; i++) { %>',
' <% var option = options[i]; %>',
' <option value="<%-option.value%>" <%=value != null && _.indexOf(value, option.value) != -1 ? "selected=\'selected\'" : ""%> <%=option.disabled ? "disabled=\'disabled\'" : ""%>><%-option.label%></option>',
' <% } %>',
' </select>',
'</div>'
].join("\n")),
events: _.extend({}, Control.prototype.events, {
"change select": "onChange",
"dblclick select": "onDoubleClick",
"focus select": "clearInvalid"
}),
formatter: {
fromRaw: function(rawData, model) {
return rawData;
},
toRaw: function(formattedData, model) {
return typeof formattedData == "object" ? formattedData : JSON.parse(formattedData);
}
},
onDoubleClick: function(e) {
this.model.trigger('doubleclick', e);
}
});
var InputControl = Backform.InputControl = Control.extend({
defaults: {
type: "text",
label: "",
maxlength: 255,
extraClasses: [],
helpMessage: null,
},
template: _.template([
'<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
'<div class="<%=Backform.controlContainerClassName%>">',
' <input type="<%=type%>" class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>" name="<%=name%>" maxlength="<%=maxlength%>" value="<%-value%>" placeholder="<%-placeholder%>" <%=disabled ? "disabled" : ""%> <%=readonly ? "readonly aria-readonly=true" : ""%> <%=required ? "required" : ""%> />',
' <% if (helpMessage && helpMessage.length) { %>',
' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
' <% } %>',
'</div>'
].join("\n")),
events: _.extend({}, Control.prototype.events, {
"change input": "onChange",
"focus input": "clearInvalid"
}),
getValueFromDOM: function() {
return this.formatter.toRaw(this.$el.find("input").val(), this.model);
}
});
var BooleanControl = Backform.BooleanControl = InputControl.extend({
defaults: {
type: "checkbox",
label: "",
controlLabel: '&nbsp;',
extraClasses: [],
id: _.uniqueId('bf_')
},
template: _.template([
'<div class="<%=Backform.controlLabelClassName%>"><%=controlLabel%></div>',
'<div class="<%=Backform.controlContainerClassName%>">',
' <div class="custom-control custom-checkbox <%= (label && label.length)?"":"custom-checkbox-no-label" %>">',
' <input type="<%=type%>" id="<%=cId%>" class="custom-control-input <%=extraClasses.join(\' \')%>" name="<%=name%>" <%=value ? "checked=\'checked\'" : ""%> <%=disabled ? "disabled" : ""%> <%=readonly ? "readonly" : ""%> <%=required ? "required" : ""%> />',
' <% if (label && label.length) { %>',
' <label class="custom-control-label" for="<%=cId%>"><%=label%></label>',
' <% } else { %>',
' <label class="custom-control-label" for="<%=cId%>"><span class="sr-only"><%=controlLabel%></span></label>',
' <% } %>',
' </div>',
'</div>'
].join("\n")),
getValueFromDOM: function() {
return this.formatter.toRaw(this.$el.find("input").is(":checked"), this.model);
}
});
Backform.CheckboxControl = BooleanControl;
Backform.RadioControl = InputControl.extend({
defaults: {
type: "radio",
label: "",
options: [],
extraClasses: [],
helpMessage: null
},
template: _.template([
'<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
'<div class="<%=Backform.controlContainerClassName%> d-flex">',
' <% for (var i=0; i < options.length; i++) { %>',
' <% var option = options[i]; %>',
' <% var id = _.uniqueId("bf_"); %>',
' <div class="custom-control custom-radio">',
' <input type="<%=type%>" class="custom-control-input <%=extraClasses.join(\' \')%>" id="<%=cId%><%=i%>" name="<%=name%>" value="<%-formatter.fromRaw(option.value)%>" <%=rawValue == option.value ? "checked=\'checked\'" : ""%> <%=disabled ? "disabled" : ""%> <%=readonly ? "disabled" : ""%> <%=required ? "required" : ""%> />',
' <label class="custom-control-label" for="<%=cId%><%=i%>"><%-option.label%></label>',
' </div>',
' <% } %>',
' <% if (helpMessage && helpMessage.length) { %>',
' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
' <% } %>',
'</div>'
].join("\n")),
formatter: JSONFormatter,
getValueFromDOM: function() {
return this.formatter.toRaw(this.$el.find("input:checked").val(), this.model);
},
bootstrap2: function() {
Backform.radioControlsClassName = "controls";
Backform.radioLabelClassName = "radio inline";
}
});
_.extend(Backform, {
radioControlsClassName: "checkbox",
radioLabelClassName: "checkbox-inline"
});
// Requires the Bootstrap Datepicker to work.
Backform.DatepickerControl = InputControl.extend({
defaults: {
type: "text",
label: "",
options: {},
extraClasses: [],
maxlength: 255,
helpMessage: null
},
events: _.extend({}, Control.prototype.events, {
"blur input": "onChange",
"change input": "onChange",
"changeDate input": "onChange",
"focus input": "clearInvalid"
}),
render: function() {
InputControl.prototype.render.apply(this, arguments);
this.$el.find("input").datepicker(this.field.get("options"));
return this;
}
});
Backform.ButtonControl = Control.extend({
defaults: {
type: "submit",
label: "Submit",
status: undefined, // error or success
message: undefined,
extraClasses: []
},
template: _.template([
'<label class="<%=Backform.controlLabelClassName%>">&nbsp;</label>',
'<div class="<%=Backform.controlsClassName%>">',
' <button type="<%=type%>" name="<%=name%>" class="btn <%=extraClasses.join(\' \')%>" <%=disabled ? "disabled" : ""%> ><%=label%></button>',
' <% var cls = ""; if (status == "error") cls = Backform.buttonStatusErrorClassName; if (status == "success") cls = Backform.buttonStatusSuccessClassname; %>',
' <span class="status <%=cls%>"><%=message%></span>',
'</div>'
].join("\n")),
initialize: function() {
Control.prototype.initialize.apply(this, arguments);
this.listenTo(this.field, "change:status", this.render);
this.listenTo(this.field, "change:message", this.render);
},
bootstrap2: function() {
Backform.buttonStatusErrorClassName = "text-error";
Backform.buttonStatusSuccessClassname = "text-success";
}
});
_.extend(Backform, {
buttonStatusErrorClassName: "text-danger",
buttonStatusSuccessClassname: "text-success"
});
return Backform;
}));

View File

@ -1,12 +0,0 @@
/*
backgrid-select-all
http://github.com/wyuenho/backgrid
Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
Licensed under the MIT @license.
*/
.backgrid .select-row-cell,
.backgrid .select-all-header-cell {
text-align: center;
}

View File

@ -1,303 +0,0 @@
/*
backgrid-select-all
http://github.com/wyuenho/backgrid
Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
Licensed under the MIT @license.
*/
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(["backbone", "backgrid", "underscore", "sources/gettext"], factory);
} else if (typeof exports == "object") {
// CommonJS
module.exports = factory(require("backbone"), require("backgrid"), require("underscore"));
}
// Browser
else factory(root.Backbone, root.Backgrid, root._);
}(this, function (Backbone, Backgrid, _, gettext) {
"use strict";
/**
Renders a checkbox for row selection.
@class Backgrid.Extension.SelectRowCell
@extends Backbone.View
*/
var SelectRowCell = Backgrid.Extension.SelectRowCell = Backbone.View.extend({
/** @property */
className: "select-row-cell",
/** @property */
tagName: "td",
/** @property */
events: {
"keydown input[type=checkbox]": "onKeydown",
"change input[type=checkbox]": "onChange",
"click input[type=checkbox]": "enterEditMode"
},
/**
Initializer. If the underlying model triggers a `select` event, this cell
will change its checked value according to the event's `selected` value.
@param {Object} options
@param {Backgrid.Column} options.column
@param {Backbone.Model} options.model
*/
initialize: function (options) {
this.column = options.column;
if (!(this.column instanceof Backgrid.Column)) {
this.column = new Backgrid.Column(this.column);
}
var column = this.column, model = this.model, $el = this.$el;
this.listenTo(column, "change:renderable", function (col, renderable) {
$el.toggleClass("renderable", renderable);
});
if (Backgrid.callByNeed(column.renderable(), column, model)) $el.addClass("renderable");
this.listenTo(model, "backgrid:select", function (mod, selected) {
this.checkbox().prop("checked", selected).change();
});
},
/**
Returns the checkbox.
*/
checkbox: function () {
return this.$el.find("input[type=checkbox]");
},
/**
Focuses the checkbox.
*/
enterEditMode: function () {
this.checkbox().focus();
},
/**
Unfocuses the checkbox.
*/
exitEditMode: function () {
this.checkbox().blur();
},
/**
Process keyboard navigation.
*/
onKeydown: function (e) {
var command = new Backgrid.Command(e);
if (command.passThru()) return true; // skip ahead to `change`
else if (command.save() || command.moveLeft() || command.moveRight() ||
command.moveUp() || command.moveDown()) {
if(this.model) {
e.preventDefault();
e.stopPropagation();
this.model.trigger("backgrid:edited", this.model, this.column, command);
}
}
},
/**
When the checkbox's value changes, this method will trigger a Backbone
`backgrid:selected` event with a reference of the model and the
checkbox's `checked` value.
*/
onChange: function () {
var checked = this.checkbox().prop("checked");
this.$el.parent().toggleClass("selected", checked);
this.model.trigger("backgrid:selected", this.model, checked);
},
/**
Renders a checkbox in a table cell.
*/
render: function () {
var id = 'selectall-' + _.uniqueId(this.column.get('name'));
this.$el.empty().append([
'<div class="custom-control custom-checkbox custom-checkbox-no-label">',
' <input tabindex="0" type="checkbox" class="custom-control-input" id="'+ id +'" />',
' <label class="custom-control-label" for="'+ id +'">',
' <span class="sr-only">' + gettext('Select All') + '<span>',
' </label>',
'</div>'
].join('\n'));
this.delegateEvents();
return this;
}
});
/**
Renders a checkbox to select all rows on the current page.
@class Backgrid.Extension.SelectAllHeaderCell
@extends Backgrid.Extension.SelectRowCell
*/
var SelectAllHeaderCell = Backgrid.Extension.SelectAllHeaderCell = SelectRowCell.extend({
/** @property */
className: "select-all-header-cell",
/** @property */
tagName: "th",
/**
Initializer. When this cell's checkbox is checked, a Backbone
`backgrid:select` event will be triggered for each model for the current
page in the underlying collection. If a `SelectRowCell` instance exists
for the rows representing the models, they will check themselves. If any
of the SelectRowCell instances trigger a Backbone `backgrid:selected`
event with a `false` value, this cell will uncheck its checkbox. In the
event of a Backbone `backgrid:refresh` event, which is triggered when the
body refreshes its rows, which can happen under a number of conditions
such as paging or the columns were reset, this cell will still remember
the previously selected models and trigger a Backbone `backgrid:select`
event on them such that the SelectRowCells can recheck themselves upon
refreshing.
@param {Object} options
@param {Backgrid.Column} options.column
@param {Backbone.Collection} options.collection
*/
initialize: function (options) {
this.column = options.column;
if (!(this.column instanceof Backgrid.Column)) {
this.column = new Backgrid.Column(this.column);
}
var collection = this.collection;
var selectedModels = this.selectedModels = {};
this.listenTo(collection.fullCollection || collection,
"backgrid:selected", function (model, selected) {
if (selected) selectedModels[model.id || model.cid] = 1;
else {
delete selectedModels[model.id || model.cid];
this.checkbox().prop("checked", false);
}
if (_.keys(selectedModels).length === (collection.fullCollection|| collection).length) {
this.checkbox().prop("checked", true);
}
});
this.listenTo(collection.fullCollection || collection, "remove", function (model) {
delete selectedModels[model.id || model.cid];
if ((collection.fullCollection || collection).length === 0) {
this.checkbox().prop("checked", false);
}
});
this.listenTo(collection, "backgrid:refresh", function () {
if ((collection.fullCollection || collection).length === 0) {
this.checkbox().prop("checked", false);
}
else {
var checked = this.checkbox().prop("checked");
for (var i = 0; i < collection.length; i++) {
var model = collection.at(i);
if (checked || selectedModels[model.id || model.cid]) {
model.trigger("backgrid:select", model, true);
}
}
}
});
var column = this.column, $el = this.$el;
this.listenTo(column, "change:renderable", function (col, renderable) {
$el.toggleClass("renderable", renderable);
});
if (Backgrid.callByNeed(column.renderable(), column, collection)) $el.addClass("renderable");
},
/**
Propagates the checked value of this checkbox to all the models of the
underlying collection by triggering a Backbone `backgrid:select` event on
the models on the current page, passing each model and the current
`checked` value of the checkbox in each event.
A `backgrid:selected` event will also be triggered with the current
`checked` value on all the models regardless of whether they are on the
current page.
This method triggers a 'backgrid:select-all' event on the collection
afterwards.
*/
onChange: function () {
var checked = this.checkbox().prop("checked");
var collection = this.collection;
collection.each(function (model) {
model.trigger("backgrid:select", model, checked);
});
if (collection.fullCollection) {
collection.fullCollection.each(function (model) {
if (!collection.get(model.cid)) {
model.trigger("backgrid:selected", model, checked);
}
});
}
this.collection.trigger("backgrid:select-all", this.collection, checked);
}
});
/**
Convenient method to retrieve a list of selected models. This method only
exists when the `SelectAll` extension has been included. Selected models
are retained across pagination.
@member Backgrid.Grid
@return {Array.<Backbone.Model>}
*/
Backgrid.Grid.prototype.getSelectedModels = function () {
var selectAllHeaderCell;
var headerCells = this.header.row.cells;
for (var i = 0, l = headerCells.length; i < l; i++) {
var headerCell = headerCells[i];
if (headerCell instanceof SelectAllHeaderCell) {
selectAllHeaderCell = headerCell;
break;
}
}
var result = [];
if (selectAllHeaderCell) {
var selectedModels = selectAllHeaderCell.selectedModels;
var collection = this.collection.fullCollection || this.collection;
for (var modelId in selectedModels) {
result.push(collection.get(modelId));
}
}
return result;
};
/**
Convenient method to deselect the selected models. This method is only
available when the `SelectAll` extension has been included.
@member Backgrid.Grid
*/
Backgrid.Grid.prototype.clearSelectedModels = function () {
var selectedModels = this.getSelectedModels();
for (var i = 0, l = selectedModels.length; i < l; i++) {
var model = selectedModels[i];
model.trigger("backgrid:select", model, false);
}
};
}));

View File

@ -1,236 +0,0 @@
/*
backgrid
http://github.com/cloudflare/backgrid
Copyright (c) 2013-present Cloudflare, Inc. and contributors
Licensed under the MIT license.
*/
.backgrid-container {
position: relative;
display: block;
width: 100%;
height: 465px;
padding: 0;
overflow: auto;
border: 0;
}
.backgrid {
width: 100%;
max-width: 100%;
background-color: transparent;
border-collapse: collapse;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.backgrid th,
.backgrid td {
display: none;
height: 20px;
max-width: 250px;
padding: 4px 5px;
overflow: hidden;
line-height: 20px;
text-align: left;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: middle;
border-bottom: 1px solid #DDD;
}
.backgrid th.renderable,
.backgrid td.renderable {
display: table-cell;
}
.backgrid th {
font-weight: bold;
text-align: center;
}
.backgrid th.sortable a {
text-decoration: none;
white-space: nowrap;
cursor: pointer;
}
.backgrid thead th {
vertical-align: bottom;
background-color: #f9f9f9;
}
.backgrid thead th a {
display: block;
}
.backgrid.backgrid-striped tbody tr:nth-child(even) {
background-color: #f9f9f9;
}
.backgrid tbody tr.empty {
font-style: italic;
color: gray;
}
.backgrid tbody tr.empty td {
display: inherit;
text-align: center;
}
.backgrid td.editor,
.backgrid tbody tr:nth-child(odd) td.editor {
background-color: rgba(82, 168, 236, 0.1);
outline: 1px solid rgba(82, 168, 236, 0.8);
outline-offset: -1px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
-webkit-transition-duration: 200ms;
-moz-transition-duration: 200ms;
-o-transition-duration: 200ms;
transition-duration: 200ms;
-webkit-transition-property: width, outline, background-color;
-moz-transition-property: width, outline, background-color;
-o-transition-property: width, outline, background-color;
transition-property: width, outline, background-color;
-webkit-transition-timing-function: ease-in-out;
-moz-transition-timing-function: ease-in-out;
-o-transition-timing-function: ease-in-out;
transition-timing-function: ease-in-out;
}
.backgrid td.editor input[type=text] {
display: block;
width: 100%;
height: 100%;
padding: 0 5px;
margin: 0;
background-color: transparent;
border: 0;
outline: 0;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
-webkit-appearance: none;
-moz-appearance: none;
}
.backgrid td.editor input[type=text]::-ms-clear {
display: none;
}
.backgrid td.error,
.backgrid tbody tr:nth-child(odd) td.error {
background-color: rgba(255, 210, 77, 0.1);
outline: 1px solid #ffd24d;
}
.backgrid td.editor :focus,
.backgrid th.editor :focus {
outline: 0;
}
.backgrid .sort-caret {
display: inline-block;
width: 0;
height: 0;
margin-left: 0.3em;
border: 0;
content: "";
}
.backgrid .ascending .sort-caret {
vertical-align: baseline;
border-top: none;
border-right: 4px solid transparent;
border-bottom: 4px solid #000000;
border-left: 4px solid transparent;
}
.backgrid .descending .sort-caret {
vertical-align: super;
border-top: 4px solid #000000;
border-right: 4px solid transparent;
border-bottom: none;
border-left: 4px solid transparent;
}
.backgrid .string-cell,
.backgrid .uri-cell,
.backgrid .email-cell,
.backgrid .string-cell.editor input[type=text],
.backgrid .uri-cell.editor input[type=text],
.backgrid .email-cell.editor input[type=text] {
text-align: left;
}
.backgrid .date-cell,
.backgrid .time-cell,
.backgrid .datetime-cell,
.backgrid .number-cell,
.backgrid .integer-cell,
.backgrid .percent-cell,
.backgrid .date-cell.editor input[type=text],
.backgrid .time-cell.editor input[type=text],
.backgrid .datetime-cell.editor input[type=text],
.backgrid .number-cell.editor input[type=text],
.backgrid .integer-cell.editor input[type=text],
.backgrid .percent-cell.editor input[type=text] {
text-align: right;
}
.backgrid .boolean-cell,
.backgrid .boolean-cell.editor input[type=checkbox] {
text-align: center;
}
.backgrid .select-cell {
text-align: center;
}
.backgrid .select-cell.editor {
padding: 0;
}
.backgrid .select-cell.editor select {
display: block;
width: 100%;
height: 28px;
padding: 4px 5px;
margin: 0;
line-height: 28px;
vertical-align: middle;
background-color: white;
border: 0;
outline: 0;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.backgrid .select-cell.editor select[multiple] {
height: auto;
}
.backgrid .select-cell.editor :focus {
border: 0;
outline: 0;
}
.backgrid .select-cell.editor select::-moz-focus-inner,
.backgrid .select-cell.editor optgroup::-moz-focus-inner,
.backgrid .select-cell.editor option::-moz-focus-inner,
.backgrid .select-cell.editor select::-o-focus-inner,
.backgrid .select-cell.editor optgroup::-o-focus-inner,
.backgrid .select-cell.editor option::-o-focus-inner {
border: 0;
}

File diff suppressed because it is too large Load Diff

View File

@ -1947,10 +1947,9 @@ def convert_data_to_dict(conn, result):
column['type_code'] = items[1][1] column['type_code'] = items[1][1]
columns.append(column) columns.append(column)
# We need to convert result from 2D array to dict for BackGrid # We need to convert result from 2D array to dict.
# BackGrid do not support for 2D array result as it it Backbone Model # This Conversion is not an overhead as most of the time
# based grid, This Conversion is not an overhead as most of the time # result will be smaller.
# result will be smaller
_tmp_result = [] _tmp_result = []
for row in result: for row in result:
temp = dict() temp = dict()

View File

@ -29,8 +29,8 @@ class CheckForXssFeatureTest(BaseFeatureTest):
We will cover, We will cover,
1) Browser Tree 1) Browser Tree
2) Properties Tab (BackFrom) 2) Properties Tab
3) Dependents Tab (BackGrid) 3) Dependents Tab
4) SQL Tab (Code Mirror) 4) SQL Tab (Code Mirror)
5) Query Tool (Result Grid) 5) Query Tool (Result Grid)
""" """
@ -55,7 +55,7 @@ class CheckForXssFeatureTest(BaseFeatureTest):
['"<script>alert(1)</script>" char', ['"<script>alert(1)</script>" char',
'typcol ' + self.test_type_name] 'typcol ' + self.test_type_name]
) )
# This is needed to test dependents tab (eg: BackGrid) # This is needed to test dependents tab
test_utils.create_constraint( test_utils.create_constraint(
self.server, self.test_db, self.server, self.test_db,
self.test_table_name, self.test_table_name,
@ -184,7 +184,7 @@ class CheckForXssFeatureTest(BaseFeatureTest):
self._check_escaped_characters( self._check_escaped_characters(
source_code, source_code,
"public.&lt;h1 onmouseover='console.log(2);'&gt;Y", "public.&lt;h1 onmouseover='console.log(2);'&gt;Y",
"Dependents tab (BackGrid)" "Dependents tab"
) )
def _check_xss_in_query_tool(self): def _check_xss_in_query_tool(self):

View File

@ -118,7 +118,7 @@ class CheckDebuggerForXssFeatureTest(BaseFeatureTest):
"[contains(.,'Hello, pgAdmin4')]")) "[contains(.,'Hello, pgAdmin4')]"))
) )
# Only this tab is vulnerable rest are BackGrid & Code Mirror # Only this tab is vulnerable rest are Code Mirror
# control which are already tested in Query tool test case # control which are already tested in Query tool test case
self.page.click_tab('id-debugger-messages', rc_dock=True) self.page.click_tab('id-debugger-messages', rc_dock=True)
source_code = self.page.find_by_xpath( source_code = self.page.find_by_xpath(

View File

@ -1,434 +0,0 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////////////////
define([
'jquery',
'backbone',
'pgadmin.backform',
], function ($, Backbone, Backform) {
describe('KeyboardshortcutControl', function () {
let field, innerFields, control, model;
beforeEach(() => {
innerFields = [
{'name': 'key', 'type': 'keyCode', 'label': 'Key'},
{'name': 'alt_option', 'type': 'checkbox',
'label': 'Alt/Option'},
{'name': 'control', 'type': 'checkbox',
'label': 'Ctrl'},
{'name': 'shift', 'type': 'checkbox', 'label': 'Shift'},
];
model = new Backbone.Model({
'shortcut': {
'control': true,
'shift': false,
'alt_option': true,
'key': {
'key_code': 73,
'char': 'I',
},
},
});
field = new Backform.Field({
id: 'shortcut',
name: 'shortcut',
control: 'keyboardShortcut',
label: 'Keyboard shortcut',
fields: innerFields,
});
control = new (field.get('control')) ({
field: field,
model: model,
});
control.render();
});
describe('keyboardShortcut UI setup', function () {
it('keyboard shortcut control should be rendered with inner fields', function () {
expect(control.$el.find('input:text[name="key"]')[0].value).toEqual('I');
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeTruthy();
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeTruthy();
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
});
});
describe('onModelChange', function () {
beforeEach((done) => {
done();
});
it('when model "key" value changes UI and innerModel should update new "key" value', function (done) {
expect(control.$el.find('input:text[name="key"]')[0].value).toEqual('I');
expect(control.innerModel.get('key')).toEqual({
'key_code': 73,
'char': 'I',
});
let val = $.extend(true, {}, model.get(field.get('name')));
model.set(field.get('name'),
$.extend(true, val, {
'key': {
'key_code': 65,
'char': 'A',
},
})
);
// wait until UI updates.
setTimeout(function() {
// this should change
expect(control.$el.find('input:text[name="key"]')[0].value).toEqual('A');
expect(control.innerModel.get('key')).toEqual({
'key_code': 65,
'char': 'A',
});
// below three should not change.
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeTruthy();
expect(control.innerModel.get('alt_option')).toBeTruthy();
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeTruthy();
expect(control.innerModel.get('control')).toBeTruthy();
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
expect(control.innerModel.get('shift')).toBeFalsy();
done();
}, 100);
});
it('when model "control" value changes UI and innerModel should update new "control" value', function (done) {
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeTruthy();
expect(control.innerModel.get('control')).toBeTruthy();
let val = $.extend(true, {}, model.get(field.get('name')));
model.set(field.get('name'),
$.extend(true, val, {
'control': false,
})
);
// wait until UI updates.
setTimeout(function() {
// this should change
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeFalsy();
expect(control.innerModel.get('control')).toBeFalsy();
// below three should not change.
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeTruthy();
expect(control.innerModel.get('alt_option')).toBeTruthy();
expect(control.$el.find('input:text[name="key"]')[0].value).toEqual('I');
expect(control.innerModel.get('key')).toEqual({
'key_code': 73,
'char': 'I',
});
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
expect(control.innerModel.get('shift')).toBeFalsy();
done();
}, 100);
});
it('when model "shift" value changes UI and innerModel should update new "shift" value', function (done) {
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
expect(control.innerModel.get('shift')).toBeFalsy();
let val = $.extend(true, {}, model.get(field.get('name')));
model.set(field.get('name'),
$.extend(true, val, {
'shift': true,
})
);
// wait until UI updates.
setTimeout(function() {
// this should change
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeTruthy();
expect(control.innerModel.get('shift')).toBeTruthy();
// below three should not change.
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeTruthy();
expect(control.innerModel.get('alt_option')).toBeTruthy();
expect(control.$el.find('input:text[name="key"]')[0].value).toEqual('I');
expect(control.innerModel.get('key')).toEqual({
'key_code': 73,
'char': 'I',
});
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeTruthy();
expect(control.innerModel.get('control')).toBeTruthy();
done();
}, 100);
});
it('when model "alt_option" value changes UI and innerModel should update new "alt_option" value', function (done) {
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeTruthy();
expect(control.innerModel.get('alt_option')).toBeTruthy();
let val = $.extend(true, {}, model.get(field.get('name')));
model.set(field.get('name'),
$.extend(true, val, {
'alt_option': false,
})
);
// wait until UI updates.
setTimeout(function() {
// this should change
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeFalsy();
expect(control.innerModel.get('alt_option')).toBeFalsy();
// below three should not change.
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
expect(control.innerModel.get('shift')).toBeFalsy();
expect(control.$el.find('input:text[name="key"]')[0].value).toEqual('I');
expect(control.innerModel.get('key')).toEqual({
'key_code': 73,
'char': 'I',
});
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeTruthy();
expect(control.innerModel.get('control')).toBeTruthy();
done();
}, 100);
});
});
describe('onInnerModelChange', function () {
beforeEach((done) => {
done();
});
it('when innerModel "key" value changes UI and model should update new "key" value', function (done) {
expect(control.$el.find('input:text[name="key"]')[0].value).toEqual('I');
expect(model.get(field.get('name'))).toEqual({
'control': true,
'shift': false,
'alt_option': true,
'key': {
'key_code': 73,
'char': 'I',
},
});
control.innerModel.set('key',
{
'key_code': 65,
'char': 'A',
}
);
// wait until UI updates.
setTimeout(function() {
// this should change
expect(control.$el.find('input:text[name="key"]')[0].value).toEqual('A');
expect(model.get(field.get('name'))).toEqual({
'control': true,
'shift': false,
'alt_option': true,
'key': {
'key_code': 65,
'char': 'A',
},
});
// below three should not change.
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeTruthy();
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeTruthy();
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
done();
}, 100);
});
it('when innerModel "control" value changes UI and model should update new "control" value', function (done) {
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeTruthy();
expect(model.get(field.get('name'))).toEqual({
'control': true,
'shift': false,
'alt_option': true,
'key': {
'key_code': 73,
'char': 'I',
},
});
control.innerModel.set('control', false);
// wait until UI updates.
setTimeout(function() {
// this should change
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeFalsy();
expect(model.get(field.get('name'))).toEqual({
'control': false,
'shift': false,
'alt_option': true,
'key': {
'key_code': 73,
'char': 'I',
},
});
// below three should not change.
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeTruthy();
expect(control.$el.find('input:text[name="key"]')[0].value).toEqual('I');
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
done();
}, 100);
});
it('when innerModel "shift" value changes UI and model should update new "shift" value', function (done) {
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
expect(model.get(field.get('name'))).toEqual({
'control': true,
'shift': false,
'alt_option': true,
'key': {
'key_code': 73,
'char': 'I',
},
});
control.innerModel.set('shift', true);
// wait until UI updates.
setTimeout(function() {
// this should change
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeTruthy();
expect(model.get(field.get('name'))).toEqual({
'control': true,
'shift': true,
'alt_option': true,
'key': {
'key_code': 73,
'char': 'I',
},
});
// below three should not change.
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeTruthy();
expect(control.$el.find('input:text[name="key"]')[0].value).toEqual('I');
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeTruthy();
done();
}, 100);
});
it('when innerModel "alt_option" value changes UI and model should update new "alt_option" value', function (done) {
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeTruthy();
expect(model.get(field.get('name'))).toEqual({
'control': true,
'shift': false,
'alt_option': true,
'key': {
'key_code': 73,
'char': 'I',
},
});
control.innerModel.set('alt_option', false);
// wait until UI updates.
setTimeout(function() {
// this should change
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeFalsy();
expect(model.get(field.get('name'))).toEqual({
'control': true,
'shift': false,
'alt_option': false,
'key': {
'key_code': 73,
'char': 'I',
},
});
// below three should not change.
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
expect(control.$el.find('input:text[name="key"]')[0].value).toEqual('I');
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeTruthy();
done();
}, 100);
});
});
describe('remove keyboardShortcut control', function () {
beforeEach(function() {
spyOn(control, 'cleanup').and.callThrough();
});
it('when removed it should remove all of it\' controls', function () {
control.remove();
expect(control.cleanup).toHaveBeenCalled();
expect(control.controls.length).toEqual(0);
});
});
});
});

View File

@ -1,199 +0,0 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////////////////
define([
'backbone',
'pgadmin.backform',
], function (Backbone, Backform) {
describe('keyCodeControl', function () {
let field, control, model, event;
beforeEach(() => {
model = new Backbone.Model({
'key': {
'key_code': 65,
'char': 'A',
},
});
field = new Backform.Field({
id: 'key',
name: 'key',
control: 'keyCode',
label: 'Key',
});
control = new (field.get('control')) ({
field: field,
model: model,
});
control.render();
event = {
which: -1,
keyCode: -1,
key: '',
preventDefault: jasmine.createSpy('preventDefault'),
};
});
describe('onkeyDown', function () {
beforeEach((done) => {
spyOn(model, 'set').and.callThrough();
spyOn(control, 'onkeyDown').and.callThrough();
done();
});
it('when key with escapeKeyCode is pressed model should not update', function (done) {
event.which = 16;
event.keyCode = 16;
event.key = 'Shift';
control.onkeyDown(event);
expect(control.onkeyDown).toHaveBeenCalled();
expect(model.set).not.toHaveBeenCalled();
expect(event.preventDefault).not.toHaveBeenCalled();
expect(model.get('key')).toEqual({
'key_code': 65,
'char': 'A',
});
// wait until UI updates.
setTimeout(function() {
expect(control.$el.find('input')[0].value).toEqual('A');
done();
}, 100);
});
it('when key other than escapeKeyCode is pressed model should update', function (done) {
event.which = 66;
event.keyCode = 66;
event.key = 'B';
control.onkeyDown(event);
expect(control.onkeyDown).toHaveBeenCalled();
expect(model.set).toHaveBeenCalled();
expect(event.preventDefault).toHaveBeenCalled();
expect(model.get('key')).toEqual({
'key_code': 66,
'char': 'B',
});
// wait until UI updates.
setTimeout(function() {
expect(control.$el.find('input')[0].value).toEqual('B');
done();
}, 100);
});
});
describe('onkeyUp', function () {
beforeEach((done) => {
spyOn(control, 'preventEvent').and.callThrough();
event.stopPropagation = jasmine.createSpy('stopPropagation');
event.stopImmediatePropagation = jasmine.createSpy('stopImmediatePropagation');
done();
});
it('when key with escapeKeyCode is pressed and released event should be propagated', function (done) {
event.which = 17;
event.keyCode = 17;
event.key = 'Ctrl';
control.preventEvent(event);
expect(control.preventEvent).toHaveBeenCalled();
expect(event.preventDefault).not.toHaveBeenCalled();
expect(event.stopPropagation).not.toHaveBeenCalled();
expect(event.stopImmediatePropagation).not.toHaveBeenCalled();
// wait until UI updates.
setTimeout(function() {
expect(control.$el.find('input')[0].value).toEqual('A');
done();
}, 100);
});
it('when key other than escapeKeyCode is pressed and released event should not be propagated', function (done) {
event.which = 66;
event.keyCode = 66;
event.key = 'B';
control.preventEvent(event);
expect(control.preventEvent).toHaveBeenCalled();
expect(event.preventDefault).toHaveBeenCalled();
expect(event.stopPropagation).toHaveBeenCalled();
expect(event.stopImmediatePropagation).toHaveBeenCalled();
// wait until UI updates.
setTimeout(function() {
expect(control.$el.find('input')[0].value).toEqual('A');
done();
}, 100);
});
});
describe('onModelChange', function () {
beforeEach((done) => {
done();
});
it('when model changes UI should update', function (done) {
expect(control.$el.find('input')[0].value).toEqual('A');
model.set('key', {
'key_code': 67,
'char': 'C',
});
// wait until UI updates.
setTimeout(function() {
expect(control.$el.find('input')[0].value).toEqual('C');
done();
}, 100);
});
});
});
});

View File

@ -43,7 +43,6 @@ const providePlugin = new webpack.ProvidePlugin({
'window.jQuery': 'jquery', 'window.jQuery': 'jquery',
_: 'underscore', _: 'underscore',
Backbone: 'backbone', Backbone: 'backbone',
Backgrid: 'backgrid',
pgAdmin: 'pgadmin', pgAdmin: 'pgadmin',
'moment': 'moment', 'moment': 'moment',
'window.moment':'moment', 'window.moment':'moment',

View File

@ -48,26 +48,6 @@ let webpackShimConfig = {
'jquery:$', // Provide jquery as dependency with name $ 'jquery:$', // Provide jquery as dependency with name $
], ],
}, },
'backgrid': {
'deps': ['backform'],
'exports': 'Backgrid',
},
'pgadmin.backform': {
'deps': ['backform', 'pgadmin.backgrid', 'select2', 'bootstrap.toggle'],
},
'pgadmin.backgrid': {
'deps': ['backgrid', 'bootstrap.datetimepicker', 'bootstrap.toggle'],
},
'backgrid.select.all': {
'deps': ['backgrid'],
},
'backgrid.paginator': {
'deps': ['backgrid', 'backbone.paginator'],
},
'backgrid.filter': {
'deps': ['backgrid'],
},
'jquery.event.drag': { 'jquery.event.drag': {
'deps': ['jquery'], 'exports': 'jQuery.fn.drag', 'deps': ['jquery'], 'exports': 'jQuery.fn.drag',
}, },
@ -146,18 +126,12 @@ let webpackShimConfig = {
//socket //socket
'socketio': path.join(__dirname, './node_modules/socket.io-client/dist/socket.io.js'), 'socketio': path.join(__dirname, './node_modules/socket.io-client/dist/socket.io.js'),
// Backbone and Backgrid // Backbone
'backbone': path.join(__dirname, './node_modules/backbone/backbone'), 'backbone': path.join(__dirname, './node_modules/backbone/backbone'),
'backbone.undo': path.join(__dirname, './node_modules/backbone-undo/Backbone.Undo'), 'backbone.undo': path.join(__dirname, './node_modules/backbone-undo/Backbone.Undo'),
'backform': path.join(__dirname, './pgadmin/static/vendor/backform/backform'),
'backgrid': path.join(__dirname, './pgadmin/static/vendor/backgrid/backgrid'),
'bootstrap.datetimepicker': path.join(__dirname, './node_modules/tempusdominus-bootstrap-4/build/js/tempusdominus-bootstrap-4.min'), 'bootstrap.datetimepicker': path.join(__dirname, './node_modules/tempusdominus-bootstrap-4/build/js/tempusdominus-bootstrap-4.min'),
'bootstrap.toggle': path.join(__dirname, './node_modules/bootstrap4-toggle/js/bootstrap4-toggle.min'), 'bootstrap.toggle': path.join(__dirname, './node_modules/bootstrap4-toggle/js/bootstrap4-toggle.min'),
'select2': path.join(__dirname, './node_modules/select2/dist/js/select2.full'), 'select2': path.join(__dirname, './node_modules/select2/dist/js/select2.full'),
'backgrid.filter': path.join(__dirname, './node_modules/backgrid-filter/backgrid-filter'),
'backgrid.select.all': path.join(__dirname, './pgadmin/static/vendor/backgrid/backgrid-select-all'),
'pgadmin.backform': path.join(__dirname, './pgadmin/static/js/backform.pgadmin'),
'pgadmin.backgrid': path.join(__dirname, './pgadmin/static/js/backgrid.pgadmin'),
'pgadmin.about': path.join(__dirname, './pgadmin/about/static/js/about'), 'pgadmin.about': path.join(__dirname, './pgadmin/about/static/js/about'),
'pgadmin.authenticate.kerberos': path.join(__dirname, './pgadmin/authenticate/static/js/kerberos'), 'pgadmin.authenticate.kerberos': path.join(__dirname, './pgadmin/authenticate/static/js/kerberos'),
@ -178,11 +152,8 @@ let webpackShimConfig = {
'pgadmin.browser.quick_search': path.join(__dirname, './pgadmin/browser/static/js/quick_search'), 'pgadmin.browser.quick_search': path.join(__dirname, './pgadmin/browser/static/js/quick_search'),
'pgadmin.browser.messages': '/browser/js/messages', 'pgadmin.browser.messages': '/browser/js/messages',
'pgadmin.browser.node': path.join(__dirname, './pgadmin/browser/static/js/node'), 'pgadmin.browser.node': path.join(__dirname, './pgadmin/browser/static/js/node'),
'pgadmin.browser.node.ui': path.join(__dirname, './pgadmin/browser/static/js/node.ui'),
'pgadmin.browser.panel': path.join(__dirname, './pgadmin/browser/static/js/panel'), 'pgadmin.browser.panel': path.join(__dirname, './pgadmin/browser/static/js/panel'),
'pgadmin.browser.toolbar': path.join(__dirname, './pgadmin/browser/static/js/toolbar'), 'pgadmin.browser.toolbar': path.join(__dirname, './pgadmin/browser/static/js/toolbar'),
'pgadmin.browser.server.privilege': path.join(__dirname, './pgadmin/browser/server_groups/servers/static/js/privilege'),
'pgadmin.browser.server.variable': path.join(__dirname, './pgadmin/browser/server_groups/servers/static/js/variable'),
'pgadmin.browser.utils': '/browser/js/utils', 'pgadmin.browser.utils': '/browser/js/utils',
'pgadmin.dashboard': path.join(__dirname, './pgadmin/dashboard/static/js/Dashboard'), 'pgadmin.dashboard': path.join(__dirname, './pgadmin/dashboard/static/js/Dashboard'),
'pgadmin.help': path.join(__dirname, './pgadmin/help/static/js/help'), 'pgadmin.help': path.join(__dirname, './pgadmin/help/static/js/help'),
@ -282,10 +253,10 @@ let webpackShimConfig = {
// Define list of pgAdmin common libraries to bundle them separately // Define list of pgAdmin common libraries to bundle them separately
// into commons JS from app.bundle.js // into commons JS from app.bundle.js
pgLibs: [ pgLibs: [
'pgadmin.browser.error', 'pgadmin.browser.server.privilege', 'pgadmin.browser.error',
'pgadmin.browser.server.variable', 'pgadmin.browser.collection', 'pgadmin.browser.node.ui', 'pgadmin.browser.collection',
'pgadmin.browser.datamodel', 'pgadmin.browser.menu', 'pgadmin.browser.panel', 'pgadmin', 'pgadmin.browser.datamodel', 'pgadmin.browser.menu', 'pgadmin.browser.panel', 'pgadmin',
'pgadmin.browser.frame', 'pgadmin.backform', 'pgadmin.backgrid', 'pgadmin.browser', 'pgadmin.browser.frame', 'pgadmin.browser',
'pgadmin.browser.node', 'pgadmin.browser.node',
'pgadmin.settings', 'pgadmin.preferences', 'pgadmin.sqlfoldcode', 'pgadmin.settings', 'pgadmin.preferences', 'pgadmin.sqlfoldcode',
], ],

View File

@ -122,9 +122,6 @@ module.exports = {
'bootstrap.datetimepicker': path.join(__dirname, './node_modules/tempusdominus-bootstrap-4/build/js/tempusdominus-bootstrap-4.min'), 'bootstrap.datetimepicker': path.join(__dirname, './node_modules/tempusdominus-bootstrap-4/build/js/tempusdominus-bootstrap-4.min'),
'bootstrap.toggle': path.join(__dirname, './node_modules/bootstrap4-toggle/js/bootstrap4-toggle.min'), 'bootstrap.toggle': path.join(__dirname, './node_modules/bootstrap4-toggle/js/bootstrap4-toggle.min'),
'backbone': path.join(__dirname, './node_modules/backbone/backbone'), 'backbone': path.join(__dirname, './node_modules/backbone/backbone'),
'backform': path.join(__dirname, './node_modules/backform/src/backform'),
'backgrid': path.join(__dirname, './pgadmin/static/vendor/backgrid/backgrid'),
'backgrid.filter': path.join(__dirname, './node_modules/backgrid-filter/backgrid-filter'),
'react': path.join(__dirname, 'node_modules/react'), 'react': path.join(__dirname, 'node_modules/react'),
'react-dom': path.join(__dirname, 'node_modules/react-dom'), 'react-dom': path.join(__dirname, 'node_modules/react-dom'),
'sources': sourcesDir + '/js', 'sources': sourcesDir + '/js',
@ -137,8 +134,6 @@ module.exports = {
'browser': path.resolve(__dirname, 'pgadmin/browser/static/js'), 'browser': path.resolve(__dirname, 'pgadmin/browser/static/js'),
'pgadmin': sourcesDir + '/js/pgadmin', 'pgadmin': sourcesDir + '/js/pgadmin',
'pgadmin.sqlfoldcode': sourcesDir + '/js/codemirror/addon/fold/pgadmin-sqlfoldcode', 'pgadmin.sqlfoldcode': sourcesDir + '/js/codemirror/addon/fold/pgadmin-sqlfoldcode',
'pgadmin.backgrid': sourcesDir + '/js/backgrid.pgadmin',
'pgadmin.backform': sourcesDir + '/js/backform.pgadmin',
'pgadmin4-tree': path.join(__dirname, 'node_modules/pgadmin4-tree'), 'pgadmin4-tree': path.join(__dirname, 'node_modules/pgadmin4-tree'),
'pgbrowser': path.resolve(__dirname, 'regression/javascript/fake_browser'), 'pgbrowser': path.resolve(__dirname, 'regression/javascript/fake_browser'),
'pgadmin.schema.dir': path.resolve(__dirname, 'pgadmin/browser/server_groups/servers/databases/schemas/static/js'), 'pgadmin.schema.dir': path.resolve(__dirname, 'pgadmin/browser/server_groups/servers/databases/schemas/static/js'),

View File

@ -3431,13 +3431,6 @@ babylon@^6.18.0:
resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==
"backbone@1.1.2 || 1.2.3 || ~1.3.2":
version "1.3.3"
resolved "https://registry.yarnpkg.com/backbone/-/backbone-1.3.3.tgz#4cc80ea7cb1631ac474889ce40f2f8bc683b2999"
integrity sha512-aK+k3TiU4tQDUrRCymDDE7XDFnMVuyE6zbZ4JX7mb4pJbQTVOH997/kyBzb8wB2s5Y/Oh7EUfj+sZhwRPxWwow==
dependencies:
underscore ">=1.8.3"
backbone@1.4.0: backbone@1.4.0:
version "1.4.0" version "1.4.0"
resolved "https://registry.yarnpkg.com/backbone/-/backbone-1.4.0.tgz#54db4de9df7c3811c3f032f34749a4cd27f3bd12" resolved "https://registry.yarnpkg.com/backbone/-/backbone-1.4.0.tgz#54db4de9df7c3811c3f032f34749a4cd27f3bd12"
@ -3445,41 +3438,6 @@ backbone@1.4.0:
dependencies: dependencies:
underscore ">=1.8.3" underscore ">=1.8.3"
backbone@~1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/backbone/-/backbone-1.2.3.tgz#c22cfd07fc86ebbeae61d18929ed115e999d65b9"
integrity sha512-1/eXj4agG79UDN7TWnZXcGD6BJrBwLZKCX7zYcBIy9jWf4mrtVkw7IE1VOYFnrKahsmPF9L55Tib9IQRvk027w==
dependencies:
underscore ">=1.7.0"
backform@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/backform/-/backform-0.2.0.tgz#b14cb8deb08c863fc595a2bc505066e32a2ad4ce"
integrity sha512-QYwlItiVqb4CDELHyBC+TM4UcoG6Mw/al+PUDUVbQ7OJojpQHaa2TV8uJHhMR6o/Xq4FGt+52qIZLp36dletfw==
backgrid-filter@^0.3.7:
version "0.3.7"
resolved "https://registry.yarnpkg.com/backgrid-filter/-/backgrid-filter-0.3.7.tgz#d4b19d0e707013d7f181f9e8c7febb4997d56f03"
integrity sha512-HKWOXXd/dES5Ll3R1+vsfPYO7yVQ0V4+h8cPirFqci4oKTyyZVJupXM2fINhqm0On9dvHijHje8h4X+Wg621gw==
dependencies:
backbone "~1.2.3"
backgrid "~0.3.7"
lunr "^0.7.0"
underscore "^1.8.3"
backgrid-select-all@^0.3.5:
version "0.3.5"
resolved "https://registry.yarnpkg.com/backgrid-select-all/-/backgrid-select-all-0.3.5.tgz#143a800e5d95ff2ae5a84d78bf4fba41f9481e94"
integrity sha512-bwMQi5d8AnBSZDiV4nWrXcOSmEODbxB6/70mSHG8cGoDfjgW5X7mLiXlmlgEP3VsA1avFD6VvCvpAKZ4BS5f9Q==
backgrid@~0.3.7:
version "0.3.8"
resolved "https://registry.yarnpkg.com/backgrid/-/backgrid-0.3.8.tgz#7d26816742d72c859cad39b13f19c9f27baffed7"
integrity sha512-Klzo941ahoj8Kqd0tRsau+VfXddV3YnQTwb6wVwIaaQxoJ9ORykQy2MNit1MUBnZO6IValYJPvCQyvZhnV6Lfg==
dependencies:
backbone "1.1.2 || 1.2.3 || ~1.3.2"
underscore "^1.8.0"
backo2@~1.0.2: backo2@~1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
@ -7843,11 +7801,6 @@ lru-cache@^7.7.1:
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.13.1.tgz#267a81fbd0881327c46a81c5922606a2cfe336c4" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.13.1.tgz#267a81fbd0881327c46a81c5922606a2cfe336c4"
integrity sha512-CHqbAq7NFlW3RSnoWXLJBxCWaZVBrfa9UEHId2M3AW8iEBurbqduNexEUCGc3SHc6iCYXNJCDi903LajSVAEPQ== integrity sha512-CHqbAq7NFlW3RSnoWXLJBxCWaZVBrfa9UEHId2M3AW8iEBurbqduNexEUCGc3SHc6iCYXNJCDi903LajSVAEPQ==
lunr@^0.7.0:
version "0.7.2"
resolved "https://registry.yarnpkg.com/lunr/-/lunr-0.7.2.tgz#79a30e932e216cba163541ee37a3607c12cd7281"
integrity sha512-qXxxSzrWOhFu4EhyvYqCGMv1nJsTy5OGQN3GtClGbRSaqJ/1XASk41nF2jjxzKTS8kjU0QybhOgGgGo6HUZqSQ==
make-dir@^1.0.0, make-dir@^1.2.0: make-dir@^1.0.0, make-dir@^1.2.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
@ -11235,7 +11188,7 @@ undeclared-identifiers@^1.1.2:
simple-concat "^1.0.0" simple-concat "^1.0.0"
xtend "^4.0.1" xtend "^4.0.1"
underscore@>=1.7.0, underscore@>=1.8.3, underscore@^1.13.1, underscore@^1.8.0, underscore@^1.8.3, underscore@^1.9.1: underscore@>=1.8.3, underscore@^1.13.1, underscore@^1.9.1:
version "1.13.1" version "1.13.1"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.1.tgz#0c1c6bd2df54b6b69f2314066d65b6cde6fcf9d1" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.1.tgz#0c1c6bd2df54b6b69f2314066d65b6cde6fcf9d1"
integrity sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g== integrity sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==