mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Remove Backgrid and Backform. Fixes #6134
This commit is contained in:
parent
b128ba2f57
commit
ca8b5c68fd
@ -208,7 +208,7 @@ Front End
|
||||
pgAdmin uses javascript extensively for the front-end implementation. It uses
|
||||
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,
|
||||
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
|
||||
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
|
||||
|
@ -20,6 +20,7 @@ Housekeeping
|
||||
************
|
||||
|
||||
| `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 #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.
|
||||
|
@ -97,9 +97,6 @@
|
||||
"axios": "^0.21.1",
|
||||
"babelify": "~10.0.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",
|
||||
"bootstrap": "^4.3.1",
|
||||
"bootstrap-datepicker": "^1.8.0",
|
||||
|
@ -14,7 +14,6 @@ import ForeignServerSchema from './foreign_server.ui';
|
||||
define('pgadmin.node.foreign_server', [
|
||||
'sources/gettext', 'sources/url_for', 'sources/pgadmin',
|
||||
'pgadmin.browser', 'pgadmin.browser.collection',
|
||||
'pgadmin.browser.server.privilege',
|
||||
], function(gettext, url_for, pgAdmin, pgBrowser) {
|
||||
|
||||
// Extend the browser's collection class for foreign server collection
|
||||
|
@ -14,8 +14,8 @@ import _ from 'lodash';
|
||||
define('pgadmin.node.user_mapping', [
|
||||
'sources/gettext', 'sources/url_for',
|
||||
'sources/pgadmin', 'pgadmin.browser',
|
||||
'pgadmin.backform', 'pgadmin.browser.collection',
|
||||
], function(gettext, url_for, pgAdmin, pgBrowser, Backform) {
|
||||
'pgadmin.browser.collection',
|
||||
], function(gettext, url_for, pgAdmin, pgBrowser) {
|
||||
|
||||
// Extend the browser's collection class for user mapping collection
|
||||
if (!pgBrowser.Nodes['coll-user_mapping']) {
|
||||
@ -105,34 +105,6 @@ define('pgadmin.node.user_mapping', [
|
||||
}
|
||||
pgAdmin.Browser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
|
||||
// Defining schema for the user mapping node
|
||||
schema: [
|
||||
{
|
||||
id: 'name', label: gettext('User'), type: 'text',
|
||||
control: Backform.NodeListByNameControl, node: 'role',
|
||||
mode: ['edit', 'create', 'properties'], select2: { allowClear: false },
|
||||
disabled: function(m) { return !m.isNew(); },
|
||||
transform: function() {
|
||||
let self = this,
|
||||
node = self.field.get('schema_node');
|
||||
let res =
|
||||
Backform.NodeListByNameControl.prototype.defaults.transform.apply(
|
||||
this, arguments
|
||||
);
|
||||
res.unshift({
|
||||
label: 'CURRENT_USER', value: 'CURRENT_USER',
|
||||
image: 'icon-' + node.type,
|
||||
},{
|
||||
label: 'PUBLIC', value: 'PUBLIC', image: 'icon-' + node.type,
|
||||
});
|
||||
return res;
|
||||
},
|
||||
}, {
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
type: 'text', mode: ['properties'],
|
||||
}
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
|
@ -13,7 +13,7 @@ import ForeignDataWrapperSchema from './foreign_data_wrapper.ui';
|
||||
|
||||
define('pgadmin.node.foreign_data_wrapper', [
|
||||
'sources/gettext', 'sources/url_for', 'pgadmin.browser',
|
||||
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege',
|
||||
'pgadmin.browser.collection',
|
||||
], function(gettext, url_for, pgBrowser) {
|
||||
|
||||
// Extend the browser's collection class for foreign data wrapper collection
|
||||
|
@ -13,7 +13,7 @@ import { getNodePrivilegeRoleSchema } from '../../../../static/js/privilege.ui';
|
||||
|
||||
define('pgadmin.node.language', [
|
||||
'sources/gettext', 'sources/url_for', 'pgadmin.browser',
|
||||
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege',
|
||||
'pgadmin.browser.collection',
|
||||
], function(gettext, url_for, pgBrowser) {
|
||||
|
||||
// Extend the browser's collection class for languages collection
|
||||
|
@ -12,7 +12,7 @@ import PublicationSchema from './publication.ui';
|
||||
|
||||
define('pgadmin.node.publication', [
|
||||
'sources/gettext', 'sources/url_for', 'pgadmin.browser',
|
||||
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege',
|
||||
'pgadmin.browser.collection',
|
||||
], function(gettext, url_for, pgBrowser) {
|
||||
|
||||
// Extend the browser's collection class for publications collection
|
||||
|
@ -16,7 +16,7 @@ import { getNodeVariableSchema } from '../../../../../static/js/variable.ui';
|
||||
define('pgadmin.node.function', [
|
||||
'sources/gettext', 'sources/url_for', 'pgadmin.browser',
|
||||
'pgadmin.node.schema.dir/child', 'pgadmin.node.schema.dir/schema_child_tree_node',
|
||||
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege',
|
||||
'pgadmin.browser.collection',
|
||||
], function(
|
||||
gettext, url_for, pgBrowser, schemaChild, schemaChildTreeNode
|
||||
) {
|
||||
|
@ -17,7 +17,7 @@ define('pgadmin.node.procedure', [
|
||||
'sources/gettext', 'sources/url_for',
|
||||
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.node.schema.dir/child',
|
||||
'pgadmin.node.schema.dir/schema_child_tree_node',
|
||||
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege',
|
||||
'pgadmin.browser.collection',
|
||||
], function(gettext, url_for, pgAdmin, pgBrowser, schemaChild, schemaChildTreeNode) {
|
||||
|
||||
if (!pgBrowser.Nodes['coll-procedure']) {
|
||||
|
@ -16,7 +16,7 @@ import _ from 'lodash';
|
||||
define('pgadmin.node.trigger_function', [
|
||||
'sources/gettext', 'sources/url_for', 'pgadmin.browser',
|
||||
'pgadmin.node.schema.dir/child', 'pgadmin.node.schema.dir/schema_child_tree_node',
|
||||
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege',
|
||||
'pgadmin.browser.collection',
|
||||
], function(
|
||||
gettext, url_for, pgBrowser, schemaChild, schemaChildTreeNode
|
||||
) {
|
||||
|
@ -12,7 +12,7 @@ import EDBFuncSchema from './edbfunc.ui';
|
||||
/* Create and Register Function Collection and Node. */
|
||||
define('pgadmin.node.edbfunc', [
|
||||
'sources/gettext', 'sources/url_for', 'pgadmin.browser',
|
||||
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege',
|
||||
'pgadmin.browser.collection',
|
||||
], function(gettext, url_for, pgBrowser) {
|
||||
|
||||
if (!pgBrowser.Nodes['coll-edbfunc']) {
|
||||
|
@ -15,7 +15,6 @@ define('pgadmin.node.edbproc', [
|
||||
'sources/gettext', 'sources/url_for',
|
||||
'sources/pgadmin', 'pgadmin.browser',
|
||||
'pgadmin.node.edbfunc', 'pgadmin.browser.collection',
|
||||
'pgadmin.browser.server.privilege',
|
||||
], function(
|
||||
gettext, url_for, pgAdmin, pgBrowser, EdbFunction
|
||||
) {
|
||||
|
@ -12,7 +12,7 @@ import EDBVarSchema from './edbvar.ui';
|
||||
/* Create and Register Function Collection and Node. */
|
||||
define('pgadmin.node.edbvar', [
|
||||
'sources/gettext', 'sources/url_for', 'pgadmin.browser',
|
||||
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege',
|
||||
'pgadmin.browser.collection',
|
||||
], function(gettext, url_for, pgBrowser) {
|
||||
|
||||
if (!pgBrowser.Nodes['coll-edbvar']) {
|
||||
|
@ -10,297 +10,11 @@
|
||||
import PGSchema from './schema.ui';
|
||||
import { getNodePrivilegeRoleSchema } from '../../../../static/js/privilege.ui';
|
||||
import { getNodeListByName } from '../../../../../../static/js/node_ajax';
|
||||
import _ from 'lodash';
|
||||
|
||||
define('pgadmin.node.schema', [
|
||||
'sources/gettext', 'sources/url_for', 'jquery',
|
||||
'pgadmin.browser', 'pgadmin.backform', 'pgadmin.backgrid',
|
||||
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege',
|
||||
], function(gettext, url_for, $, pgBrowser, Backform, Backgrid) {
|
||||
|
||||
// VacuumSettings Collection to display all settings parameters as Grid
|
||||
Backform.VacuumCollectionControl =
|
||||
Backform.Control.extend({
|
||||
|
||||
grid_columns:undefined,
|
||||
|
||||
initialize: function() {
|
||||
Backform.Control.prototype.initialize.apply(this, arguments);
|
||||
let self = this,
|
||||
m = this.model,
|
||||
url = self.field.get('url');
|
||||
|
||||
if (url && m.isNew()) {
|
||||
let node = self.field.get('node'),
|
||||
node_data = self.field.get('node_data'),
|
||||
node_info = self.field.get('node_info'),
|
||||
full_url = node.generate_url.apply(
|
||||
node, [
|
||||
null, url, node_data, false, node_info,
|
||||
]),
|
||||
data;
|
||||
m.trigger('pgadmin-view:fetching', m, self.field);
|
||||
|
||||
// fetch default values for autovacuum fields
|
||||
$.ajax({
|
||||
async: false,
|
||||
url: full_url,
|
||||
})
|
||||
.done(function (res) {
|
||||
data = res;
|
||||
})
|
||||
.fail(function() {
|
||||
m.trigger('pgadmin-view:fetch:error', m, self.field);
|
||||
});
|
||||
m.trigger('pgadmin-view:fetched', m, self.field);
|
||||
|
||||
// Add fetched models into collection
|
||||
if (data && _.isArray(data)) {
|
||||
m.get(self.field.get('name')).reset(data, {silent: true});
|
||||
}
|
||||
}
|
||||
},
|
||||
events : {
|
||||
'keydown': 'keyDownHandler',
|
||||
},
|
||||
|
||||
keyDownHandler : function(event){
|
||||
// move the focus to editable input
|
||||
let lastButton = $(this.$el).find('button:last');
|
||||
let firstEditableCell = $(this.$el).find('td.editable:first');
|
||||
if (event.keyCode== 9 && !event.shiftKey){
|
||||
if ($(firstEditableCell).is(':visible')
|
||||
&& ($(event.target)).is($(lastButton))){
|
||||
$(firstEditableCell).trigger('click');
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
let self = this,
|
||||
attributes = self.field.attributes;
|
||||
|
||||
// remove grid
|
||||
if(self.grid) {
|
||||
self.grid.remove();
|
||||
delete self.grid;
|
||||
self.grid = undefined;
|
||||
}
|
||||
|
||||
self.$el.empty();
|
||||
|
||||
let gridHeader = _.template([
|
||||
'<div class="subnode-header">',
|
||||
'<% if (label && label != "") %> {',
|
||||
' <span class="control-label col-sm-4"><%-label%></span>',
|
||||
'}',
|
||||
'</div>'].join('\n')),
|
||||
gridBody = $('<div class="pgadmin-control-group backgrid form-group col-12 object subnode"></div>').append(
|
||||
gridHeader(attributes)
|
||||
);
|
||||
|
||||
// Initialize a new Grid instance
|
||||
let grid = self.grid = new Backgrid.Grid({
|
||||
columns: self.grid_columns,
|
||||
collection: self.model.get(self.field.get('name')),
|
||||
className: 'backgrid table table-bordered table-noouter-border table-hover',
|
||||
});
|
||||
|
||||
// render grid
|
||||
self.$el.addClass('mb-0');
|
||||
self.$el.append($(gridBody).append(grid.render().$el));
|
||||
|
||||
return self;
|
||||
},
|
||||
});
|
||||
|
||||
// We will use this function in VacuumSettings Control
|
||||
// to convert data type on the fly
|
||||
Backform.cellFunction = function(model) {
|
||||
let vartype = model.get('column_type');
|
||||
|
||||
switch(vartype) {
|
||||
case 'integer':
|
||||
return Backgrid.IntegerCell;
|
||||
case 'number':
|
||||
return Backgrid.NumberCell.extend({
|
||||
decimals: this.get('decimals') || Backgrid.NumberFormatter.prototype.defaults.decimals,
|
||||
});
|
||||
case 'string':
|
||||
return Backgrid.StringCell;
|
||||
default:
|
||||
return Backgrid.Cell;
|
||||
}
|
||||
};
|
||||
|
||||
// Define Security Model with fields and validation for VacuumSettings Control
|
||||
Backform.VacuumTableModel = pgBrowser.Node.Model.extend({
|
||||
defaults: {
|
||||
name: undefined,
|
||||
setting: undefined,
|
||||
label:undefined,
|
||||
value: undefined,
|
||||
column_type: undefined,
|
||||
},
|
||||
|
||||
toJSON: function(){
|
||||
let d = pgBrowser.Node.Model.prototype.toJSON.apply(this);
|
||||
delete d.label;
|
||||
delete d.setting;
|
||||
delete d.column_type;
|
||||
return d;
|
||||
},
|
||||
});
|
||||
|
||||
/* As Backform.VacuumSettingsSchema is commonly used in table & partition, so keeping it as it is for now.
|
||||
this and other supporting model can be removed after their migration to react. */
|
||||
// Extend the browser's collection class for VacuumSettingsModel
|
||||
Backform.VacuumSettingsSchema = [{
|
||||
id: 'spacer_ctrl', group: gettext('Table'), mode: ['edit', 'create'], type: 'spacer',
|
||||
},{
|
||||
id: 'autovacuum_custom', label: gettext('Custom auto-vacuum?'),
|
||||
group: gettext('Table'), mode: ['edit', 'create'],
|
||||
type: 'switch', controlLabelClassName: 'control-label pg-el-sm-4 pg-el-12',
|
||||
controlsClassName: 'pgadmin-controls pg-el-sm-8 pg-el-12',
|
||||
disabled: function(m) {
|
||||
// If table is partitioned table then disabled it.
|
||||
if (m.top && m.top.get('is_partitioned')) {
|
||||
// We also need to unset rest of all
|
||||
setTimeout(function() {
|
||||
m.set('autovacuum_custom', false);
|
||||
}, 10);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return m.top.inSchema.apply(this, [m]);
|
||||
},
|
||||
},{
|
||||
id: 'autovacuum_enabled', label: gettext('Autovacuum Enabled?'),
|
||||
group: gettext('Table'), mode: ['edit', 'create'],
|
||||
type: 'radioModern', controlLabelClassName: 'control-label pg-el-sm-4 pg-el-12',
|
||||
controlsClassName: 'pgadmin-controls pg-el-sm-8 pg-el-12',
|
||||
options: [
|
||||
{'label': gettext('Not set'), 'value': 'x'},
|
||||
{'label': gettext('Yes'), 'value': 't'},
|
||||
{'label': gettext('No'), 'value': 'f'},
|
||||
],
|
||||
deps: ['autovacuum_custom'],
|
||||
disabled: function(m) {
|
||||
if(!m.top.inSchema.apply(this, [m]) && m.get('autovacuum_custom')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We also need to unset rest of all
|
||||
setTimeout(function() {
|
||||
m.set('autovacuum_enabled', 'x');
|
||||
}, 10);
|
||||
return true;
|
||||
},
|
||||
},{
|
||||
id: 'vacuum_table', label: '',
|
||||
model: Backform.VacuumTableModel, editable: false, type: 'collection',
|
||||
canEdit: true, group: gettext('Table'),
|
||||
mode: ['edit', 'create'], url: 'get_table_vacuum',
|
||||
control: Backform.VacuumCollectionControl.extend({
|
||||
grid_columns :[
|
||||
{
|
||||
name: 'label', label: gettext('Label'),
|
||||
headerCell: Backgrid.Extension.CustomHeaderCell,
|
||||
cell: 'string', editable: false, cellHeaderClasses:'width_percent_40',
|
||||
},
|
||||
{
|
||||
name: 'value', label: gettext('Value'),
|
||||
cellHeaderClasses:'width_percent_30',
|
||||
decimals: 5,
|
||||
cellFunction: Backform.cellFunction, editable: function(m) {
|
||||
return m.handler.get('autovacuum_custom');
|
||||
}, headerCell: Backgrid.Extension.CustomHeaderCell,
|
||||
},
|
||||
{
|
||||
name: 'setting', label: gettext('Default'),
|
||||
cellHeaderClasses:'width_percent_30',
|
||||
headerCell: Backgrid.Extension.CustomHeaderCell,
|
||||
cellFunction: Backform.cellFunction, editable: false,
|
||||
},
|
||||
],
|
||||
}),
|
||||
deps: ['autovacuum_custom'],
|
||||
},{
|
||||
id: 'spacer_ctrl', group: gettext('TOAST table'), mode: ['edit', 'create'], type: 'spacer',
|
||||
},{
|
||||
id: 'toast_autovacuum', label: gettext('Custom auto-vacuum?'),
|
||||
group: gettext('TOAST table'), mode: ['edit', 'create'],
|
||||
type: 'switch', controlLabelClassName: 'control-label pg-el-sm-4 pg-el-12',
|
||||
controlsClassName: 'pgadmin-controls pg-el-sm-8 pg-el-12',
|
||||
disabled: function(m) {
|
||||
// We need to check additional condition to toggle enable/disable
|
||||
// for table auto-vacuum
|
||||
return !(!m.top.inSchema.apply(this, [m]) &&
|
||||
(m.isNew() || (m.get('toast_autovacuum_enabled') === true || m.top.get('hastoasttable') === true)));
|
||||
},
|
||||
},{
|
||||
id: 'toast_autovacuum_enabled', label: gettext('Autovacuum Enabled?'),
|
||||
group: gettext('TOAST table'), mode: ['edit', 'create'],
|
||||
type: 'radioModern', controlLabelClassName: 'control-label pg-el-sm-4 pg-el-12',
|
||||
controlsClassName: 'pgadmin-controls pg-el-sm-8 pg-el-12',
|
||||
options: [
|
||||
{'label': gettext('Not set'), 'value': 'x'},
|
||||
{'label': gettext('Yes'), 'value': 't'},
|
||||
{'label': gettext('No'), 'value': 'f'},
|
||||
],
|
||||
deps:['toast_autovacuum'],
|
||||
disabled: function(m) {
|
||||
// If in schema & in create mode then enable it
|
||||
if(!m.top.inSchema.apply(this, [m]) &&
|
||||
m.get('toast_autovacuum') === true) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m.isNew() || m.get('hastoasttable')) {
|
||||
// we also need to unset rest of all
|
||||
setTimeout(function() {
|
||||
m.set('toast_autovacuum_enabled', 'x');
|
||||
}, 10);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},{
|
||||
id: 'vacuum_toast', label: '',
|
||||
model: Backform.VacuumTableModel, type: 'collection', editable: function(m) {
|
||||
return m.isNew();
|
||||
},
|
||||
canEdit: true, group: gettext('TOAST table'),
|
||||
mode: ['properties', 'edit', 'create'], url: 'get_toast_table_vacuum',
|
||||
control: Backform.VacuumCollectionControl.extend({
|
||||
grid_columns :[
|
||||
{
|
||||
name: 'label', label: gettext('Label'),
|
||||
headerCell: Backgrid.Extension.CustomHeaderCell,
|
||||
cell: 'string', editable: false, cellHeaderClasses:'width_percent_40',
|
||||
},
|
||||
{
|
||||
name: 'value', label: gettext('Value'),
|
||||
cellHeaderClasses:'width_percent_30',
|
||||
headerCell: Backgrid.Extension.CustomHeaderCell,
|
||||
decimals: 5,
|
||||
cellFunction: Backform.cellFunction, editable: function(m) {
|
||||
return m.handler.get('toast_autovacuum');
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'setting', label: gettext('Default'),
|
||||
cellHeaderClasses:'width_percent_30',
|
||||
headerCell: Backgrid.Extension.CustomHeaderCell,
|
||||
cellFunction: Backform.cellFunction, editable: false,
|
||||
},
|
||||
],
|
||||
}),
|
||||
deps: ['toast_autovacuum'],
|
||||
}];
|
||||
'pgadmin.browser', 'pgadmin.browser.collection',
|
||||
], function(gettext, url_for, $, pgBrowser) {
|
||||
|
||||
// Extend the browser's collection class for schema collection
|
||||
if (!pgBrowser.Nodes['coll-schema']) {
|
||||
@ -376,38 +90,5 @@ define('pgadmin.node.schema', [
|
||||
};
|
||||
}
|
||||
|
||||
/* As TableChildSwitchCell is commonly used in index, column & TableDialog, so keeping it as it is for now.
|
||||
this and other supporting model can be removed after their migration to react. */
|
||||
// Switch Cell with Deps (specifically for table children)
|
||||
Backgrid.Extension.TableChildSwitchCell = Backgrid.Extension.SwitchCell.extend({
|
||||
initialize: function() {
|
||||
Backgrid.Extension.SwitchCell.prototype.initialize.apply(this, arguments);
|
||||
Backgrid.Extension.DependentCell.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
dependentChanged: function () {
|
||||
let model = this.model,
|
||||
column = this.column,
|
||||
editable = this.column.get('editable'),
|
||||
input = this.$el.find('input[type=checkbox]').first(),
|
||||
self_name = column.get('name'),
|
||||
is_editable;
|
||||
|
||||
is_editable = _.isFunction(editable) ? !!editable.apply(column, [model]) : !!editable;
|
||||
if (is_editable) {
|
||||
this.$el.addClass('editable');
|
||||
input.bootstrapToggle('disabled',false);
|
||||
} else {
|
||||
this.$el.removeClass('editable');
|
||||
input.bootstrapToggle('disabled',true);
|
||||
// Set self value into model to false
|
||||
setTimeout(function() { model.set(self_name, false); }, 10);
|
||||
}
|
||||
|
||||
this.delegateEvents();
|
||||
return this;
|
||||
},
|
||||
remove: Backgrid.Extension.DependentCell.prototype.remove,
|
||||
});
|
||||
|
||||
return pgBrowser.Nodes['schema'];
|
||||
});
|
||||
|
@ -13,9 +13,9 @@ import _ from 'lodash';
|
||||
|
||||
define('pgadmin.node.primary_key', [
|
||||
'sources/gettext', 'sources/url_for',
|
||||
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform', 'pgadmin.backgrid',
|
||||
'sources/pgadmin', 'pgadmin.browser',
|
||||
'pgadmin.browser.collection',
|
||||
], function(gettext, url_for, pgAdmin, pgBrowser, Backform, Backgrid) {
|
||||
], function(gettext, url_for, pgAdmin, pgBrowser) {
|
||||
|
||||
// Extend the browser's node class for index constraint node
|
||||
if (!pgBrowser.Nodes['primary_key']) {
|
||||
@ -166,447 +166,6 @@ define('pgadmin.node.primary_key', [
|
||||
// We can't update columns of existing index constraint.
|
||||
return !m.isNew();
|
||||
},
|
||||
// Define the schema for the index constraint node
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), type: 'text',
|
||||
mode: ['properties', 'create', 'edit'], editable:true,
|
||||
cellHeaderClasses:'width_percent_40',
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
type: 'text' , mode: ['properties'], editable: false,
|
||||
cellHeaderClasses:'width_percent_20',
|
||||
},{
|
||||
id: 'is_sys_obj', label: gettext('System primary key?'),
|
||||
cell:'boolean', type: 'switch', mode: ['properties'],
|
||||
},{
|
||||
id: 'comment', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline', mode: ['properties', 'create', 'edit'],
|
||||
deps:['name'], disabled:function(m) {
|
||||
let name = m.get('name');
|
||||
if (!(name && name != '')) {
|
||||
setTimeout(function(){
|
||||
if(m.get('comment') && m.get('comment') !== '') {
|
||||
m.set('comment', null);
|
||||
}
|
||||
},10);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},{
|
||||
id: 'columns', label: gettext('Columns'),
|
||||
type: 'collection', group: gettext('Definition'),
|
||||
editable: false,
|
||||
cell: Backgrid.StringCell.extend({
|
||||
initialize: function() {
|
||||
Backgrid.StringCell.prototype.initialize.apply(this, arguments);
|
||||
|
||||
let self = this,
|
||||
collection = this.model.get('columns');
|
||||
|
||||
// Do not listen for any event(s) for existing constraint.
|
||||
if (_.isUndefined(self.model.get('oid'))) {
|
||||
let tableCols = self.model.top.get('columns');
|
||||
self.listenTo(tableCols, 'remove' , self.removeColumn);
|
||||
self.listenTo(tableCols, 'change:name', self.resetColOptions);
|
||||
}
|
||||
|
||||
collection.on('pgadmin:multicolumn:updated', function() {
|
||||
self.render.apply(self);
|
||||
});
|
||||
self.listenTo(collection, 'add', self.render);
|
||||
self.listenTo(collection, 'remove', self.render);
|
||||
},
|
||||
removeColumn: function(m) {
|
||||
let self = this,
|
||||
removedCols = self.model.get('columns').where(
|
||||
{column: m.get('name')}
|
||||
);
|
||||
|
||||
self.model.get('columns').remove(removedCols);
|
||||
setTimeout(function () {
|
||||
self.render();
|
||||
}, 10);
|
||||
|
||||
let key = 'primary_key';
|
||||
setTimeout(function () {
|
||||
let constraints = self.model.top.get(key),
|
||||
removed = [];
|
||||
constraints.each(function(constraint) {
|
||||
if (constraint.get('columns').length == 0) {
|
||||
removed.push(constraint);
|
||||
}
|
||||
});
|
||||
constraints.remove(removed);
|
||||
},100);
|
||||
|
||||
},
|
||||
resetColOptions : function(m) {
|
||||
let self = this,
|
||||
updatedCols = self.model.get('columns').where(
|
||||
{column: m.previous('name')}
|
||||
);
|
||||
if (updatedCols.length > 0) {
|
||||
/*
|
||||
* Table column name has changed so update
|
||||
* column name in primary key as well.
|
||||
*/
|
||||
updatedCols[0].set(
|
||||
{'column': m.get('name')},
|
||||
{silent: true});
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
self.render();
|
||||
}, 10);
|
||||
},
|
||||
formatter: {
|
||||
fromRaw: function (rawValue) {
|
||||
return rawValue.pluck('column').toString();
|
||||
},
|
||||
toRaw: function (val) { return val; },
|
||||
},
|
||||
render: function() {
|
||||
return Backgrid.StringCell.prototype.render.apply(this, arguments);
|
||||
},
|
||||
remove: function() {
|
||||
let tableCols = this.model.top.get('columns'),
|
||||
primary_key_col = this.model.get('columns');
|
||||
|
||||
if (primary_key_col) {
|
||||
primary_key_col.off('pgadmin:multicolumn:updated');
|
||||
}
|
||||
|
||||
this.stopListening(tableCols, 'remove' , self.removeColumn);
|
||||
this.stopListening(tableCols, 'change:name' , self.resetColOptions);
|
||||
|
||||
Backgrid.StringCell.prototype.remove.apply(this, arguments);
|
||||
},
|
||||
}),
|
||||
canDelete: true, canAdd: true,
|
||||
control: Backform.MultiSelectAjaxControl.extend({
|
||||
defaults: _.extend(
|
||||
{},
|
||||
Backform.NodeListByNameControl.prototype.defaults,
|
||||
{
|
||||
select2: {
|
||||
multiple: true,
|
||||
allowClear: true,
|
||||
width: 'style',
|
||||
placeholder: gettext('Select the column(s)'),
|
||||
},
|
||||
}
|
||||
),
|
||||
keyPathAccessor: function(obj, path) {
|
||||
let res = obj;
|
||||
if(_.isArray(res)) {
|
||||
return _.map(res, function(o) { return o['column'];
|
||||
});
|
||||
}
|
||||
path = path.split('.');
|
||||
for (let path_val of path) {
|
||||
if (_.isNull(res)) return null;
|
||||
if (_.isEmpty(path_val)) continue;
|
||||
if (!_.isUndefined(res[path_val])) res = res[path_val];
|
||||
}
|
||||
return _.isObject(res) && !_.isArray(res) ? null : res;
|
||||
},
|
||||
initialize: function() {
|
||||
// Here we will decide if we need to call URL
|
||||
// Or fetch the data from parent columns collection
|
||||
let self = this;
|
||||
if(this.model.handler) {
|
||||
Backform.Select2Control.prototype.initialize.apply(this, arguments);
|
||||
// Do not listen for any event(s) for existing constraint.
|
||||
if (_.isUndefined(self.model.get('oid'))) {
|
||||
let tableCols = self.model.top.get('columns');
|
||||
self.listenTo(tableCols, 'remove' , self.resetColOptions);
|
||||
self.listenTo(tableCols, 'change:name', self.resetColOptions);
|
||||
}
|
||||
|
||||
self.custom_options();
|
||||
} else {
|
||||
Backform.MultiSelectAjaxControl.prototype.initialize.apply(this, arguments);
|
||||
}
|
||||
self.model.get('columns').on('pgadmin:multicolumn:updated', function() {
|
||||
self.render.apply(self);
|
||||
});
|
||||
},
|
||||
resetColOptions: function() {
|
||||
this.genResetColOptions();
|
||||
},
|
||||
custom_options: function() {
|
||||
this.genCustomOptions();
|
||||
},
|
||||
onChange: function() {
|
||||
let self = this,
|
||||
model = this.model,
|
||||
attrArr = this.field.get('name').split('.'),
|
||||
name = attrArr.shift(),
|
||||
vals = this.getValueFromDOM(),
|
||||
collection = model.get(name),
|
||||
removed = [];
|
||||
|
||||
this.stopListening(this.model, 'change:' + name, this.render);
|
||||
|
||||
/*
|
||||
* Iterate through all the values, and find out how many are already
|
||||
* present in the collection.
|
||||
*/
|
||||
collection.each(function(m) {
|
||||
let column = m.get('column'),
|
||||
idx = _.indexOf(vals, column);
|
||||
|
||||
if (idx > -1) {
|
||||
vals.splice(idx, 1);
|
||||
} else {
|
||||
removed.push(column);
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Adding new values
|
||||
*/
|
||||
|
||||
_.each(vals, function(v) {
|
||||
let m = new (self.field.get('model'))(
|
||||
{column: v}, { silent: true,
|
||||
top: self.model.top,
|
||||
collection: collection,
|
||||
handler: collection,
|
||||
});
|
||||
|
||||
collection.add(m);
|
||||
});
|
||||
|
||||
/*
|
||||
* Removing unwanted!
|
||||
*/
|
||||
_.each(removed, function(v) {
|
||||
collection.remove(collection.where({column: v}));
|
||||
});
|
||||
|
||||
this.listenTo(this.model, 'change:' + name, this.render);
|
||||
},
|
||||
remove: function() {
|
||||
if(this.model.handler) {
|
||||
let self = this,
|
||||
tableCols = self.model.top.get('columns');
|
||||
self.stopListening(tableCols, 'remove' , self.resetColOptions);
|
||||
self.stopListening(tableCols, 'change:name' , self.resetColOptions);
|
||||
self.model.get('columns').off('pgadmin:multicolumn:updated');
|
||||
|
||||
Backform.Select2Control.prototype.remove.apply(this, arguments);
|
||||
|
||||
} else {
|
||||
Backform.MultiSelectAjaxControl.prototype.remove.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
let index = this.model.get('index');
|
||||
if(!_.isUndefined(index) && index != '') {
|
||||
let col = this.model.get('columns');
|
||||
col.reset([], {silent: true});
|
||||
}
|
||||
return Backform.Select2Control.prototype.render.apply(this, arguments);
|
||||
},
|
||||
}),
|
||||
deps: ['index'], node: 'column',
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
defaults: {
|
||||
column: undefined,
|
||||
},
|
||||
validate: function() {
|
||||
return null;
|
||||
},
|
||||
}),
|
||||
transform : function(data){
|
||||
let res = [];
|
||||
if (data && _.isArray(data)) {
|
||||
_.each(data, function(d) {
|
||||
res.push({label: d.label, value: d.label, image:'icon-column'});
|
||||
});
|
||||
}
|
||||
return res;
|
||||
},
|
||||
select2:{allowClear:false},
|
||||
readonly: function(m) {
|
||||
return this.checkReadOnly(m);
|
||||
},
|
||||
disabled: function(m) {
|
||||
// Disable if index is selected.
|
||||
let index = m.get('index');
|
||||
return !(_.isUndefined(index) || index == '');
|
||||
},
|
||||
},{
|
||||
id: 'include', label: gettext('Include columns'),
|
||||
type: 'array', group: gettext('Definition'),
|
||||
editable: false,
|
||||
canDelete: true, canAdd: true, mode: ['properties', 'create', 'edit'],
|
||||
visible: function(m) {
|
||||
/* In table properties, m.node_info is not available */
|
||||
m = m.top;
|
||||
return (!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
|
||||
&& !_.isUndefined(m.node_info.server.version) &&
|
||||
m.node_info.server.version >= 110000);
|
||||
},
|
||||
control: Backform.MultiSelectAjaxControl.extend({
|
||||
defaults: _.extend(
|
||||
{},
|
||||
Backform.NodeListByNameControl.prototype.defaults,
|
||||
{
|
||||
select2: {
|
||||
allowClear: false,
|
||||
width: 'style',
|
||||
multiple: true,
|
||||
placeholder: gettext('Select the column(s)'),
|
||||
},
|
||||
}
|
||||
),
|
||||
initialize: function() {
|
||||
// Here we will decide if we need to call URL
|
||||
// Or fetch the data from parent columns collection
|
||||
let self = this;
|
||||
if(this.model.handler) {
|
||||
Backform.Select2Control.prototype.initialize.apply(this, arguments);
|
||||
// Do not listen for any event(s) for existing constraint.
|
||||
if (_.isUndefined(self.model.get('oid'))) {
|
||||
let tableCols = self.model.top.get('columns');
|
||||
self.listenTo(tableCols, 'remove' , self.resetColOptions);
|
||||
self.listenTo(tableCols, 'change:name', self.resetColOptions);
|
||||
}
|
||||
|
||||
self.custom_options();
|
||||
} else {
|
||||
Backform.MultiSelectAjaxControl.prototype.initialize.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
resetColOptions: function() {
|
||||
this.genResetColOptions();
|
||||
},
|
||||
custom_options: function() {
|
||||
this.genCustomOptions();
|
||||
},
|
||||
}),
|
||||
deps: ['index'], node: 'column',
|
||||
readonly: function(m) {
|
||||
return this.checkReadOnly(m);
|
||||
},
|
||||
disabled: function(m) {
|
||||
// Disable if index is selected.
|
||||
let index = m.get('index');
|
||||
if(_.isUndefined(index) || index == '') {
|
||||
return false;
|
||||
} else {
|
||||
setTimeout(function(){
|
||||
m.set('include', []);
|
||||
},10);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},{
|
||||
id: 'spcname', label: gettext('Tablespace'),
|
||||
type: 'text', group: gettext('Definition'),
|
||||
control: 'node-list-by-name', node: 'tablespace',
|
||||
deps: ['index'],
|
||||
select2:{allowClear:false},
|
||||
filter: function(m) {
|
||||
// Don't show pg_global tablespace in selection.
|
||||
return (m.label != 'pg_global');
|
||||
},
|
||||
disabled: function(m) {
|
||||
// Disable if index is selected.
|
||||
m = m.top || m;
|
||||
let index = m.get('index');
|
||||
if(_.isUndefined(index) || index == '') {
|
||||
return false;
|
||||
} else {
|
||||
setTimeout(function(){
|
||||
m.set('spcname', '');
|
||||
},10);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},{
|
||||
id: 'index', label: gettext('Index'),
|
||||
mode: ['create'],
|
||||
type: 'text', group: gettext('Definition'),
|
||||
control: Backform.NodeListByNameControl.extend({
|
||||
initialize:function() {
|
||||
Backform.NodeListByNameControl.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
}),
|
||||
select2:{allowClear:true}, node: 'index',
|
||||
readonly: function(m) {
|
||||
// If we are in table edit mode then disable it
|
||||
if (_.has(m, 'top') && !_.isUndefined(m.top)
|
||||
&& !m.top.isNew()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// We can't update index of existing index constraint.
|
||||
return !m.isNew();
|
||||
},
|
||||
// We will not show this field in Create Table mode
|
||||
visible: function(m) {
|
||||
return !_.isUndefined(m.top.node_info['table']);
|
||||
},
|
||||
},{
|
||||
id: 'fillfactor', label: gettext('Fill factor'), deps: ['index'],
|
||||
type: 'int', group: gettext('Definition'), allowNull: true,
|
||||
disabled: function(m) {
|
||||
// Disable if index is selected.
|
||||
let index = m.get('index');
|
||||
if(_.isUndefined(index) || index == '') {
|
||||
return false;
|
||||
} else {
|
||||
setTimeout(function(){
|
||||
m.set('fillfactor', null);
|
||||
},10);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},{
|
||||
id: 'condeferrable', label: gettext('Deferrable?'),
|
||||
type: 'switch', group: gettext('Definition'), deps: ['index'],
|
||||
readonly: function(m) {
|
||||
return this.checkReadOnly(m);
|
||||
},
|
||||
disabled: function(m) {
|
||||
// Disable if index is selected.
|
||||
let index = m.get('index');
|
||||
if(_.isUndefined(index) || index == '') {
|
||||
return false;
|
||||
} else {
|
||||
setTimeout(function(){
|
||||
if(m.get('condeferrable'))
|
||||
m.set('condeferrable', false);
|
||||
},10);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},{
|
||||
id: 'condeferred', label: gettext('Deferred?'),
|
||||
type: 'switch', group: gettext('Definition'),
|
||||
deps: ['condeferrable'],
|
||||
readonly: function(m) {
|
||||
return this.checkReadOnly(m);
|
||||
},
|
||||
disabled: function(m) {
|
||||
// Disable if condeferred is false or unselected.
|
||||
if(m.get('condeferrable')) {
|
||||
return false;
|
||||
} else {
|
||||
setTimeout(function(){
|
||||
if(m.get('condeferred'))
|
||||
m.set('condeferred', false);
|
||||
},10);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
validate: function() {
|
||||
this.errorModel.clear();
|
||||
// Clear parent's error as well
|
||||
|
@ -19,7 +19,6 @@ define('pgadmin.node.mview', [
|
||||
'sources/pgadmin', 'pgadmin.browser',
|
||||
'pgadmin.node.schema.dir/child',
|
||||
'pgadmin.node.schema.dir/schema_child_tree_node', 'sources/utils',
|
||||
'pgadmin.browser.server.privilege',
|
||||
], function(
|
||||
gettext, url_for, $, pgAdmin, pgBrowser,
|
||||
schemaChild, schemaChildTreeNode, commonUtils
|
||||
|
@ -14,7 +14,7 @@ import ViewSchema from './view.ui';
|
||||
define('pgadmin.node.view', [
|
||||
'sources/gettext', 'sources/url_for', 'pgadmin.browser',
|
||||
'pgadmin.node.schema.dir/child', 'pgadmin.node.schema.dir/schema_child_tree_node',
|
||||
'pgadmin.browser.server.privilege', 'pgadmin.node.rule',
|
||||
'pgadmin.node.rule',
|
||||
], function(
|
||||
gettext, url_for, pgBrowser, schemaChild, schemaChildTreeNode
|
||||
) {
|
||||
|
@ -19,7 +19,6 @@ define('pgadmin.node.database', [
|
||||
'sources/gettext', 'sources/url_for', 'jquery',
|
||||
'sources/pgadmin', 'pgadmin.browser.utils',
|
||||
'pgadmin.authenticate.kerberos', 'pgadmin.browser.collection',
|
||||
'pgadmin.browser.server.privilege', 'pgadmin.browser.server.variable',
|
||||
], function(gettext, url_for, $, pgAdmin, pgBrowser, Kerberos) {
|
||||
|
||||
if (!pgBrowser.Nodes['coll-database']) {
|
||||
|
@ -11,9 +11,8 @@ import { getNodePgaJobStepSchema } from './pga_jobstep.ui';
|
||||
import _ from 'lodash';
|
||||
|
||||
define('pgadmin.node.pga_jobstep', [
|
||||
'sources/gettext', 'sources/url_for', 'pgadmin.browser', 'backform',
|
||||
'backgrid', 'pgadmin.backform',
|
||||
], function(gettext, url_for, pgBrowser, Backform) {
|
||||
'sources/gettext', 'sources/url_for', 'pgadmin.browser',
|
||||
], function(gettext, url_for, pgBrowser) {
|
||||
|
||||
if (!pgBrowser.Nodes['coll-pga_jobstep']) {
|
||||
pgBrowser.Nodes['coll-pga_jobstep'] =
|
||||
@ -93,37 +92,6 @@ define('pgadmin.node.pga_jobstep', [
|
||||
}
|
||||
},
|
||||
idAttribute: 'jstid',
|
||||
schema: [{
|
||||
id: 'jstid', label: gettext('ID'), type: 'int',
|
||||
cellHeaderClasses: 'width_percent_5', mode: ['properties'],
|
||||
},{
|
||||
id: 'jstname', label: gettext('Name'), type: 'text',
|
||||
cellHeaderClasses: 'width_percent_60',
|
||||
},{
|
||||
id: 'jstenabled', label: gettext('Enabled?'),
|
||||
type: 'switch',
|
||||
},{
|
||||
id: 'jstkind', label: gettext('Kind'), type: 'switch',
|
||||
options: {
|
||||
'onText': gettext('SQL'), 'offText': gettext('Batch'),
|
||||
'onColor': 'primary', 'offColor': 'primary',
|
||||
}, control: Backform.SwitchControl,
|
||||
},{
|
||||
id: 'jstconntype', label: gettext('Connection type'),
|
||||
type: 'switch', deps: ['jstkind'], mode: ['properties'],
|
||||
disabled: function(m) { return !m.get('jstkind'); },
|
||||
options: {
|
||||
'onText': gettext('Local'), 'offText': gettext('Remote'),
|
||||
'onColor': 'primary', 'offColor': 'primary', width: '65',
|
||||
},
|
||||
},{
|
||||
id: 'jstonerror', label: gettext('On error'), cell: 'select2',
|
||||
control: 'select2', options: [
|
||||
{'label': gettext('Fail'), 'value': 'f'},
|
||||
{'label': gettext('Success'), 'value': 's'},
|
||||
{'label': gettext('Ignore'), 'value': 'i'},
|
||||
], select2: {allowClear: false},
|
||||
}],
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
});
|
@ -18,7 +18,6 @@ define('pgadmin.node.server', [
|
||||
'sources/pgadmin', 'pgadmin.browser',
|
||||
'pgadmin.user_management.current_user',
|
||||
'pgadmin.authenticate.kerberos',
|
||||
'pgadmin.browser.server.privilege',
|
||||
], function(
|
||||
gettext, url_for, $, pgAdmin, pgBrowser,
|
||||
current_user, Kerberos,
|
||||
|
@ -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;
|
||||
});
|
@ -15,8 +15,7 @@ import _ from 'lodash';
|
||||
|
||||
define('pgadmin.node.tablespace', [
|
||||
'sources/gettext', 'sources/url_for',
|
||||
'pgadmin.browser', 'pgadmin.browser.collection', 'pgadmin.browser.node.ui',
|
||||
'pgadmin.browser.server.privilege',
|
||||
'pgadmin.browser', 'pgadmin.browser.collection',
|
||||
], function(
|
||||
gettext, url_for, pgBrowser
|
||||
) {
|
||||
|
@ -12,8 +12,7 @@ import _ from 'lodash';
|
||||
define([
|
||||
'sources/gettext', 'sources/pgadmin',
|
||||
'sources/browser/generate_url',
|
||||
'pgadmin.backform', 'pgadmin.backgrid',
|
||||
'pgadmin.browser.node', 'backgrid.select.all',
|
||||
'pgadmin.browser.node',
|
||||
], function(gettext, pgAdmin, generateUrl) {
|
||||
|
||||
let pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
|
||||
|
@ -10,17 +10,16 @@
|
||||
import {getNodeView, removeNodeView} from './node_view';
|
||||
import Notify from '../../../static/js/helpers/Notifier';
|
||||
import _ from 'lodash';
|
||||
import { pgHandleItemError } from '../../../static/js/utils';
|
||||
|
||||
define('pgadmin.browser.node', [
|
||||
'sources/gettext', 'jquery', 'sources/pgadmin',
|
||||
'backbone', 'pgadmin.browser.datamodel',
|
||||
'backform', 'sources/browser/generate_url', 'pgadmin.help', 'sources/utils',
|
||||
'pgadmin.browser.utils', 'pgadmin.backform',
|
||||
'sources/browser/generate_url', 'pgadmin.help', 'sources/utils',
|
||||
'pgadmin.browser.utils',
|
||||
], function(
|
||||
gettext, $, pgAdmin,
|
||||
Backbone, pgBrowser,
|
||||
Backform, generateUrl, help,
|
||||
generateUrl, help,
|
||||
commonUtils
|
||||
) {
|
||||
|
||||
@ -294,210 +293,6 @@ define('pgadmin.browser.node', [
|
||||
return true;
|
||||
}
|
||||
},
|
||||
///////
|
||||
// Generate a Backform view using the node's model type
|
||||
//
|
||||
// Used to generate view for the particular node properties, edit,
|
||||
// creation.
|
||||
getView: function(item, type, el, node, formType, callback, ctx, cancelFunc) {
|
||||
let that = this;
|
||||
|
||||
if (!this.type || this.type == '')
|
||||
// We have no information, how to generate view for this type.
|
||||
return null;
|
||||
|
||||
if (this.model) {
|
||||
// This will be the URL, used for object manipulation.
|
||||
// i.e. Create, Update in these cases
|
||||
let urlBase = this.generate_url(item, type, node, false, null, that.url_jump_after_node);
|
||||
|
||||
if (!urlBase)
|
||||
// Ashamed of myself, I don't know how to manipulate this
|
||||
// node.
|
||||
return null;
|
||||
|
||||
let attrs = {};
|
||||
|
||||
// In order to get the object data from the server, we must set
|
||||
// object-id in the model (except in the create mode).
|
||||
if (type !== 'create') {
|
||||
attrs[this.model.idAttribute || this.model.prototype.idAttribute ||
|
||||
'id'] = node._id;
|
||||
}
|
||||
|
||||
// We know - which data model to be used for this object.
|
||||
let info = pgBrowser.tree.getTreeNodeHierarchy(item),
|
||||
newModel = new(this.model.extend({
|
||||
urlRoot: urlBase,
|
||||
}))(
|
||||
attrs, {
|
||||
node_info: info,
|
||||
}
|
||||
),
|
||||
fields = Backform.generateViewSchema(
|
||||
info, newModel, type, this, node
|
||||
);
|
||||
|
||||
if (type == 'create' || type == 'edit') {
|
||||
|
||||
if (callback && ctx) {
|
||||
callback = callback.bind(ctx);
|
||||
} else {
|
||||
callback = function() {
|
||||
console.warn(
|
||||
'Broke something!!! Why we don\'t have the callback or the context???'
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
let onSessionInvalid = function(msg) {
|
||||
let alertMessage = `
|
||||
<div class="error-in-footer">
|
||||
<div class="d-flex px-2 py-1">
|
||||
<div class="pr-2">
|
||||
<i class="fa fa-exclamation-triangle text-danger" aria-hidden="true" role="img"></i>
|
||||
</div>
|
||||
<div role="alert" class="alert-text">${msg}</div>
|
||||
<div class="ml-auto close-error-bar">
|
||||
<a class="close-error fa fa-times text-danger"></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
if (!_.isUndefined(that.statusBar)) {
|
||||
that.statusBar.html(alertMessage).css('visibility', 'visible');
|
||||
that.statusBar.find('a.close-error').bind('click', function() {
|
||||
this.empty().css('visibility', 'hidden');
|
||||
}.bind(that.statusBar));
|
||||
}
|
||||
|
||||
let sessHasChanged = false;
|
||||
if(this.sessChanged && this.sessChanged()){
|
||||
sessHasChanged = true;
|
||||
}
|
||||
callback(true, sessHasChanged);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
let onSessionValidated = function(sessHasChanged) {
|
||||
|
||||
if (!_.isUndefined(that.statusBar)) {
|
||||
that.statusBar.empty().css('visibility', 'hidden');
|
||||
}
|
||||
|
||||
callback(false, sessHasChanged);
|
||||
};
|
||||
|
||||
callback(false, false);
|
||||
|
||||
newModel.on('pgadmin-session:valid', onSessionValidated);
|
||||
newModel.on('pgadmin-session:invalid', onSessionInvalid);
|
||||
}
|
||||
|
||||
let view;
|
||||
// 'schema' has the information about how to generate the form.
|
||||
if (_.size(fields)) {
|
||||
// This will contain the actual view
|
||||
|
||||
if (formType == 'fieldset') {
|
||||
// It is used to show, edit, create the object in the
|
||||
// properties tab.
|
||||
view = new Backform.Accordian({
|
||||
el: el,
|
||||
model: newModel,
|
||||
schema: fields,
|
||||
});
|
||||
} else {
|
||||
// This generates a view to be used by the node dialog
|
||||
// (for create/edit operation).
|
||||
view = new Backform.Dialog({
|
||||
el: el,
|
||||
model: newModel,
|
||||
schema: fields,
|
||||
});
|
||||
}
|
||||
|
||||
let setFocusOnEl = function() {
|
||||
let container = $(el).find('.tab-content:first > .tab-pane.active:first');
|
||||
commonUtils.findAndSetFocus(container);
|
||||
};
|
||||
|
||||
if (!newModel.isNew()) {
|
||||
// This is definetely not in create mode
|
||||
let msgDiv = '<div role="status" class="pg-panel-message pg-panel-properties-message">' +
|
||||
gettext('Retrieving data from the server...') + '</div>',
|
||||
$msgDiv = $(msgDiv);
|
||||
let timer = setTimeout(function(_ctx) {
|
||||
// notify user if request is taking longer than 1 second
|
||||
|
||||
if (!_.isUndefined(_ctx)) {
|
||||
$msgDiv.appendTo(_ctx);
|
||||
}
|
||||
}, 1000, ctx);
|
||||
|
||||
|
||||
let fetchAjaxHook = function() {
|
||||
newModel.fetch({
|
||||
success: function() {
|
||||
// Clear timeout and remove message
|
||||
clearTimeout(timer);
|
||||
$msgDiv.addClass('d-none');
|
||||
|
||||
// We got the latest attributes of the object. Render the view
|
||||
// now.
|
||||
view.render();
|
||||
setFocusOnEl();
|
||||
newModel.startNewSession();
|
||||
},
|
||||
error: function(model, xhr, options) {
|
||||
let _label = that && item ?
|
||||
pgBrowser.tree.getTreeNodeHierarchy(
|
||||
item
|
||||
)[that.type].label : '';
|
||||
pgBrowser.Events.trigger(
|
||||
'pgadmin:node:retrieval:error', 'properties',
|
||||
xhr, options.textStatus, options.errorThrown, item
|
||||
);
|
||||
if (!pgHandleItemError(xhr, {
|
||||
item: item,
|
||||
info: info,
|
||||
}
|
||||
)) {
|
||||
Notify.pgNotifier(
|
||||
options.textStatus, xhr,
|
||||
gettext('Error retrieving properties - %s', options.errorThrown || _label),
|
||||
function(msg) {
|
||||
if(msg === 'CRYPTKEY_SET') {
|
||||
fetchAjaxHook();
|
||||
} else {
|
||||
console.warn(arguments);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
// Close the panel (if could not fetch properties)
|
||||
if (cancelFunc) {
|
||||
cancelFunc();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
fetchAjaxHook();
|
||||
} else {
|
||||
// Yay - render the view now!
|
||||
view.render();
|
||||
setFocusOnEl();
|
||||
newModel.startNewSession();
|
||||
}
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
addUtilityPanel: function(width, height, docker) {
|
||||
let body = window.document.body,
|
||||
el = document.createElement('div'),
|
||||
@ -1182,8 +977,6 @@ define('pgadmin.browser.node', [
|
||||
tree = pgAdmin.Browser.tree,
|
||||
j = panel.$container.find('.obj_properties').first(),
|
||||
view = j.data('obj-view'),
|
||||
content = $('<div></div>')
|
||||
.addClass('pg-prop-content col-12'),
|
||||
confirm_close = true;
|
||||
|
||||
// Handle key press events for Cancel, save and help button
|
||||
@ -1220,70 +1013,11 @@ define('pgadmin.browser.node', [
|
||||
});
|
||||
}, 200); // wait for panel tab to render
|
||||
|
||||
// Template function to create the status bar
|
||||
let createStatusBar = function(location) {
|
||||
let statusBar = $('<div role="status"></div>').addClass(
|
||||
'pg-prop-status-bar'
|
||||
).appendTo(j);
|
||||
statusBar.css('visibility', 'hidden');
|
||||
if (location == 'header') {
|
||||
statusBar.appendTo(that.header);
|
||||
} else {
|
||||
statusBar.prependTo(that.footer);
|
||||
}
|
||||
that.statusBar = statusBar;
|
||||
return statusBar;
|
||||
}.bind(panel),
|
||||
// Template function to create the button-group
|
||||
createButtons = function(buttons, location, extraClasses) {
|
||||
// Arguments must be non-zero length array of type
|
||||
// object, which contains following attributes:
|
||||
// label, type, extraClasses, register
|
||||
if (buttons && _.isArray(buttons) && buttons.length > 0) {
|
||||
// All buttons will be created within a single
|
||||
// div area.
|
||||
let btnGroup =
|
||||
$('<div class="pg-prop-btn-group"></div>'),
|
||||
// Template used for creating a button
|
||||
tmpl = _.template([
|
||||
'<button tabindex="0" type="<%= type %>" ',
|
||||
'class="btn <%=extraClasses.join(\' \')%>"',
|
||||
'<% if (disabled) { %> disabled="disabled"<% } %> title="<%-tooltip%>"',
|
||||
'<% if (label != "") {} else { %> aria-label="<%-tooltip%>"<% } %> >',
|
||||
'<span class="<%= icon %>"></span><% if (label != "") { %> <%-label%><% } %></button>',
|
||||
].join(' '));
|
||||
if (location == 'header') {
|
||||
btnGroup.appendTo(that.header);
|
||||
} else {
|
||||
btnGroup.appendTo(that.footer);
|
||||
}
|
||||
if (extraClasses) {
|
||||
btnGroup.addClass(extraClasses);
|
||||
}
|
||||
_.each(buttons, function(btn) {
|
||||
// Create the actual button, and append to
|
||||
// the group div
|
||||
|
||||
// icon may not present for this button
|
||||
if (!btn.icon) {
|
||||
btn.icon = '';
|
||||
}
|
||||
let b = $(tmpl(btn));
|
||||
btnGroup.append(b);
|
||||
// Register is a callback to set callback
|
||||
// for certain operation for this button.
|
||||
btn.register(b);
|
||||
});
|
||||
return btnGroup;
|
||||
}
|
||||
return null;
|
||||
}.bind(panel),
|
||||
// Callback to show object properties
|
||||
properties = function() {
|
||||
// Callback to show object properties
|
||||
let properties = function() {
|
||||
|
||||
// Avoid unnecessary reloads
|
||||
let i = tree.selected(),
|
||||
d = i && tree.itemData(i),
|
||||
treeHierarchy = tree.getTreeNodeHierarchy(i);
|
||||
|
||||
// Cache the current IDs for next time
|
||||
@ -1291,106 +1025,11 @@ define('pgadmin.browser.node', [
|
||||
|
||||
/* Remove any dom rendered by getNodeView */
|
||||
removeNodeView(j[0]);
|
||||
if(that.getSchema) {
|
||||
let treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item);
|
||||
getNodeView(
|
||||
that.type, treeNodeInfo, 'properties', data, 'tab', j[0], this, onEdit
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!content.hasClass('has-pg-prop-btn-group'))
|
||||
content.addClass('has-pg-prop-btn-group');
|
||||
|
||||
// We need to release any existing view, before
|
||||
// creating new view.
|
||||
if (view) {
|
||||
// Release the view
|
||||
view.remove({
|
||||
data: true,
|
||||
internal: true,
|
||||
silent: true,
|
||||
});
|
||||
// Deallocate the view
|
||||
// delete view;
|
||||
view = null;
|
||||
// Reset the data object
|
||||
j.data('obj-view', null);
|
||||
}
|
||||
// Make sure the HTML element is empty.
|
||||
j.empty();
|
||||
that.header = $('<div></div>').addClass(
|
||||
'pg-prop-header'
|
||||
).appendTo(j);
|
||||
that.footer = $('<div></div>').addClass(
|
||||
'pg-prop-footer'
|
||||
).appendTo(j);
|
||||
|
||||
// Create a view to show the properties in fieldsets
|
||||
view = that.getView(item, 'properties', content, data, 'fieldset', undefined, j);
|
||||
if (view) {
|
||||
// Save it for release it later
|
||||
j.data('obj-view', view);
|
||||
|
||||
// Create proper buttons
|
||||
|
||||
let buttons = [];
|
||||
|
||||
buttons.push({
|
||||
label: gettext('Edit'),
|
||||
type: 'edit',
|
||||
tooltip: gettext('Edit'),
|
||||
extraClasses: ['btn', 'btn-primary', 'pull-right', 'm-1'],
|
||||
icon: 'fa fa-sm fa-pencil-alt',
|
||||
disabled: _.isFunction(that.canEdit) ? !that.canEdit.apply(that, [d, i]) : !that.canEdit,
|
||||
register: function(btn) {
|
||||
btn.on('click',() => {
|
||||
onEdit();
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
buttons.push({
|
||||
label: '',
|
||||
type: 'help',
|
||||
tooltip: gettext('SQL help for this object type.'),
|
||||
extraClasses: ['btn-primary-icon', 'btn-primary-icon', 'm-1'],
|
||||
icon: 'fa fa-info',
|
||||
disabled: (that.sqlAlterHelp == '' && that.sqlCreateHelp == '' && !that.epasHelp) ? true : false,
|
||||
register: function(btn) {
|
||||
btn.on('click',() => {
|
||||
onSqlHelp();
|
||||
});
|
||||
},
|
||||
});
|
||||
createButtons(buttons, 'header', 'pg-prop-btn-group-above');
|
||||
}
|
||||
j.append(content);
|
||||
}.bind(panel),
|
||||
onSqlHelp = function() {
|
||||
// Construct the URL
|
||||
let server = pgBrowser.tree.getTreeNodeHierarchy(item).server;
|
||||
|
||||
let url = pgBrowser.utils.pg_help_path;
|
||||
let fullUrl = '';
|
||||
|
||||
if (server.server_type == 'ppas' && that.epasHelp) {
|
||||
fullUrl = help.getEPASHelpUrl(server.version);
|
||||
} else {
|
||||
if (that.sqlCreateHelp == '' && that.sqlAlterHelp != '') {
|
||||
fullUrl = help.getHelpUrl(url, that.sqlAlterHelp, server.version);
|
||||
} else if (that.sqlCreateHelp != '' && that.sqlAlterHelp == '') {
|
||||
fullUrl = help.getHelpUrl(url, that.sqlCreateHelp, server.version);
|
||||
} else {
|
||||
if (view.model.isNew()) {
|
||||
fullUrl = help.getHelpUrl(url, that.sqlCreateHelp, server.version);
|
||||
} else {
|
||||
fullUrl = help.getHelpUrl(url, that.sqlAlterHelp, server.version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.open(fullUrl, 'postgres_help');
|
||||
let treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item);
|
||||
getNodeView(
|
||||
that.type, treeNodeInfo, 'properties', data, 'tab', j[0], this, onEdit
|
||||
);
|
||||
return;
|
||||
}.bind(panel),
|
||||
|
||||
onDialogHelp = function() {
|
||||
@ -1427,52 +1066,6 @@ define('pgadmin.browser.node', [
|
||||
}
|
||||
}.bind(panel),
|
||||
|
||||
warnBeforeAttributeChange = function(yes_callback) {
|
||||
let $props = this.$container.find('.obj_properties').first(),
|
||||
objview = $props && $props.data('obj-view'),
|
||||
self = this;
|
||||
|
||||
if (objview && objview.model && !_.isUndefined(objview.model.warn_text) && !_.isNull(objview.model.warn_text)) {
|
||||
let warn_text;
|
||||
warn_text = gettext(objview.model.warn_text);
|
||||
if(objview.model.sessChanged()){
|
||||
Notify.confirm(
|
||||
gettext('Warning'),
|
||||
warn_text,
|
||||
function() {
|
||||
setTimeout(function(){
|
||||
yes_callback();
|
||||
}.bind(self), 50);
|
||||
return true;
|
||||
},
|
||||
function() {
|
||||
return true;
|
||||
}
|
||||
);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
yes_callback();
|
||||
return true;
|
||||
}
|
||||
}.bind(panel),
|
||||
|
||||
informBeforeAttributeChange = function(ok_callback) {
|
||||
let $props = this.$container.find('.obj_properties').first(),
|
||||
objview = $props && $props.data('obj-view');
|
||||
|
||||
if (objview && objview.model && !_.isUndefined(objview.model.inform_text) && !_.isNull(objview.model.inform_text)) {
|
||||
Notify.alert(
|
||||
gettext('Warning'),
|
||||
gettext(objview.model.inform_text)
|
||||
);
|
||||
|
||||
}
|
||||
ok_callback();
|
||||
return true;
|
||||
}.bind(panel),
|
||||
|
||||
onSave = function(_view, saveBtn) {
|
||||
let m = _view.model,
|
||||
d = m.toJSON(true),
|
||||
@ -1547,229 +1140,41 @@ define('pgadmin.browser.node', [
|
||||
/* Remove any dom rendered by getNodeView */
|
||||
removeNodeView(j[0]);
|
||||
/* getSchema is a schema for React. Get the react node view */
|
||||
if(that.getSchema) {
|
||||
let treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item);
|
||||
getNodeView(
|
||||
that.type, treeNodeInfo, action, data, 'dialog', j[0], this, onEdit,
|
||||
(nodeData)=>{
|
||||
if(nodeData.node) {
|
||||
onSaveFunc(nodeData.node, treeNodeInfo);
|
||||
// Removing the node-prop property of panel
|
||||
// so that we show updated data on panel
|
||||
let pnlProperties = pgBrowser.docker.findPanels('properties')[0],
|
||||
pnlSql = pgBrowser.docker.findPanels('sql')[0],
|
||||
pnlStats = pgBrowser.docker.findPanels('statistics')[0],
|
||||
pnlDependencies = pgBrowser.docker.findPanels('dependencies')[0],
|
||||
pnlDependents = pgBrowser.docker.findPanels('dependents')[0];
|
||||
let treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item);
|
||||
getNodeView(
|
||||
that.type, treeNodeInfo, action, data, 'dialog', j[0], this, onEdit,
|
||||
(nodeData)=>{
|
||||
if(nodeData.node) {
|
||||
onSaveFunc(nodeData.node, treeNodeInfo);
|
||||
// Removing the node-prop property of panel
|
||||
// so that we show updated data on panel
|
||||
let pnlProperties = pgBrowser.docker.findPanels('properties')[0],
|
||||
pnlSql = pgBrowser.docker.findPanels('sql')[0],
|
||||
pnlStats = pgBrowser.docker.findPanels('statistics')[0],
|
||||
pnlDependencies = pgBrowser.docker.findPanels('dependencies')[0],
|
||||
pnlDependents = pgBrowser.docker.findPanels('dependents')[0];
|
||||
|
||||
if (pnlProperties)
|
||||
$(pnlProperties).removeData('node-prop');
|
||||
if (pnlSql)
|
||||
$(pnlSql).removeData('node-prop');
|
||||
if (pnlStats)
|
||||
$(pnlStats).removeData('node-prop');
|
||||
if (pnlDependencies)
|
||||
$(pnlDependencies).removeData('node-prop');
|
||||
if (pnlDependents)
|
||||
$(pnlDependents).removeData('node-prop');
|
||||
if (pnlProperties)
|
||||
$(pnlProperties).removeData('node-prop');
|
||||
if (pnlSql)
|
||||
$(pnlSql).removeData('node-prop');
|
||||
if (pnlStats)
|
||||
$(pnlStats).removeData('node-prop');
|
||||
if (pnlDependencies)
|
||||
$(pnlDependencies).removeData('node-prop');
|
||||
if (pnlDependents)
|
||||
$(pnlDependents).removeData('node-prop');
|
||||
|
||||
if(nodeData.success === 0) {
|
||||
Notify.alert(gettext('Error'),
|
||||
gettext(nodeData.errormsg)
|
||||
);
|
||||
}
|
||||
if(nodeData.success === 0) {
|
||||
Notify.alert(gettext('Error'),
|
||||
gettext(nodeData.errormsg)
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// We need to release any existing view, before
|
||||
// creating the new view.
|
||||
if (view) {
|
||||
// Release the view
|
||||
view.remove({
|
||||
data: true,
|
||||
internal: true,
|
||||
silent: true,
|
||||
});
|
||||
// Deallocate the view
|
||||
view = null;
|
||||
// Reset the data object
|
||||
j.data('obj-view', null);
|
||||
}
|
||||
// Make sure the HTML element is empty.
|
||||
j.empty();
|
||||
|
||||
that.header = $('<div></div>').addClass(
|
||||
'pg-prop-header'
|
||||
).appendTo(j);
|
||||
that.footer = $('<div></div>').addClass(
|
||||
'pg-prop-footer'
|
||||
).appendTo(j);
|
||||
|
||||
let updateButtons = function(hasError, modified) {
|
||||
|
||||
let btnGroup = this.find('.pg-prop-btn-group'),
|
||||
btnSave = btnGroup.find('button.btn-primary'),
|
||||
btnReset = btnGroup.find('button.btn-secondary[type="reset"]');
|
||||
|
||||
if (hasError || !modified) {
|
||||
btnSave.prop('disabled', true);
|
||||
btnSave.attr('disabled', 'disabled');
|
||||
} else {
|
||||
btnSave.prop('disabled', false);
|
||||
btnSave.removeAttr('disabled');
|
||||
}
|
||||
);
|
||||
return;
|
||||
|
||||
if (!modified) {
|
||||
btnReset.prop('disabled', true);
|
||||
btnReset.attr('disabled', 'disabled');
|
||||
} else {
|
||||
btnReset.prop('disabled', false);
|
||||
btnReset.removeAttr('disabled');
|
||||
}
|
||||
};
|
||||
|
||||
// Create a view to edit/create the properties in fieldsets
|
||||
view = that.getView(item, action, content, data, 'dialog', updateButtons, j, onCancelFunc);
|
||||
if (view) {
|
||||
// Save it to release it later
|
||||
j.data('obj-view', view);
|
||||
|
||||
self.icon(
|
||||
_.isFunction(that['node_image']) ?
|
||||
(that['node_image']).apply(that, [data, view.model]) :
|
||||
(that['node_image'] || ('icon-' + that.type))
|
||||
);
|
||||
|
||||
// Create proper buttons
|
||||
let btn_grp = createButtons([{
|
||||
label: '',
|
||||
type: 'help',
|
||||
tooltip: gettext('SQL help for this object type.'),
|
||||
extraClasses: ['btn-primary-icon', 'pull-left', 'mx-1'],
|
||||
icon: 'fa fa-info',
|
||||
disabled: (that.sqlAlterHelp == '' && that.sqlCreateHelp == '' && !that.epasHelp) ? true : false,
|
||||
register: function(btn) {
|
||||
btn.on('click',() => {
|
||||
onSqlHelp();
|
||||
});
|
||||
},
|
||||
}, {
|
||||
label: '',
|
||||
type: 'help',
|
||||
tooltip: gettext('Help for this dialog.'),
|
||||
extraClasses: ['btn-primary-icon', 'pull-left', 'mx-1'],
|
||||
icon: 'fa fa-question',
|
||||
disabled: (that.dialogHelp == '') ? true : false,
|
||||
register: function(btn) {
|
||||
btn.on('click',() => {
|
||||
onDialogHelp();
|
||||
});
|
||||
},
|
||||
}, {
|
||||
label: gettext('Cancel'),
|
||||
type: 'cancel',
|
||||
tooltip: gettext('Cancel changes to this object.'),
|
||||
extraClasses: ['btn-secondary', 'mx-1'],
|
||||
icon: 'fa fa-times',
|
||||
disabled: false,
|
||||
register: function(btn) {
|
||||
btn.on('click',() => {
|
||||
// Removing the action-mode
|
||||
self.$container.removeAttr('action-mode');
|
||||
onCancelFunc.call(true);
|
||||
});
|
||||
},
|
||||
}, {
|
||||
label: gettext('Reset'),
|
||||
type: 'reset',
|
||||
tooltip: gettext('Reset the fields on this dialog.'),
|
||||
extraClasses: ['btn-secondary', 'mx-1'],
|
||||
icon: 'fa fa-recycle',
|
||||
disabled: true,
|
||||
register: function(btn) {
|
||||
btn.on('click',() => {
|
||||
warnBeforeChangesLost.call(
|
||||
self,
|
||||
gettext('Changes will be lost. Are you sure you want to reset?'),
|
||||
function() {
|
||||
setTimeout(function() {
|
||||
editFunc.call();
|
||||
}, 0);
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
}, {
|
||||
label: gettext('Save'),
|
||||
type: 'save',
|
||||
tooltip: gettext('Save this object.'),
|
||||
extraClasses: ['btn-primary', 'mx-1'],
|
||||
icon: 'fa fa-save',
|
||||
disabled: true,
|
||||
register: function(btn) {
|
||||
// Save the changes
|
||||
btn.on('click',() => {
|
||||
warnBeforeAttributeChange.call(
|
||||
self,
|
||||
function() {
|
||||
informBeforeAttributeChange.call(self, function(){
|
||||
setTimeout(function() {
|
||||
onSave.call(this, view, btn);
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
}], 'footer', 'pg-prop-btn-group-below');
|
||||
|
||||
btn_grp.on('keydown', 'button', function(event) {
|
||||
if (!event.shiftKey && event.keyCode == 9 && $(this).nextAll('button:not([disabled])').length == 0) {
|
||||
// set focus back to first focusable element on dialog
|
||||
view.$el.closest('.wcFloating').find('[tabindex]:not([tabindex="-1"]').first().focus();
|
||||
return false;
|
||||
}
|
||||
let btnGroup = $(self.$container.find('.pg-prop-btn-group'));
|
||||
let el = $(btnGroup).find('button:first');
|
||||
if (self.$container.find('.number-cell.editable:last').is(':visible')){
|
||||
if (event.keyCode === 9 && event.shiftKey) {
|
||||
if ($(el).is($(event.target))){
|
||||
$(self.$container.find('td.editable:last').trigger('click'));
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(function() {
|
||||
pgBrowser.keyboardNavigation.getDialogTabNavigator(self.pgElContainer);
|
||||
}, 200);
|
||||
}
|
||||
|
||||
// Create status bar.
|
||||
createStatusBar('footer');
|
||||
|
||||
// Add some space, so that - button group does not override the
|
||||
// space
|
||||
content.addClass('pg-prop-has-btn-group-below');
|
||||
|
||||
// Show contents before buttons
|
||||
j.prepend(content);
|
||||
// add required attributes to select2 input to resolve accessibility issue.
|
||||
$('.select2-search__field').attr('aria-label', 'select2');
|
||||
view.$el.closest('.wcFloating').find('.wcFrameButtonBar > .wcFrameButton[style!="display: none;"]').on('keydown', function(e) {
|
||||
|
||||
if(e.shiftKey && e.keyCode === 9) {
|
||||
e.stopPropagation();
|
||||
setTimeout(() => {
|
||||
view.$el.closest('.wcFloating').find('[tabindex]:not([tabindex="-1"]):not([disabled])').last().focus();
|
||||
}, 10);
|
||||
}
|
||||
});
|
||||
}.bind(panel),
|
||||
closePanel = function(confirm_close_flag) {
|
||||
if(!_.isUndefined(confirm_close_flag)) {
|
||||
@ -1832,7 +1237,6 @@ define('pgadmin.browser.node', [
|
||||
}]);
|
||||
}, 0);
|
||||
},
|
||||
onCancelFunc = closePanel,
|
||||
onSaveFunc = updateTreeItem.bind(panel, that),
|
||||
onEdit = editFunc.bind(panel);
|
||||
|
||||
|
@ -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;
|
||||
});
|
@ -30,7 +30,7 @@
|
||||
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 {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import Notify from '../../../static/js/helpers/Notifier';
|
||||
|
||||
define('pgadmin.settings', [
|
||||
'jquery', 'sources/pgadmin',
|
||||
'sources/gettext', 'sources/url_for', 'pgadmin.backform',
|
||||
'sources/gettext', 'sources/url_for',
|
||||
], function($, pgAdmin, gettext, url_for) {
|
||||
|
||||
// This defines the Preference/Options Dialog for pgAdmin IV.
|
||||
|
@ -2,7 +2,6 @@
|
||||
@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/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/webcabin-docker/Build/wcDocker.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/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/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
@ -89,8 +89,8 @@ var initBrowserTree = async (pgBrowser) => {
|
||||
await treeModelX.root.ensureLoaded();
|
||||
var _height = undefined;
|
||||
|
||||
document.getElementsByClassName('wcLayoutPane').forEach((item, index) => {
|
||||
if (index > 0 && $(item).find('#tree').length == 1) {
|
||||
[...document.getElementsByClassName('wcLayoutPane')].forEach((item, index) => {
|
||||
if ($(item).find('#tree').length == 1) {
|
||||
_height = item.clientHeight - 30;
|
||||
}
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -20,12 +20,10 @@ $theme-colors: (
|
||||
@import "bootstrap/scss/bootstrap";
|
||||
@import 'webcabin.pgadmin';
|
||||
@import 'bootstrap.overrides';
|
||||
@import 'backgrid.overrides';
|
||||
@import 'tree.overrides';
|
||||
@import 'select2.overrides';
|
||||
@import 'codemirror.overrides';
|
||||
@import 'alert';
|
||||
@import 'backform.overrides';
|
||||
@import 'pgadmin.grid';
|
||||
@import 'pgadmin.style';
|
||||
@import 'bootstrap4-toggle.overrides';
|
||||
|
658
web/pgadmin/static/vendor/backform/backform.js
vendored
658
web/pgadmin/static/vendor/backform/backform.js
vendored
@ -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%>"> </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: ' ',
|
||||
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%>"> </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;
|
||||
|
||||
}));
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
}));
|
236
web/pgadmin/static/vendor/backgrid/backgrid.css
vendored
236
web/pgadmin/static/vendor/backgrid/backgrid.css
vendored
@ -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;
|
||||
}
|
3011
web/pgadmin/static/vendor/backgrid/backgrid.js
vendored
3011
web/pgadmin/static/vendor/backgrid/backgrid.js
vendored
File diff suppressed because it is too large
Load Diff
@ -1947,10 +1947,9 @@ def convert_data_to_dict(conn, result):
|
||||
column['type_code'] = items[1][1]
|
||||
columns.append(column)
|
||||
|
||||
# We need to convert result from 2D array to dict for BackGrid
|
||||
# BackGrid do not support for 2D array result as it it Backbone Model
|
||||
# based grid, This Conversion is not an overhead as most of the time
|
||||
# result will be smaller
|
||||
# We need to convert result from 2D array to dict.
|
||||
# This Conversion is not an overhead as most of the time
|
||||
# result will be smaller.
|
||||
_tmp_result = []
|
||||
for row in result:
|
||||
temp = dict()
|
||||
|
@ -29,8 +29,8 @@ class CheckForXssFeatureTest(BaseFeatureTest):
|
||||
|
||||
We will cover,
|
||||
1) Browser Tree
|
||||
2) Properties Tab (BackFrom)
|
||||
3) Dependents Tab (BackGrid)
|
||||
2) Properties Tab
|
||||
3) Dependents Tab
|
||||
4) SQL Tab (Code Mirror)
|
||||
5) Query Tool (Result Grid)
|
||||
"""
|
||||
@ -55,7 +55,7 @@ class CheckForXssFeatureTest(BaseFeatureTest):
|
||||
['"<script>alert(1)</script>" char',
|
||||
'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(
|
||||
self.server, self.test_db,
|
||||
self.test_table_name,
|
||||
@ -184,7 +184,7 @@ class CheckForXssFeatureTest(BaseFeatureTest):
|
||||
self._check_escaped_characters(
|
||||
source_code,
|
||||
"public.<h1 onmouseover='console.log(2);'>Y",
|
||||
"Dependents tab (BackGrid)"
|
||||
"Dependents tab"
|
||||
)
|
||||
|
||||
def _check_xss_in_query_tool(self):
|
||||
|
@ -118,7 +118,7 @@ class CheckDebuggerForXssFeatureTest(BaseFeatureTest):
|
||||
"[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
|
||||
self.page.click_tab('id-debugger-messages', rc_dock=True)
|
||||
source_code = self.page.find_by_xpath(
|
||||
|
@ -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);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
@ -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);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
@ -43,7 +43,6 @@ const providePlugin = new webpack.ProvidePlugin({
|
||||
'window.jQuery': 'jquery',
|
||||
_: 'underscore',
|
||||
Backbone: 'backbone',
|
||||
Backgrid: 'backgrid',
|
||||
pgAdmin: 'pgadmin',
|
||||
'moment': 'moment',
|
||||
'window.moment':'moment',
|
||||
|
@ -48,26 +48,6 @@ let webpackShimConfig = {
|
||||
'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': {
|
||||
'deps': ['jquery'], 'exports': 'jQuery.fn.drag',
|
||||
},
|
||||
@ -146,18 +126,12 @@ let webpackShimConfig = {
|
||||
//socket
|
||||
'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.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.toggle': path.join(__dirname, './node_modules/bootstrap4-toggle/js/bootstrap4-toggle.min'),
|
||||
'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.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.messages': '/browser/js/messages',
|
||||
'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.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.dashboard': path.join(__dirname, './pgadmin/dashboard/static/js/Dashboard'),
|
||||
'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
|
||||
// into commons JS from app.bundle.js
|
||||
pgLibs: [
|
||||
'pgadmin.browser.error', 'pgadmin.browser.server.privilege',
|
||||
'pgadmin.browser.server.variable', 'pgadmin.browser.collection', 'pgadmin.browser.node.ui',
|
||||
'pgadmin.browser.error',
|
||||
'pgadmin.browser.collection',
|
||||
'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.settings', 'pgadmin.preferences', 'pgadmin.sqlfoldcode',
|
||||
],
|
||||
|
@ -122,9 +122,6 @@ module.exports = {
|
||||
'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'),
|
||||
'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-dom': path.join(__dirname, 'node_modules/react-dom'),
|
||||
'sources': sourcesDir + '/js',
|
||||
@ -137,8 +134,6 @@ module.exports = {
|
||||
'browser': path.resolve(__dirname, 'pgadmin/browser/static/js'),
|
||||
'pgadmin': sourcesDir + '/js/pgadmin',
|
||||
'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'),
|
||||
'pgbrowser': path.resolve(__dirname, 'regression/javascript/fake_browser'),
|
||||
'pgadmin.schema.dir': path.resolve(__dirname, 'pgadmin/browser/server_groups/servers/databases/schemas/static/js'),
|
||||
|
@ -3431,13 +3431,6 @@ babylon@^6.18.0:
|
||||
resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
|
||||
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:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/backbone/-/backbone-1.4.0.tgz#54db4de9df7c3811c3f032f34749a4cd27f3bd12"
|
||||
@ -3445,41 +3438,6 @@ backbone@1.4.0:
|
||||
dependencies:
|
||||
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:
|
||||
version "1.0.2"
|
||||
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"
|
||||
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:
|
||||
version "1.3.0"
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.1.tgz#0c1c6bd2df54b6b69f2314066d65b6cde6fcf9d1"
|
||||
integrity sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==
|
||||
|
Loading…
Reference in New Issue
Block a user