mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
1) Port Properties panel for collection node, Dashboard, and SQL panel in React. Fixes #7132
2) Added transaction start time to Server activity sessions view. Fixes #7215
This commit is contained in:
parent
931a399890
commit
cb052f1988
@ -9,12 +9,14 @@ This release contains a number of bug fixes and new features since the release o
|
||||
New features
|
||||
************
|
||||
|
||||
| `Issue #7215 <https://redmine.postgresql.org/issues/7215>`_ - Added transaction start time to Server activity sessions view.
|
||||
| `Issue #7249 <https://redmine.postgresql.org/issues/7249>`_ - Added support for unique keys in ERD.
|
||||
| `Issue #7257 <https://redmine.postgresql.org/issues/7257>`_ - Support running the container under OpenShift with alternate UIDs.
|
||||
|
||||
Housekeeping
|
||||
************
|
||||
|
||||
| `Issue #7132 <https://redmine.postgresql.org/issues/7132>`_ - Port Properties panel for collection node, Dashboard, and SQL panel in React.
|
||||
| `Issue #7149 <https://redmine.postgresql.org/issues/7149>`_ - Port preferences dialog to React.
|
||||
|
||||
Bug fixes
|
||||
|
@ -83,10 +83,16 @@
|
||||
"@date-io/date-fns": "1.x",
|
||||
"@emotion/sheet": "^1.0.1",
|
||||
"@fortawesome/fontawesome-free": "^5.14.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.1.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.1.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.1.0",
|
||||
"@fortawesome/react-fontawesome": "^0.1.18",
|
||||
"@material-ui/core": "4.11.0",
|
||||
"@material-ui/icons": "^4.11.2",
|
||||
"@material-ui/lab": "4.0.0-alpha.58",
|
||||
"@material-ui/pickers": "^3.2.10",
|
||||
"@mui/icons-material": "^5.4.2",
|
||||
"@mui/material": "^5.4.3",
|
||||
"@projectstorm/react-diagrams": "^6.6.1",
|
||||
"@simonwep/pickr": "^1.5.1",
|
||||
"@szhsin/react-menu": "^2.2.0",
|
||||
@ -122,6 +128,7 @@
|
||||
"date-fns": "^2.24.0",
|
||||
"diff-arrays-of-objects": "^1.1.8",
|
||||
"dropzone": "^5.9.3",
|
||||
"html-loader": "^3.1.0",
|
||||
"html2canvas": "^1.0.0-rc.7",
|
||||
"immutability-helper": "^3.0.0",
|
||||
"imports-loader": "^2.0.0",
|
||||
@ -155,6 +162,8 @@
|
||||
"react-dom": "^17.0.1",
|
||||
"react-draggable": "^4.4.4",
|
||||
"react-rnd": "^10.3.5",
|
||||
"react-fontawesome": "^1.7.1",
|
||||
"react-router-dom": "^6.2.2",
|
||||
"react-select": "^4.2.1",
|
||||
"react-table": "^7.6.3",
|
||||
"react-timer-hook": "^3.0.5",
|
||||
|
@ -68,25 +68,6 @@ define('pgadmin.node.cast', [
|
||||
|
||||
},
|
||||
|
||||
// Define the backform model for cast node
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
|
||||
// Define the schema for cast
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
editable: false, type: 'text', readonly: true, cellHeaderClasses: 'width_percent_50',
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
editable: false, type: 'text', mode: ['properties'],
|
||||
},
|
||||
{
|
||||
id: 'description', label: gettext('Comment'),
|
||||
type: 'multiline', cellHeaderClasses: 'width_percent_50',
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
getSchema: function(treeNodeInfo, itemNodeData){
|
||||
return new CastSchema({
|
||||
getTypeOptions: ()=>getNodeAjaxOptions('get_type', this, treeNodeInfo, itemNodeData),
|
||||
|
@ -12,9 +12,8 @@ import { getNodeListByName, getNodeAjaxOptions } from '../../../../../../static/
|
||||
|
||||
define('pgadmin.node.event_trigger', [
|
||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
|
||||
'sources/pgadmin', 'pgadmin.browser',
|
||||
'pgadmin.backform', 'pgadmin.browser.collection',
|
||||
], function(gettext, url_for, $, _, pgAdmin, pgBrowser, Backform) {
|
||||
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.browser.collection',
|
||||
], function(gettext, url_for, $, _, pgAdmin, pgBrowser) {
|
||||
|
||||
// Extend the browser's collection class for event trigger collection
|
||||
if (!pgBrowser.Nodes['coll-event_trigger']) {
|
||||
@ -80,132 +79,6 @@ define('pgadmin.node.event_trigger', [
|
||||
}
|
||||
);
|
||||
},
|
||||
// Define the model for event trigger node
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
defaults: {
|
||||
oid: undefined,
|
||||
name: undefined,
|
||||
eventowner: undefined,
|
||||
is_sys_obj: undefined,
|
||||
comment: undefined,
|
||||
enabled: 'O',
|
||||
eventfuncoid: undefined,
|
||||
eventfunname: undefined,
|
||||
eventname: 'DDL_COMMAND_START',
|
||||
when: undefined,
|
||||
xmin: undefined,
|
||||
source: undefined,
|
||||
language: undefined,
|
||||
},
|
||||
|
||||
// Default values!
|
||||
initialize: function(attrs, args) {
|
||||
var isNew = (_.size(attrs) === 0);
|
||||
|
||||
if (isNew) {
|
||||
var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
|
||||
|
||||
this.set({'eventowner': userInfo.name}, {silent: true});
|
||||
}
|
||||
pgAdmin.Browser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
|
||||
// Define the schema for the event trigger node
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text',
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
type: 'text', mode: ['properties'],
|
||||
},{
|
||||
id: 'eventowner', label: gettext('Owner'), cell: 'string',
|
||||
type: 'text', mode: ['properties', 'edit','create'], node: 'role',
|
||||
control: Backform.NodeListByNameControl,
|
||||
},{
|
||||
id: 'is_sys_obj', label: gettext('System event trigger?'),
|
||||
cell:'boolean', type: 'switch',
|
||||
mode: ['properties'],
|
||||
},{
|
||||
id: 'comment', label: gettext('Comment'), type: 'multiline',
|
||||
},{
|
||||
id: 'enabled', label: gettext('Trigger enabled?'),
|
||||
group: gettext('Definition'), mode: ['properties', 'edit','create'],
|
||||
options: [
|
||||
{label: gettext('Enable'), value: 'O'},
|
||||
{label: gettext('Disable'), value: 'D'},
|
||||
{label: gettext('Replica'), value: 'R'},
|
||||
{label: gettext('Always'), value: 'A'},
|
||||
],
|
||||
control: 'select2', select2: { allowClear: false, width: '100%' },
|
||||
},{
|
||||
id: 'eventfunname', label: gettext('Trigger function'),
|
||||
type: 'text', control: 'node-ajax-options', group: gettext('Definition'),
|
||||
url:'fopts', cache_node: 'trigger_function',
|
||||
},{
|
||||
id: 'eventname', label: gettext('Event'),
|
||||
group: gettext('Definition'), cell: 'string',
|
||||
options: [
|
||||
{label: gettext('DDL COMMAND START'), value: 'DDL_COMMAND_START'},
|
||||
{label: gettext('DDL COMMAND END'), value: 'DDL_COMMAND_END'},
|
||||
{label: gettext('SQL DROP'), value: 'SQL_DROP'},
|
||||
],
|
||||
control: 'select2', select2: { allowClear: false, width: '100%' },
|
||||
},{
|
||||
id: 'when', label: gettext('When TAG in'), cell: 'string',
|
||||
type: 'text', group: gettext('Definition'),
|
||||
control: Backform.SqlFieldControl,
|
||||
extraClasses:['custom_height_css_class'],
|
||||
},{
|
||||
id: 'seclabels', label: gettext('Security labels'),
|
||||
model: pgBrowser.SecLabelModel, editable: false, type: 'collection',
|
||||
group: gettext('Security'), mode: ['edit', 'create'],
|
||||
min_version: 90200, canAdd: true,
|
||||
canEdit: false, canDelete: true, control: 'unique-col-collection',
|
||||
},
|
||||
],
|
||||
// event trigger model data validation.
|
||||
validate: function() {
|
||||
var msg = undefined;
|
||||
// Clear any existing error msg.
|
||||
this.errorModel.clear();
|
||||
|
||||
if (_.isUndefined(this.get('name'))
|
||||
|| String(this.get('name')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Event trigger name cannot be empty.');
|
||||
this.errorModel.set('name', msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('eventowner'))
|
||||
|| String(this.get('eventowner')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Event trigger owner cannot be empty.');
|
||||
this.errorModel.set('eventowner', msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('enabled'))) {
|
||||
msg = gettext('Event trigger enabled status cannot be empty.');
|
||||
this.errorModel.set('enabled', msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('eventfunname'))
|
||||
|| String(this.get('eventfunname')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Event trigger function cannot be empty.');
|
||||
this.errorModel.set('eventfunname', msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('eventname'))) {
|
||||
msg = gettext('Event trigger event cannot be empty.');
|
||||
this.errorModel.set('eventname', msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -13,9 +13,8 @@ import ExtensionsSchema from './extension.ui';
|
||||
|
||||
define('pgadmin.node.extension', [
|
||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
|
||||
'sources/pgadmin', 'pgadmin.browser',
|
||||
'pgadmin.backform', 'pgadmin.browser.collection',
|
||||
], function(gettext, url_for, $, _, pgAdmin, pgBrowser, Backform) {
|
||||
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.browser.collection',
|
||||
], function(gettext, url_for, $, _, pgAdmin, pgBrowser) {
|
||||
|
||||
/*
|
||||
* Create and Add an Extension Collection into nodes
|
||||
@ -96,109 +95,6 @@ define('pgadmin.node.extension', [
|
||||
* Define model for the Node and specify the properties
|
||||
* of the model in schema.
|
||||
*/
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
schema: [
|
||||
{
|
||||
id: 'name', label: gettext('Name'), first_empty: true,
|
||||
type: 'text', mode: ['properties', 'create', 'edit'],
|
||||
visible: true, url:'avails', readonly: function(m) {
|
||||
return !m.isNew();
|
||||
},
|
||||
transform: function(data, cell) {
|
||||
var res = [],
|
||||
control = cell || this,
|
||||
label = control.model.get('name');
|
||||
|
||||
if (!control.model.isNew()) {
|
||||
res.push({label: label, value: label});
|
||||
}
|
||||
else {
|
||||
if (data && _.isArray(data)) {
|
||||
_.each(data, function(d) {
|
||||
if (d.installed_version === null)
|
||||
|
||||
/*
|
||||
* d contains json data and sets into
|
||||
* select's option control
|
||||
*
|
||||
* We need to stringify data because formatter will
|
||||
* convert Array Object as [Object] string
|
||||
*/
|
||||
res.push({label: d.name, value: JSON.stringify(d)});
|
||||
});
|
||||
}
|
||||
}
|
||||
return res;
|
||||
},
|
||||
|
||||
/*
|
||||
* extends NodeAjaxOptionsControl to override the properties
|
||||
* getValueFromDOM which takes stringified data from option of
|
||||
* select control and parse it. And `onChange` takes the stringified
|
||||
* data from select's option, thus convert it to json format and set the
|
||||
* data into Model which is used to enable/disable the schema field.
|
||||
*/
|
||||
control: Backform.NodeAjaxOptionsControl.extend({
|
||||
getValueFromDOM: function() {
|
||||
var data = this.formatter.toRaw(
|
||||
_.unescape(this.$el.find('select').val()), this.model);
|
||||
/*
|
||||
* return null if data is empty to prevent it from
|
||||
* throwing parsing error. Adds check as name can be empty
|
||||
*/
|
||||
if (data === '') {
|
||||
return null;
|
||||
}
|
||||
else if (typeof(data) === 'string') {
|
||||
data=JSON.parse(data);
|
||||
}
|
||||
return data.name;
|
||||
},
|
||||
|
||||
/*
|
||||
* When name is changed, extract value from its select option and
|
||||
* set attributes values into the model
|
||||
*/
|
||||
onChange: function() {
|
||||
Backform.NodeAjaxOptionsControl.prototype.onChange.apply(
|
||||
this, arguments
|
||||
);
|
||||
var selectedValue = this.$el.find('select').val();
|
||||
if (selectedValue.trim() != '') {
|
||||
var d = this.formatter.toRaw(selectedValue, this.model);
|
||||
if(typeof(d) === 'string')
|
||||
d=JSON.parse(d);
|
||||
this.model.set({
|
||||
'version' : '',
|
||||
'relocatable': (
|
||||
(!_.isNull(d.relocatable[0]) &&
|
||||
!_.isUndefined(d.relocatable[0])) ? d.relocatable[0]: ''
|
||||
),
|
||||
});
|
||||
} else {
|
||||
this.model.set({
|
||||
'version': '', 'relocatable': true, 'schema': '',
|
||||
});
|
||||
}
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
type: 'text', mode: ['properties'],
|
||||
},
|
||||
{
|
||||
id: 'owner', label: gettext('Owner'), control: 'node-list-by-name',
|
||||
mode: ['properties'], node: 'role', cell: 'string',
|
||||
cache_level: 'server',
|
||||
},
|
||||
{
|
||||
id: 'comment', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline', readonly: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
getSchema: (treeNodeInfo, itemNodeData)=>{
|
||||
let nodeObj = pgAdmin.Browser.Nodes['extension'];
|
||||
return new ExtensionsSchema(
|
||||
|
@ -13,9 +13,9 @@ import ForeignServerSchema from './foreign_server.ui';
|
||||
|
||||
define('pgadmin.node.foreign_server', [
|
||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'sources/pgadmin',
|
||||
'pgadmin.browser', 'pgadmin.backform', 'pgadmin.browser.collection',
|
||||
'pgadmin.browser', 'pgadmin.browser.collection',
|
||||
'pgadmin.browser.server.privilege',
|
||||
], function(gettext, url_for, $, _, pgAdmin, pgBrowser, Backform) {
|
||||
], function(gettext, url_for, $, _, pgAdmin, pgBrowser) {
|
||||
|
||||
// Extend the browser's collection class for foreign server collection
|
||||
if (!pgBrowser.Nodes['coll-foreign_server']) {
|
||||
@ -82,45 +82,6 @@ define('pgadmin.node.foreign_server', [
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
// Defining model for foreign server node
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
|
||||
// Default values!
|
||||
initialize: function(attrs, args) {
|
||||
var isNew = (_.size(attrs) === 0);
|
||||
|
||||
if (isNew) {
|
||||
var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
|
||||
|
||||
this.set({'fsrvowner': userInfo.name}, {silent: true});
|
||||
}
|
||||
pgAdmin.Browser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
|
||||
// Defining schema for the foreign server node
|
||||
schema: [
|
||||
{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text', disabled: function() {
|
||||
return (
|
||||
this.mode == 'edit' && this.node_info.server.version < 90200
|
||||
);
|
||||
},
|
||||
}, {
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
type: 'text', mode: ['properties'],
|
||||
}, {
|
||||
id: 'fsrvowner', label: gettext('Owner'), type: 'text',
|
||||
control: Backform.NodeListByNameControl, node: 'role',
|
||||
mode: ['edit', 'create', 'properties'], select2: { allowClear: false },
|
||||
}, {
|
||||
id: 'description', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline',
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -12,10 +12,10 @@ import { getNodePrivilegeRoleSchema } from '../../../../static/js/privilege.ui';
|
||||
import ForeignDataWrapperSchema from './foreign_data_wrapper.ui';
|
||||
|
||||
define('pgadmin.node.foreign_data_wrapper', [
|
||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
|
||||
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform',
|
||||
'sources/gettext', 'sources/url_for', 'jquery',
|
||||
'sources/pgadmin', 'pgadmin.browser',
|
||||
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege',
|
||||
], function(gettext, url_for, $, _, pgAdmin, pgBrowser, Backform) {
|
||||
], function(gettext, url_for, $, pgAdmin, pgBrowser) {
|
||||
|
||||
// Extend the browser's collection class for foreign data wrapper collection
|
||||
if (!pgBrowser.Nodes['coll-foreign_data_wrapper']) {
|
||||
@ -87,44 +87,6 @@ define('pgadmin.node.foreign_data_wrapper', [
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
// Defining model for foreign data wrapper node
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
|
||||
// Default values!
|
||||
initialize: function(attrs, args) {
|
||||
var isNew = (_.size(attrs) === 0);
|
||||
|
||||
if (isNew) {
|
||||
var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
|
||||
|
||||
this.set({'fdwowner': userInfo.name}, {silent: true});
|
||||
}
|
||||
pgBrowser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
|
||||
// Defining schema for the foreign data wrapper node
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text', readonly: function() {
|
||||
// name field will be disabled only if edit mode
|
||||
return (
|
||||
this.mode == 'edit'
|
||||
);
|
||||
},
|
||||
}, {
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
type: 'text', mode: ['properties'],
|
||||
}, {
|
||||
id: 'fdwowner', label: gettext('Owner'), type: 'text',
|
||||
control: Backform.NodeListByNameControl, node: 'role',
|
||||
mode: ['edit', 'create', 'properties'], select2: { allowClear: false },
|
||||
}, {
|
||||
id: 'description', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline',
|
||||
}],
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -10,13 +10,12 @@
|
||||
import { getNodeAjaxOptions, getNodeListByName } from '../../../../../../static/js/node_ajax';
|
||||
import LanguageSchema from './language.ui';
|
||||
import { getNodePrivilegeRoleSchema } from '../../../../static/js/privilege.ui';
|
||||
import _ from 'lodash';
|
||||
|
||||
define('pgadmin.node.language', [
|
||||
'sources/gettext', 'sources/url_for', 'jquery',
|
||||
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform',
|
||||
'sources/pgadmin', 'pgadmin.browser',
|
||||
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege',
|
||||
], function(gettext, url_for, $, pgAdmin, pgBrowser, Backform) {
|
||||
], function(gettext, url_for, $, pgAdmin, pgBrowser) {
|
||||
|
||||
// Extend the browser's collection class for languages collection
|
||||
if (!pgBrowser.Nodes['coll-language']) {
|
||||
@ -71,37 +70,6 @@ define('pgadmin.node.language', [
|
||||
}]);
|
||||
},
|
||||
|
||||
// Define the model for language node
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
initialize: function(attrs, args) {
|
||||
var isNew = (_.size(attrs) === 0);
|
||||
if (isNew) {
|
||||
var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
|
||||
|
||||
this.set({'lanowner': userInfo.name}, {silent: true});
|
||||
}
|
||||
pgBrowser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
|
||||
// Define the schema for the language node
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), type: 'text',
|
||||
mode: ['properties'],
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string', mode: ['properties'],
|
||||
type: 'text',
|
||||
},{
|
||||
id: 'lanowner', label: gettext('Owner'), type: 'text',
|
||||
control: Backform.NodeListByNameControl, node: 'role',
|
||||
mode: ['edit', 'properties', 'create'], select2: { allowClear: false },
|
||||
},
|
||||
{
|
||||
id: 'description', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline',
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
getSchema: function(treeNodeInfo, itemNodeData){
|
||||
return new LanguageSchema(
|
||||
|
@ -12,9 +12,9 @@ import PublicationSchema from './publication.ui';
|
||||
|
||||
define('pgadmin.node.publication', [
|
||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
|
||||
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform',
|
||||
'sources/pgadmin', 'pgadmin.browser',
|
||||
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege',
|
||||
], function(gettext, url_for, $, _, pgAdmin, pgBrowser, Backform) {
|
||||
], function(gettext, url_for, $, _, pgAdmin, pgBrowser) {
|
||||
|
||||
// Extend the browser's collection class for publications collection
|
||||
if (!pgBrowser.Nodes['coll-publication']) {
|
||||
@ -23,7 +23,7 @@ define('pgadmin.node.publication', [
|
||||
node: 'publication',
|
||||
label: gettext('Publications'),
|
||||
type: 'coll-publication',
|
||||
columns: ['name', 'pubowner', 'pubtable', 'all_table'],
|
||||
columns: ['name', 'pubowner', 'proptable', 'all_table'],
|
||||
|
||||
});
|
||||
}
|
||||
@ -70,82 +70,7 @@ define('pgadmin.node.publication', [
|
||||
icon: 'wcTabIcon icon-publication', data: {action: 'create'},
|
||||
}]);
|
||||
},
|
||||
// Define the model for publication node
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
|
||||
// Default values!
|
||||
initialize: function(attrs, args) {
|
||||
var isNew = (_.size(attrs) === 0);
|
||||
if (isNew) {
|
||||
var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
|
||||
|
||||
this.set({'pubowner': userInfo.name}, {silent: true});
|
||||
}
|
||||
pgBrowser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
|
||||
// Define the schema for the publication node
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), type: 'text',
|
||||
mode: ['properties', 'create', 'edit'],
|
||||
visible: function() {
|
||||
if(!_.isUndefined(this.node_info) && !_.isUndefined(this.node_info.server)
|
||||
&& !_.isUndefined(this.node_info.server.version) &&
|
||||
this.node_info.server.version >= 100000) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string', mode: ['properties'],
|
||||
type: 'text',
|
||||
},{
|
||||
id: 'pubowner', label: gettext('Owner'), type: 'text',
|
||||
control: Backform.NodeListByNameControl, node: 'role',
|
||||
disabled: function(m){
|
||||
if(m.isNew())
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
mode: ['edit', 'properties', 'create'], select2: { allowClear: false},
|
||||
},{
|
||||
id: 'all_table', label: gettext('All tables?'), type: 'switch',
|
||||
group: gettext('Definition'), mode: ['edit', 'properties', 'create'], deps: ['name'],
|
||||
readonly: function(m) {return !m.isNew();},
|
||||
},
|
||||
{
|
||||
id: 'pubtable', label: gettext('Tables'), type: 'text', group: gettext('Definition'),
|
||||
mode: ['properties'],
|
||||
},
|
||||
],
|
||||
|
||||
|
||||
/* validate function is used to validate the input given by
|
||||
* the user. In case of error, message will be displayed on
|
||||
* the GUI for the respective control.
|
||||
*/
|
||||
|
||||
sessChanged: function() {
|
||||
if (this.sessAttrs['pubtable'] == '' && this.origSessAttrs['pubtable'] == '')
|
||||
return false;
|
||||
return pgBrowser.DataModel.prototype.sessChanged.apply(this);
|
||||
},
|
||||
|
||||
canCreate: function(itemData, item) {
|
||||
|
||||
var treeData = pgBrowser.tree.getTreeNodeHierarchy(item),
|
||||
server = treeData['server'];
|
||||
|
||||
// If server is less than 10 then do not allow 'create' menu
|
||||
if (server && server.version < 100000)
|
||||
return false;
|
||||
|
||||
// by default we want to allow create menu
|
||||
return true;
|
||||
},
|
||||
|
||||
}),
|
||||
|
||||
getSchema: function(treeNodeInfo, itemNodeData){
|
||||
return new PublicationSchema(
|
||||
|
@ -147,7 +147,7 @@ export default class PublicationSchema extends BaseUISchema {
|
||||
group: gettext('Definition'), mode: ['edit', 'create'],
|
||||
deps: ['all_table'], disabled: obj.isAllTable,
|
||||
},{
|
||||
id: 'pubtable', label: gettext('Tables'), type: 'text', group: gettext('Definition'),
|
||||
id: 'proptable', label: gettext('Tables'), type: 'text', group: gettext('Definition'),
|
||||
mode: ['properties'],
|
||||
},{
|
||||
type: 'nested-fieldset', mode: ['create','edit', 'properties'],
|
||||
|
@ -1,3 +1,6 @@
|
||||
SELECT pg_catalog.quote_ident(pgb_table.schemaname)||'.'||pg_catalog.quote_ident(pgb_table.tablename)
|
||||
AS pubtable FROM pg_catalog.pg_publication_tables pgb_table WHERE pubname = '{{ pname }}'
|
||||
AS pubtable,
|
||||
pg_catalog.quote_ident(pgb_table.schemaname)||'.'||pg_catalog.quote_ident(pgb_table.tablename)
|
||||
AS proptable
|
||||
FROM pg_catalog.pg_publication_tables pgb_table WHERE pubname = '{{ pname }}'
|
||||
AND pgb_table.schemaname NOT LIKE 'pgagent';
|
||||
|
@ -45,37 +45,6 @@ define('pgadmin.node.aggregate', [
|
||||
|
||||
this.initialized = true;
|
||||
},
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
|
||||
// Default values!
|
||||
initialize: function(attrs, args) {
|
||||
var isNew = (_.size(attrs) === 0);
|
||||
|
||||
if (isNew) {
|
||||
var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
|
||||
var schemaInfo = args.node_info.schema;
|
||||
|
||||
this.set({'owner': userInfo.name}, {silent: true});
|
||||
this.set({'schema': schemaInfo._label}, {silent: true});
|
||||
}
|
||||
pgAdmin.Browser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Aggregate'), cell: 'string',
|
||||
type: 'text', mode: ['properties', 'create', 'edit'],
|
||||
},{
|
||||
id: 'owner', label: gettext('Owner'), cell: 'string',
|
||||
type: 'text', mode: ['properties', 'create', 'edit'],
|
||||
control: 'node-list-by-name',
|
||||
node: 'role',
|
||||
},{
|
||||
id: 'description', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline', mode: ['properties', 'create', 'edit'],
|
||||
}
|
||||
],
|
||||
}),
|
||||
getSchema: ()=>{
|
||||
return new AggregateSchema();
|
||||
}
|
||||
|
@ -45,32 +45,6 @@ define('pgadmin.node.catalog_object_column', [
|
||||
getSchema: function() {
|
||||
return new CatalogObjectColumnSchema();
|
||||
},
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
defaults: {
|
||||
attname: undefined,
|
||||
attowner: undefined,
|
||||
atttypid: undefined,
|
||||
attnum: undefined,
|
||||
cltype: undefined,
|
||||
collspcname: undefined,
|
||||
attacl: undefined,
|
||||
is_sys_obj: undefined,
|
||||
description: undefined,
|
||||
},
|
||||
schema: [{
|
||||
id: 'attname', label: gettext('Column'), cell: 'string',
|
||||
type: 'text', readonly: true,
|
||||
},{
|
||||
id: 'attnum', label: gettext('Position'), cell: 'string',
|
||||
type: 'text', readonly: true,
|
||||
},{
|
||||
id: 'cltype', label: gettext('Data type'), cell: 'string',
|
||||
group: gettext('Definition'), type: 'text', readonly: true,
|
||||
},{
|
||||
id: 'description', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline', readonly: true,
|
||||
}],
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -43,31 +43,6 @@ define('pgadmin.node.catalog_object', [
|
||||
|
||||
},
|
||||
getSchema: ()=>new CatalogObjectSchema(),
|
||||
/* Few fields are kept since the properties tab for collection is not
|
||||
yet migrated to new react schema. Once the properties for collection
|
||||
is removed, remove this model */
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
defaults: {
|
||||
name: undefined,
|
||||
namespaceowner: undefined,
|
||||
nspacl: undefined,
|
||||
description: undefined,
|
||||
},
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text', readonly: true,
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
type: 'text',
|
||||
},{
|
||||
id: 'owner', label: gettext('Owner'), cell: 'string',
|
||||
type: 'text', readonly: true,
|
||||
},{
|
||||
id: 'description', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline' , readonly: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
});
|
||||
|
||||
|
@ -69,42 +69,6 @@ define('pgadmin.node.collation', [
|
||||
]);
|
||||
|
||||
},
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
|
||||
// Default values!
|
||||
initialize: function(attrs, args) {
|
||||
var isNew = (_.size(attrs) === 0);
|
||||
|
||||
if (isNew) {
|
||||
var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
|
||||
var schemaInfo = args.node_info.schema;
|
||||
|
||||
this.set({'owner': userInfo.name}, {silent: true});
|
||||
this.set({'schema': schemaInfo._label}, {silent: true});
|
||||
}
|
||||
pgAdmin.Browser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text', mode: ['properties', 'create', 'edit'],
|
||||
disabled: 'inSchema',
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
type: 'text' , mode: ['properties'],
|
||||
},{
|
||||
id: 'owner', label: gettext('Owner'), cell: 'string',
|
||||
type: 'text', mode: ['properties', 'create', 'edit'],
|
||||
disabled: 'inSchema', control: 'node-list-by-name',
|
||||
node: 'role',
|
||||
},{
|
||||
id: 'description', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline', mode: ['properties', 'create', 'edit'],
|
||||
disabled: 'inSchema',
|
||||
}
|
||||
],
|
||||
}),
|
||||
getSchema: (treeNodeInfo, itemNodeData)=>{
|
||||
let nodeObj = pgAdmin.Browser.Nodes['collation'];
|
||||
return new CollationSchema(
|
||||
|
@ -75,43 +75,6 @@ define('pgadmin.node.domain_constraints', [
|
||||
getSchema: function() {
|
||||
return new DomainConstraintSchema();
|
||||
},
|
||||
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
// Domain Constraint Schema
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), type:'text', cell:'string',
|
||||
},{
|
||||
id: 'description', label: gettext('Comment'), type: 'multiline', cell:
|
||||
'string', mode: ['properties', 'create', 'edit'], min_version: 90500,
|
||||
}],
|
||||
// Client Side Validation
|
||||
validate: function() {
|
||||
var err = {},
|
||||
errmsg;
|
||||
|
||||
if (_.isUndefined(this.get('name')) || String(this.get('name')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
err['name'] = gettext('Name cannot be empty.');
|
||||
errmsg = err['name'];
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('consrc')) || String(this.get('consrc')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
err['consrc'] = gettext('Check cannot be empty.');
|
||||
errmsg = errmsg || err['consrc'];
|
||||
|
||||
}
|
||||
|
||||
this.errorModel.clear().set(err);
|
||||
|
||||
if (_.size(err)) {
|
||||
this.trigger('on-status', {msg: errmsg});
|
||||
return errmsg;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -98,38 +98,6 @@ define('pgadmin.node.domain', [
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
// Domain Node Model
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
initialize: function(attrs, args) {
|
||||
var isNew = (_.size(attrs) === 0);
|
||||
if (isNew) {
|
||||
// Set Selected Schema
|
||||
var schema = args.node_info.schema.label;
|
||||
this.set({'basensp': schema}, {silent: true});
|
||||
|
||||
// Set Current User
|
||||
var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
|
||||
this.set({'owner': userInfo.name}, {silent: true});
|
||||
}
|
||||
pgBrowser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
// Domain Schema
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text', mode: ['properties', 'create', 'edit'],
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
type: 'text' , mode: ['properties'],
|
||||
},{
|
||||
id: 'owner', label: gettext('Owner'), cell: 'string', control: Backform.NodeListByNameControl,
|
||||
node: 'role', type: 'text', mode: ['edit', 'create', 'properties'],
|
||||
},{
|
||||
id: 'description', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline',
|
||||
}],
|
||||
}),
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -10,15 +10,16 @@ import { getNodeListByName, getNodeAjaxOptions } from '../../../../../../../stat
|
||||
import { getNodeVariableSchema } from '../../../../../static/js/variable.ui';
|
||||
import { getNodePrivilegeRoleSchema } from '../../../../../static/js/privilege.ui';
|
||||
import ForeignTableSchema from './foreign_table.ui';
|
||||
import _ from 'lodash';
|
||||
|
||||
/* Create and Register Foreign Table Collection and Node. */
|
||||
define('pgadmin.node.foreign_table', [
|
||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone',
|
||||
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform', 'pgadmin.backgrid',
|
||||
'sources/gettext', 'sources/url_for', 'jquery', 'backbone',
|
||||
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backgrid',
|
||||
'pgadmin.node.schema.dir/child', 'pgadmin.node.schema.dir/schema_child_tree_node',
|
||||
'pgadmin.browser.collection',
|
||||
], function(
|
||||
gettext, url_for, $, _, Backbone, pgAdmin, pgBrowser, Backform, Backgrid,
|
||||
gettext, url_for, $, Backbone, pgAdmin, pgBrowser, Backgrid,
|
||||
schemaChild, schemaChildTreeNode
|
||||
) {
|
||||
|
||||
@ -34,465 +35,6 @@ define('pgadmin.node.foreign_table', [
|
||||
});
|
||||
}
|
||||
|
||||
// Options Model
|
||||
var ColumnOptionsModel = pgBrowser.Node.Model.extend({
|
||||
idAttribute: 'option',
|
||||
defaults: {
|
||||
option: undefined,
|
||||
value: undefined,
|
||||
},
|
||||
schema: [
|
||||
{id: 'option', label: gettext('Option'), type:'text', editable: true, cellHeaderClasses: 'width_percent_30'},
|
||||
{
|
||||
id: 'value', label: gettext('Value'), type: 'text', editable: true, cellHeaderClasses: 'width_percent_50',
|
||||
},
|
||||
],
|
||||
validate: function() {
|
||||
if (_.isUndefined(this.get('value')) ||
|
||||
_.isNull(this.get('value')) ||
|
||||
String(this.get('value')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
var msg = 'Please enter a value.';
|
||||
|
||||
this.errorModel.set('value', msg);
|
||||
|
||||
return msg;
|
||||
} else {
|
||||
this.errorModel.unset('value');
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
// Columns Model
|
||||
var ColumnsModel = pgBrowser.Node.Model.extend({
|
||||
idAttribute: 'attnum',
|
||||
defaults: {
|
||||
attname: undefined,
|
||||
datatype: undefined,
|
||||
typlen: undefined,
|
||||
precision: undefined,
|
||||
typdefault: undefined,
|
||||
attnotnull: undefined,
|
||||
collname: undefined,
|
||||
attnum: undefined,
|
||||
inheritedfrom: undefined,
|
||||
inheritedid: undefined,
|
||||
attstattarget: undefined,
|
||||
coloptions: [],
|
||||
},
|
||||
type_options: undefined,
|
||||
schema: [{
|
||||
id: 'attname', label: gettext('Name'), cell: 'string', type: 'text',
|
||||
editable: 'is_editable_column', cellHeaderClasses: 'width_percent_40',
|
||||
},{
|
||||
id: 'datatype', label: gettext('Data type'), cell: 'node-ajax-options',
|
||||
control: 'node-ajax-options', type: 'text', url: 'get_types',
|
||||
editable: 'is_editable_column', cellHeaderClasses: 'width_percent_0',
|
||||
group: gettext('Definition'),
|
||||
transform: function(d, self){
|
||||
self.model.type_options = d;
|
||||
return d;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'typlen', label: gettext('Length'),
|
||||
cell: 'string', group: gettext('Definition'),
|
||||
type: 'int', deps: ['datatype'],
|
||||
disabled: function(m) {
|
||||
var val = m.get('typlen');
|
||||
// We will store type from selected from combobox
|
||||
if(!(_.isUndefined(m.get('inheritedid'))
|
||||
|| _.isNull(m.get('inheritedid'))
|
||||
|| _.isUndefined(m.get('inheritedfrom'))
|
||||
|| _.isNull(m.get('inheritedfrom')))) {
|
||||
|
||||
if (!_.isUndefined(val)) {
|
||||
setTimeout(function() {
|
||||
m.set('typlen', undefined);
|
||||
}, 10);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
var of_type = m.get('datatype'),
|
||||
has_length = false;
|
||||
if(m.type_options) {
|
||||
m.set('is_tlength', false, {silent: true});
|
||||
|
||||
// iterating over all the types
|
||||
_.each(m.type_options, function(o) {
|
||||
// if type from selected from combobox matches in options
|
||||
if ( of_type == o.value ) {
|
||||
// if length is allowed for selected type
|
||||
if(o.length)
|
||||
{
|
||||
// set the values in model
|
||||
has_length = true;
|
||||
m.set('is_tlength', true, {silent: true});
|
||||
m.set('min_val', o.min_val, {silent: true});
|
||||
m.set('max_val', o.max_val, {silent: true});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!has_length && !_.isUndefined(val)) {
|
||||
setTimeout(function() {
|
||||
m.set('typlen', undefined);
|
||||
}, 10);
|
||||
}
|
||||
|
||||
return !(m.get('is_tlength'));
|
||||
}
|
||||
if (!has_length && !_.isUndefined(val)) {
|
||||
setTimeout(function() {
|
||||
m.set('typlen', undefined);
|
||||
}, 10);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
cellHeaderClasses: 'width_percent_10',
|
||||
},
|
||||
{
|
||||
id: 'precision', label: gettext('Precision'),
|
||||
type: 'int', deps: ['datatype'],
|
||||
cell: 'string', group: gettext('Definition'),
|
||||
disabled: function(m) {
|
||||
var val = m.get('precision');
|
||||
if(!(_.isUndefined(m.get('inheritedid'))
|
||||
|| _.isNull(m.get('inheritedid'))
|
||||
|| _.isUndefined(m.get('inheritedfrom'))
|
||||
|| _.isNull(m.get('inheritedfrom')))) {
|
||||
|
||||
if (!_.isUndefined(val)) {
|
||||
setTimeout(function() {
|
||||
m.set('precision', undefined);
|
||||
}, 10);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
var of_type = m.get('datatype'),
|
||||
has_precision = false;
|
||||
|
||||
if(m.type_options) {
|
||||
m.set('is_precision', false, {silent: true});
|
||||
// iterating over all the types
|
||||
_.each(m.type_options, function(o) {
|
||||
// if type from selected from combobox matches in options
|
||||
if ( of_type == o.value ) {
|
||||
// if precession is allowed for selected type
|
||||
if(o.precision)
|
||||
{
|
||||
has_precision = true;
|
||||
// set the values in model
|
||||
m.set('is_precision', true, {silent: true});
|
||||
m.set('min_val', o.min_val, {silent: true});
|
||||
m.set('max_val', o.max_val, {silent: true});
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!has_precision && !_.isUndefined(val)) {
|
||||
setTimeout(function() {
|
||||
m.set('precision', undefined);
|
||||
}, 10);
|
||||
}
|
||||
return !(m.get('is_precision'));
|
||||
}
|
||||
if (!has_precision && !_.isUndefined(val)) {
|
||||
setTimeout(function() {
|
||||
m.set('precision', undefined);
|
||||
}, 10);
|
||||
}
|
||||
return true;
|
||||
}, cellHeaderClasses: 'width_percent_10',
|
||||
},
|
||||
{
|
||||
id: 'typdefault', label: gettext('Default'), type: 'text',
|
||||
cell: 'string', min_version: 90300, group: gettext('Definition'),
|
||||
placeholder: gettext('Enter an expression or a value.'),
|
||||
cellHeaderClasses: 'width_percent_10',
|
||||
editable: function(m) {
|
||||
if(!(_.isUndefined(m.get('inheritedid'))
|
||||
|| _.isNull(m.get('inheritedid'))
|
||||
|| _.isUndefined(m.get('inheritedfrom'))
|
||||
|| _.isNull(m.get('inheritedfrom')))) { return false; }
|
||||
if (this.get('node_info').server.version < 90300){
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'attnotnull', label: gettext('Not NULL?'),
|
||||
cell: 'boolean',type: 'switch', editable: 'is_editable_column',
|
||||
cellHeaderClasses: 'width_percent_10', group: gettext('Definition'),
|
||||
},
|
||||
{
|
||||
id: 'attstattarget', label: gettext('Statistics'), min_version: 90200,
|
||||
cell: 'integer', type: 'int', group: gettext('Definition'),
|
||||
editable: function(m) {
|
||||
if (_.isUndefined(m.isNew) || m.isNew()) { return false; }
|
||||
if (this.get('node_info').server.version < 90200){
|
||||
return false;
|
||||
}
|
||||
return (_.isUndefined(m.get('inheritedid')) || _.isNull(m.get('inheritedid'))
|
||||
|| _.isUndefined(m.get('inheritedfrom')) || _.isNull(m.get('inheritedfrom'))) ? true : false;
|
||||
}, cellHeaderClasses: 'width_percent_10',
|
||||
},
|
||||
{
|
||||
id: 'collname', label: gettext('Collation'), cell: 'node-ajax-options',
|
||||
control: 'node-ajax-options', type: 'text', url: 'get_collations',
|
||||
min_version: 90300, editable: function(m) {
|
||||
if (!(_.isUndefined(m.isNew)) && !m.isNew()) { return false; }
|
||||
return (_.isUndefined(m.get('inheritedid')) || _.isNull(m.get('inheritedid'))
|
||||
|| _.isUndefined(m.get('inheritedfrom')) || _.isNull(m.get('inheritedfrom'))) ? true : false;
|
||||
},
|
||||
cellHeaderClasses: 'width_percent_20', group: gettext('Definition'),
|
||||
},
|
||||
{
|
||||
id: 'attnum', cell: 'string',type: 'text', visible: false,
|
||||
},
|
||||
{
|
||||
id: 'inheritedfrom', label: gettext('Inherited From'), cell: 'string',
|
||||
type: 'text', visible: false, mode: ['properties', 'edit'],
|
||||
cellHeaderClasses: 'width_percent_10',
|
||||
},
|
||||
{
|
||||
id: 'coloptions', label: gettext('Options'), cell: 'string',
|
||||
type: 'collection', group: gettext('Options'), mode: ['edit', 'create'],
|
||||
model: ColumnOptionsModel, canAdd: true, canDelete: true, canEdit: false,
|
||||
control: Backform.UniqueColCollectionControl, uniqueCol : ['option'],
|
||||
min_version: 90200,
|
||||
}],
|
||||
validate: function() {
|
||||
var errmsg = null;
|
||||
|
||||
if (_.isUndefined(this.get('attname')) || String(this.get('attname')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
errmsg = gettext('Column Name cannot be empty.');
|
||||
this.errorModel.set('attname', errmsg);
|
||||
} else {
|
||||
this.errorModel.unset('attname');
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('datatype')) || String(this.get('datatype'))
|
||||
.replace(/^\s+|\s+$/g, '') == '') {
|
||||
errmsg = gettext('Column Datatype cannot be empty.');
|
||||
this.errorModel.set('datatype', errmsg);
|
||||
} else {
|
||||
this.errorModel.unset('datatype');
|
||||
}
|
||||
|
||||
return errmsg;
|
||||
},
|
||||
is_editable_column: function(m) {
|
||||
return (_.isUndefined(m.get('inheritedid')) || _.isNull(m.get('inheritedid'))
|
||||
|| _.isUndefined(m.get('inheritedfrom')) || _.isNull(m.get('inheritedfrom'))) ? true : false;
|
||||
},
|
||||
toJSON: Backbone.Model.prototype.toJSON,
|
||||
});
|
||||
|
||||
|
||||
/* NodeAjaxOptionsMultipleControl is for multiple selection of Combobox.
|
||||
* This control is used to select Multiple Parent Tables to be inherited.
|
||||
* It also populates/vacates Columns on selection/deselection of the option (i.e. table name).
|
||||
* To populates the column, it calls the server and fetch the columns data
|
||||
* for the selected table.
|
||||
*/
|
||||
var NodeAjaxOptionsMultipleControl = Backform.NodeAjaxOptionsControl.extend({
|
||||
onChange: function() {
|
||||
var model = this.model,
|
||||
attrArr = this.field.get('name').split('.'),
|
||||
name = attrArr.shift(),
|
||||
path = attrArr.join('.'),
|
||||
value = this.getValueFromDOM(),
|
||||
changes = {},
|
||||
columns = model.get('columns'),
|
||||
inherits = model.get(name);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
if (typeof(inherits) == 'string'){ inherits = JSON.parse(inherits); }
|
||||
|
||||
// Remove Columns if inherit option is deselected from the combobox
|
||||
if(_.size(value) < _.size(inherits)) {
|
||||
var dif = _.difference(inherits, value);
|
||||
var rmv_columns = columns.where({inheritedid: parseInt(dif[0])});
|
||||
columns.remove(rmv_columns);
|
||||
}
|
||||
else
|
||||
{
|
||||
_.each(value, function(i) {
|
||||
// Fetch Columns from server
|
||||
var fnd_columns = columns.where({inheritedid: parseInt(i)});
|
||||
if (fnd_columns && fnd_columns.length <= 0) {
|
||||
var inhted_columns = self.fetchColumns(i);
|
||||
columns.add(inhted_columns);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
changes[name] = _.isEmpty(path) ? value : _.clone(model.get(name)) || {};
|
||||
this.stopListening(this.model, 'change:' + name, this.render);
|
||||
model.set(changes);
|
||||
this.listenTo(this.model, 'change:' + name, this.render);
|
||||
},
|
||||
fetchColumns: function(table_id){
|
||||
var self = this,
|
||||
url = 'get_columns',
|
||||
m = self.model.top || self.model;
|
||||
|
||||
var node = this.field.get('schema_node'),
|
||||
node_info = this.field.get('node_info'),
|
||||
full_url = node.generate_url.apply(
|
||||
node, [
|
||||
null, url, this.field.get('node_data'),
|
||||
this.field.get('url_with_id') || false, node_info,
|
||||
]),
|
||||
cache_level = this.field.get('cache_level') || node.type,
|
||||
cache_node = this.field.get('cache_node');
|
||||
|
||||
cache_node = (cache_node && pgBrowser.Nodes['cache_node']) || node;
|
||||
|
||||
m.trigger('pgadmin:view:fetching', m, self.field);
|
||||
var data = {attrelid: table_id};
|
||||
|
||||
// Fetching Columns data for the selected table.
|
||||
$.ajax({
|
||||
async: false,
|
||||
url: full_url,
|
||||
data: data,
|
||||
})
|
||||
.done(function(res) {
|
||||
/*
|
||||
* We will cache this data for short period of time for avoiding
|
||||
* same calls.
|
||||
*/
|
||||
data = cache_node.cache(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) || [];
|
||||
return data;
|
||||
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
// Constraints Model
|
||||
var ConstraintModel = pgBrowser.Node.Model.extend({
|
||||
idAttribute: 'conoid',
|
||||
initialize: function(attrs) {
|
||||
var isNew = (_.size(attrs) === 0);
|
||||
if (!isNew) {
|
||||
this.convalidated_default = this.get('convalidated');
|
||||
}
|
||||
pgBrowser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
defaults: {
|
||||
conoid: undefined,
|
||||
conname: undefined,
|
||||
consrc: undefined,
|
||||
connoinherit: undefined,
|
||||
convalidated: true,
|
||||
conislocal: undefined,
|
||||
},
|
||||
convalidated_default: true,
|
||||
schema: [{
|
||||
id: 'conoid', type: 'text', cell: 'string', visible: false,
|
||||
},{
|
||||
id: 'conname', label: gettext('Name'), type: 'text', cell: 'string',
|
||||
editable: 'is_editable', cellHeaderClasses: 'width_percent_30',
|
||||
},{
|
||||
id: 'consrc', label: gettext('Check'), type: 'multiline',
|
||||
editable: 'is_editable', cell: Backgrid.Extension.TextareaCell,
|
||||
cellHeaderClasses: 'width_percent_30',
|
||||
},{
|
||||
id: 'connoinherit', label: gettext('No inherit?'), type: 'switch',
|
||||
cell: 'boolean', editable: 'is_editable',
|
||||
cellHeaderClasses: 'width_percent_20',
|
||||
},{
|
||||
id: 'convalidated', label: gettext('Validate?'), type: 'switch',
|
||||
cell: 'boolean', cellHeaderClasses: 'width_percent_20',
|
||||
editable: function(m) {
|
||||
if (_.isUndefined(m.isNew)) { return true; }
|
||||
if (!m.isNew()) {
|
||||
if(m.get('convalidated') && m.convalidated_default) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
],
|
||||
validate: function() {
|
||||
var err = {},
|
||||
errmsg;
|
||||
|
||||
if (_.isUndefined(this.get('conname')) || String(this.get('conname')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
err['conname'] = gettext('Constraint Name cannot be empty.');
|
||||
errmsg = err['conname'];
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('consrc')) || String(this.get('consrc'))
|
||||
.replace(/^\s+|\s+$/g, '') == '') {
|
||||
err['consrc'] = gettext('Constraint Check cannot be empty.');
|
||||
errmsg = errmsg || err['consrc'];
|
||||
}
|
||||
|
||||
this.errorModel.clear().set(err);
|
||||
|
||||
return errmsg;
|
||||
},
|
||||
is_editable: function(m) {
|
||||
return _.isUndefined(m.isNew) ? true : m.isNew();
|
||||
},
|
||||
toJSON: Backbone.Model.prototype.toJSON,
|
||||
});
|
||||
|
||||
|
||||
// Options Model
|
||||
var OptionsModel = pgBrowser.Node.Model.extend({
|
||||
defaults: {
|
||||
option: undefined,
|
||||
value: undefined,
|
||||
},
|
||||
schema: [{
|
||||
id: 'option', label: gettext('Option'), cell: 'string', type: 'text',
|
||||
editable: true, cellHeaderClasses:'width_percent_50',
|
||||
},{
|
||||
id: 'value', label: gettext('Value'), cell: 'string',type: 'text',
|
||||
editable: true, cellHeaderClasses:'width_percent_50',
|
||||
},
|
||||
],
|
||||
validate: function() {
|
||||
// TODO: Add validation here
|
||||
},
|
||||
toJSON: Backbone.Model.prototype.toJSON,
|
||||
});
|
||||
|
||||
|
||||
if (!pgBrowser.Nodes['foreign_table']) {
|
||||
pgBrowser.Nodes['foreign_table'] = schemaChild.SchemaChildNode.extend({
|
||||
type: 'foreign_table',
|
||||
@ -561,160 +103,6 @@ define('pgadmin.node.foreign_table', [
|
||||
}
|
||||
);
|
||||
},
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
initialize: function(attrs, args) {
|
||||
var isNew = (_.size(attrs) === 0);
|
||||
if (isNew) {
|
||||
var schema = args.node_info.schema._label,
|
||||
userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
|
||||
|
||||
// Set Selected Schema and Current User
|
||||
this.set({
|
||||
'basensp': schema, 'owner': userInfo.name,
|
||||
}, {silent: true});
|
||||
}
|
||||
pgBrowser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
defaults: {
|
||||
name: undefined,
|
||||
oid: undefined,
|
||||
owner: undefined,
|
||||
basensp: undefined,
|
||||
is_sys_obj: undefined,
|
||||
description: undefined,
|
||||
ftsrvname: undefined,
|
||||
strftoptions: undefined,
|
||||
inherits: [],
|
||||
columns: [],
|
||||
constraints: [],
|
||||
ftoptions: [],
|
||||
relacl: [],
|
||||
stracl: [],
|
||||
seclabels: [],
|
||||
},
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text', mode: ['properties', 'create', 'edit'],
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
type: 'text' , mode: ['properties'],
|
||||
},{
|
||||
id: 'owner', label: gettext('Owner'), cell: 'string',
|
||||
control: Backform.NodeListByNameControl,
|
||||
node: 'role', type: 'text', select2: { allowClear: false },
|
||||
},{
|
||||
id: 'basensp', label: gettext('Schema'), cell: 'node-list-by-name',
|
||||
control: 'node-list-by-name', cache_level: 'database', type: 'text',
|
||||
node: 'schema', mode:['create', 'edit'],
|
||||
},{
|
||||
id: 'is_sys_obj', label: gettext('System foreign table?'),
|
||||
cell:'boolean', type: 'switch', mode: ['properties'],
|
||||
},{
|
||||
id: 'description', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline',
|
||||
},{
|
||||
id: 'ftsrvname', label: gettext('Foreign server'), cell: 'string', control: 'node-ajax-options',
|
||||
type: 'text', group: gettext('Definition'), url: 'get_foreign_servers',
|
||||
readonly: function(m) { return !m.isNew(); }, cache_node: 'database',
|
||||
},{
|
||||
id: 'inherits', label: gettext('Inherits'), group: gettext('Definition'),
|
||||
type: 'array', min_version: 90500, control: NodeAjaxOptionsMultipleControl,
|
||||
url: 'get_tables', select2: {multiple: true},
|
||||
'cache_level': 'database',
|
||||
transform: function(d) {
|
||||
if (this.field.get('mode') == 'edit') {
|
||||
var oid = this.model.get('oid');
|
||||
var s = _.findWhere(d, {'id': oid});
|
||||
if (s) {
|
||||
d = _.reject(d, s);
|
||||
}
|
||||
}
|
||||
return d;
|
||||
},
|
||||
},{
|
||||
id: 'columns', label: gettext('Columns'), cell: 'string',
|
||||
type: 'collection', group: gettext('Columns'), mode: ['edit', 'create'],
|
||||
model: ColumnsModel, canAdd: true, canDelete: true, canEdit: true,
|
||||
columns: ['attname', 'datatype', 'inheritedfrom'],
|
||||
canDeleteRow: function(m) {
|
||||
return (_.isUndefined(m.get('inheritedid')) || _.isNull(m.get('inheritedid'))
|
||||
|| _.isUndefined(m.get('inheritedfrom')) || _.isNull(m.get('inheritedfrom'))) ? true : false;
|
||||
},
|
||||
canEditRow: function(m) {
|
||||
return (_.isUndefined(m.get('inheritedid')) || _.isNull(m.get('inheritedid'))
|
||||
|| _.isUndefined(m.get('inheritedfrom')) || _.isNull(m.get('inheritedfrom'))) ? true : false;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'constraints', label: gettext('Constraints'), cell: 'string',
|
||||
type: 'collection', group: gettext('Constraints'), mode: ['edit', 'create'],
|
||||
model: ConstraintModel, canAdd: true, canDelete: true, columns: ['conname','consrc', 'connoinherit', 'convalidated'],
|
||||
canEdit: function(o) {
|
||||
if (o instanceof Backbone.Model) {
|
||||
if (o instanceof ConstraintModel) {
|
||||
return o.isNew();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}, min_version: 90500, canDeleteRow: function(m) {
|
||||
return (m.get('conislocal') == true || _.isUndefined(m.get('conislocal'))) ? true : false;
|
||||
},
|
||||
},{
|
||||
id: 'strftoptions', label: gettext('Options'), cell: 'string',
|
||||
type: 'text', group: gettext('Definition'), mode: ['properties'],
|
||||
},{
|
||||
id: 'ftoptions', label: gettext('Options'), cell: 'string',
|
||||
type: 'collection', group: gettext('Options'), mode: ['edit', 'create'],
|
||||
model: OptionsModel, canAdd: true, canDelete: true, canEdit: false,
|
||||
control: 'unique-col-collection', uniqueCol : ['option'],
|
||||
},{
|
||||
id: 'relacl', label: gettext('Privileges'), cell: 'string',
|
||||
type: 'text', group: gettext('Security'),
|
||||
mode: ['properties'], min_version: 90200,
|
||||
}, pgBrowser.SecurityGroupSchema, {
|
||||
id: 'acl', label: gettext('Privileges'), model: pgAdmin
|
||||
.Browser.Node.PrivilegeRoleModel.extend(
|
||||
{privileges: ['a','r','w','x']}), uniqueCol : ['grantee', 'grantor'],
|
||||
editable: false, type: 'collection', group: 'security',
|
||||
mode: ['edit', 'create'],
|
||||
canAdd: true, canDelete: true, control: 'unique-col-collection',
|
||||
min_version: 90200,
|
||||
},{
|
||||
id: 'seclabels', label: gettext('Security labels'),
|
||||
model: pgBrowser.SecLabelModel, type: 'collection',
|
||||
group: 'security', mode: ['edit', 'create'],
|
||||
min_version: 90100, canAdd: true,
|
||||
canEdit: false, canDelete: true,
|
||||
control: 'unique-col-collection', uniqueCol : ['provider'],
|
||||
},
|
||||
],
|
||||
validate: function()
|
||||
{
|
||||
var err = {},
|
||||
errmsg = null;
|
||||
|
||||
if (_.isUndefined(this.get('name')) || String(this.get('name')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
err['name'] = gettext('Name cannot be empty.');
|
||||
errmsg = err['name'];
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('basensp')) || String(this.get('basensp'))
|
||||
.replace(/^\s+|\s+$/g, '') == '') {
|
||||
err['basensp'] = gettext('Schema cannot be empty.');
|
||||
errmsg = errmsg || err['basensp'];
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('ftsrvname')) || String(this.get('ftsrvname')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
err['ftsrvname'] = gettext('Foreign server cannot be empty.');
|
||||
errmsg = errmsg || err['ftsrvname'];
|
||||
}
|
||||
|
||||
this.errorModel.clear().set(err);
|
||||
|
||||
return errmsg;
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -93,32 +93,6 @@ define('pgadmin.node.fts_configuration', [
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
// Defining model for FTS Configuration node
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
|
||||
initialize: function(attrs, opts) {
|
||||
var isNew = (_.size(attrs) === 0);
|
||||
pgAdmin.Browser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
|
||||
if (isNew) {
|
||||
var user = pgBrowser.serverInfo[opts.node_info.server._id].user;
|
||||
this.set({
|
||||
'owner': user.name,
|
||||
'schema': opts.node_info.schema._id,
|
||||
}, {silent: true});
|
||||
}
|
||||
},
|
||||
// Defining schema for FTS Configuration
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text', cellHeaderClasses: 'width_percent_50',
|
||||
}, {
|
||||
id: 'description', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline', cellHeaderClasses: 'width_percent_50',
|
||||
}],
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -88,31 +88,6 @@ define('pgadmin.node.fts_dictionary', [
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
// Defining backform model for FTS Dictionary node
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
initialize: function(attrs, args) {
|
||||
var isNew = (_.size(attrs) === 0);
|
||||
pgAdmin.Browser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
|
||||
if (isNew) {
|
||||
var user = pgBrowser.serverInfo[args.node_info.server._id].user;
|
||||
this.set({
|
||||
'owner': user.name,
|
||||
'schema': args.node_info.schema._id,
|
||||
}, {silent: true});
|
||||
}
|
||||
},
|
||||
// Defining schema for fts dictionary
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text', cellHeaderClasses: 'width_percent_50',
|
||||
}, {
|
||||
id: 'description', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline', cellHeaderClasses: 'width_percent_50',
|
||||
}],
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -70,108 +70,6 @@ define('pgadmin.node.fts_parser', [
|
||||
|
||||
},
|
||||
|
||||
// Defining backform model for fts parser node
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
defaults: {
|
||||
name: undefined, // Fts parser name
|
||||
is_sys_obj: undefined, // Is system object
|
||||
description: undefined, // Comment on parser
|
||||
},
|
||||
initialize: function(attrs, args) {
|
||||
var isNew = (_.size(attrs) === 0);
|
||||
pgAdmin.Browser.Node.Model.prototype.initialize.apply(
|
||||
this, arguments
|
||||
);
|
||||
if (isNew) {
|
||||
this.set('schema', args.node_info.schema._id);
|
||||
}
|
||||
},
|
||||
// Defining schema for fts parser
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text', cellHeaderClasses: 'width_percent_50',
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
editable: false, type: 'text', mode:['properties'],
|
||||
},{
|
||||
id: 'description', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline', cellHeaderClasses: 'width_percent_50',
|
||||
}],
|
||||
|
||||
/*
|
||||
* Triggers control specific error messages for parser name,
|
||||
* start, token, end, lextype functions and schema, if any one of them is not specified
|
||||
* while creating new fts parser
|
||||
*/
|
||||
validate: function() {
|
||||
var name = this.get('name'),
|
||||
start = this.get('prsstart'),
|
||||
token = this.get('prstoken'),
|
||||
end = this.get('prsend'),
|
||||
lextype = this.get('prslextype'),
|
||||
schema = this.get('schema'),
|
||||
msg;
|
||||
|
||||
// Validate fts parser name
|
||||
if (_.isUndefined(name) ||
|
||||
_.isNull(name) ||
|
||||
String(name).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Name must be specified.');
|
||||
this.errorModel.set('name', msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
// Validate start function control
|
||||
else if (_.isUndefined(start) ||
|
||||
_.isNull(start) ||
|
||||
String(start).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Start function must be selected.');
|
||||
this.errorModel.set('prsstart', msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
// Validate gettoken function control
|
||||
else if (_.isUndefined(token) ||
|
||||
_.isNull(token) ||
|
||||
String(token).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Get next token function must be selected.');
|
||||
this.errorModel.set('prstoken', msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
// Validate end function control
|
||||
else if (_.isUndefined(end) ||
|
||||
_.isNull(end) ||
|
||||
String(end).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('End function must be selected.');
|
||||
this.errorModel.set('prsend', msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
// Validate lextype function control
|
||||
else if (_.isUndefined(lextype) ||
|
||||
_.isNull(lextype) ||
|
||||
String(lextype).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Lextype function must be selected.');
|
||||
this.errorModel.set('prslextype', msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
// Validate schema for fts parser
|
||||
else if (_.isUndefined(schema) ||
|
||||
_.isNull(schema) ||
|
||||
String(schema).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Schema must be selected.');
|
||||
this.errorModel.set('schema', msg);
|
||||
return msg;
|
||||
}
|
||||
else this.errorModel.clear();
|
||||
|
||||
this.trigger('on-status-clear');
|
||||
return null;
|
||||
},
|
||||
}),
|
||||
getSchema: (treeNodeInfo, itemNodeData) => {
|
||||
let nodeObj = pgAdmin.Browser.Nodes['fts_parser'];
|
||||
return new FTSParserSchema(
|
||||
|
@ -70,65 +70,6 @@ define('pgadmin.node.fts_template', [
|
||||
|
||||
},
|
||||
|
||||
// Defining backform model for fts template node
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
initialize: function(attrs, args) {
|
||||
var isNew = (_.size(attrs) === 0);
|
||||
pgAdmin.Browser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
if (isNew) {
|
||||
this.set('schema', args.node_info.schema._id);
|
||||
}
|
||||
},
|
||||
// Defining schema for fts template
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text', cellHeaderClasses: 'width_percent_50',
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
editable: false, type: 'text', mode:['properties'],
|
||||
},{
|
||||
id: 'description', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline', cellHeaderClasses: 'width_percent_50',
|
||||
}],
|
||||
|
||||
/*
|
||||
* Triggers control specific error messages for template name,
|
||||
* lexize function and schema, if any one of them is not specified
|
||||
* while creating new fts template
|
||||
*/
|
||||
validate: function() {
|
||||
var name = this.get('name'),
|
||||
lexize = this.get('tmpllexize'),
|
||||
schema = this.get('schema'),
|
||||
msg;
|
||||
|
||||
// Validate fts template name
|
||||
if (_.isUndefined(name) || _.isNull(name) || String(name).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Name must be specified.');
|
||||
this.errorModel.set('name', msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
// Validate lexize function control
|
||||
else if (_.isUndefined(lexize) || _.isNull(lexize) || String(lexize).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Lexize function must be selected.');
|
||||
this.errorModel.set('tmpllexize', msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
// Validate schema for fts template
|
||||
else if (_.isUndefined(schema) || _.isNull(schema) || String(schema).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Schema must be selected.');
|
||||
this.errorModel.set('schema', msg);
|
||||
return msg;
|
||||
}
|
||||
else this.errorModel.clear();
|
||||
|
||||
this.trigger('on-status-clear');
|
||||
return null;
|
||||
},
|
||||
}),
|
||||
getSchema: (treeNodeInfo, itemNodeData) => {
|
||||
let nodeObj = pgAdmin.Browser.Nodes['fts_template'];
|
||||
return new FTSTemplateSchema(
|
||||
|
@ -11,7 +11,6 @@ import { getNodeAjaxOptions, getNodeListByName, getNodeListById} from '../../../
|
||||
import FunctionSchema from './function.ui';
|
||||
import { getNodePrivilegeRoleSchema } from '../../../../../static/js/privilege.ui';
|
||||
import { getNodeVariableSchema } from '../../../../../static/js/variable.ui';
|
||||
import _ from 'lodash';
|
||||
|
||||
/* Create and Register Function Collection and Node. */
|
||||
define('pgadmin.node.function', [
|
||||
@ -109,44 +108,7 @@ define('pgadmin.node.function', [
|
||||
}
|
||||
);
|
||||
},
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
initialize: function(attrs, args) {
|
||||
var isNew = (_.size(attrs) === 0);
|
||||
if (isNew) {
|
||||
// Set Selected Schema
|
||||
var schema_id = args.node_info.schema._id;
|
||||
this.set({'pronamespace': schema_id}, {silent: true});
|
||||
|
||||
// Set Current User
|
||||
var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
|
||||
this.set({'funcowner': userInfo.name}, {silent: true});
|
||||
}
|
||||
pgBrowser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text', mode: ['properties', 'create', 'edit'],
|
||||
disabled: 'isDisabled',
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
type: 'text' , mode: ['properties'],
|
||||
},{
|
||||
id: 'funcowner', label: gettext('Owner'), cell: 'string',
|
||||
control: Backform.NodeListByNameControl, node: 'role', type:
|
||||
'text', disabled: 'isDisabled',
|
||||
},{
|
||||
id: 'pronamespace', label: gettext('Schema'), cell: 'string',
|
||||
control: 'node-list-by-id', type: 'text', cache_level: 'database',
|
||||
node: 'schema', disabled: 'isDisabled', mode: ['create', 'edit'],
|
||||
},{
|
||||
id: 'description', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline', disabled: 'isDisabled',
|
||||
}],
|
||||
}),
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return pgBrowser.Nodes['function'];
|
||||
});
|
||||
|
@ -11,7 +11,6 @@ import { getNodeAjaxOptions, getNodeListByName, getNodeListById} from '../../../
|
||||
import FunctionSchema from './function.ui';
|
||||
import { getNodePrivilegeRoleSchema } from '../../../../../static/js/privilege.ui';
|
||||
import { getNodeVariableSchema } from '../../../../../static/js/variable.ui';
|
||||
import _ from 'lodash';
|
||||
|
||||
/* Create and Register Procedure Collection and Node. */
|
||||
define('pgadmin.node.procedure', [
|
||||
@ -126,126 +125,6 @@ define('pgadmin.node.procedure', [
|
||||
);
|
||||
},
|
||||
|
||||
model: Function.model.extend({
|
||||
defaults: _.extend({},
|
||||
Function.model.prototype.defaults,
|
||||
{
|
||||
lanname: 'edbspl',
|
||||
}
|
||||
),
|
||||
canVarAdd: function() {
|
||||
var server = this.node_info.server;
|
||||
return server.version >= 90500;
|
||||
},
|
||||
isVisible: function() {
|
||||
if (this.name == 'sysfunc') { return false; }
|
||||
else if (this.name == 'sysproc') { return true; }
|
||||
return false;
|
||||
},
|
||||
isDisabled: function(m) {
|
||||
if(this.node_info && 'catalog' in this.node_info) {
|
||||
return true;
|
||||
}
|
||||
switch(this.name){
|
||||
case 'provolatile':
|
||||
case 'proisstrict':
|
||||
case 'procost':
|
||||
case 'proleakproof':
|
||||
if(this.node_info.server.version < 90500 ||
|
||||
this.node_info.server.server_type != 'ppas' ||
|
||||
m.get('lanname') != 'edbspl') {
|
||||
|
||||
setTimeout(function() {
|
||||
m.set('provolatile', null);
|
||||
m.set('proisstrict', false);
|
||||
m.set('procost', null);
|
||||
m.set('proleakproof', false);
|
||||
}, 10);
|
||||
return true;
|
||||
}
|
||||
else{
|
||||
return false;
|
||||
}
|
||||
case 'variables':
|
||||
case 'prosecdef':
|
||||
return this.node_info.server.version < 90500;
|
||||
case 'prorows':
|
||||
var server = this.node_info.server;
|
||||
return !(server.version >= 90500 && m.get('proretset') == true);
|
||||
case 'proparallel':
|
||||
if (this.node_info.server.version < 90600 ||
|
||||
this.node_info.server.server_type != 'ppas' ||
|
||||
m.get('lanname') != 'edbspl') {
|
||||
setTimeout(function() {
|
||||
m.set('proparallel', null);
|
||||
}, 10);
|
||||
return true;
|
||||
}
|
||||
else{
|
||||
return false;
|
||||
}
|
||||
case 'lanname':
|
||||
return this.node_info.server.version < 110000;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
validate: function()
|
||||
{
|
||||
var err = {},
|
||||
errmsg,
|
||||
seclabels = this.get('seclabels');
|
||||
|
||||
if (_.isUndefined(this.get('name')) || String(this.get('name')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
err['name'] = gettext('Name cannot be empty.');
|
||||
errmsg = err['name'];
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('pronamespace')) || String(this.get('pronamespace')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
err['pronamespace'] = gettext('Schema cannot be empty.');
|
||||
errmsg = errmsg || err['pronamespace'];
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('lanname')) || String(this.get('lanname')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
err['lanname'] = gettext('Language cannot be empty.');
|
||||
errmsg = errmsg || err['lanname'];
|
||||
}
|
||||
|
||||
if (String(this.get('lanname')) == 'c') {
|
||||
if (_.isUndefined(this.get('probin')) || String(this.get('probin'))
|
||||
.replace(/^\s+|\s+$/g, '') == '') {
|
||||
err['probin'] = gettext('Object File cannot be empty.');
|
||||
errmsg = errmsg || err['probin'];
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('prosrc_c')) || String(this.get('prosrc_c')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
err['prosrc_c'] = gettext('Link Symbol cannot be empty.');
|
||||
errmsg = errmsg || err['prosrc_c'];
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (_.isUndefined(this.get('prosrc')) || String(this.get('prosrc')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
err['prosrc'] = gettext('Code cannot be empty.');
|
||||
errmsg = errmsg || err['prosrc'];
|
||||
}
|
||||
}
|
||||
|
||||
if (seclabels) {
|
||||
var secLabelsErr;
|
||||
for (var i = 0; i < seclabels.models.length && !secLabelsErr; i++) {
|
||||
secLabelsErr = (seclabels.models[i]).validate.apply(seclabels.models[i]);
|
||||
if (secLabelsErr) {
|
||||
err['seclabels'] = secLabelsErr;
|
||||
errmsg = errmsg || secLabelsErr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.errorModel.clear().set(err);
|
||||
|
||||
return null;
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -107,151 +107,6 @@ define('pgadmin.node.trigger_function', [
|
||||
}
|
||||
);
|
||||
},
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
initialize: function(attrs, args) {
|
||||
var isNew = (_.size(attrs) === 0);
|
||||
if (isNew) {
|
||||
// Set Selected Schema
|
||||
var schema_id = args.node_info.schema._id;
|
||||
this.set({'pronamespace': schema_id}, {silent: true});
|
||||
|
||||
// Set Current User
|
||||
var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
|
||||
this.set({'funcowner': userInfo.name}, {silent: true});
|
||||
}
|
||||
pgBrowser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
defaults: {
|
||||
name: undefined,
|
||||
oid: undefined,
|
||||
funcowner: undefined,
|
||||
description: undefined,
|
||||
},
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text', mode: ['properties', 'create', 'edit'],
|
||||
disabled: 'isDisabled', readonly: 'isReadonly',
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
type: 'text' , mode: ['properties'],
|
||||
},{
|
||||
id: 'funcowner', label: gettext('Owner'), cell: 'string',
|
||||
control: Backform.NodeListByNameControl, node: 'role', type:
|
||||
'text', disabled: 'isDisabled', readonly: 'isReadonly',
|
||||
},{
|
||||
id: 'description', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline', disabled: 'isDisabled', readonly: 'isReadonly',
|
||||
}],
|
||||
validate: function(keys)
|
||||
{
|
||||
var err = {},
|
||||
errmsg,
|
||||
seclabels = this.get('seclabels');
|
||||
|
||||
// Nothing to validate
|
||||
if(keys && keys.length == 0) {
|
||||
this.errorModel.clear();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('name')) || String(this.get('name')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
err['name'] = gettext('Name cannot be empty.');
|
||||
errmsg = err['name'];
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('funcowner')) || String(this.get('funcowner')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
err['funcowner'] = gettext('Owner cannot be empty.');
|
||||
errmsg = errmsg || err['funcowner'];
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('pronamespace')) || String(this.get('pronamespace')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
err['pronamespace'] = gettext('Schema cannot be empty.');
|
||||
errmsg = errmsg || err['pronamespace'];
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('prorettypename')) || String(this.get('prorettypename')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
err['prorettypename'] = gettext('Return type cannot be empty.');
|
||||
errmsg = errmsg || err['prorettypename'];
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('lanname')) || String(this.get('lanname')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
err['lanname'] = gettext('Language cannot be empty.');
|
||||
errmsg = errmsg || err['lanname'];
|
||||
}
|
||||
|
||||
if (String(this.get('lanname')) == 'c') {
|
||||
if (_.isUndefined(this.get('probin')) || String(this.get('probin'))
|
||||
.replace(/^\s+|\s+$/g, '') == '') {
|
||||
err['probin'] = gettext('Object File cannot be empty.');
|
||||
errmsg = errmsg || err['probin'];
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('prosrc_c')) || String(this.get('prosrc_c')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
err['prosrc_c'] = gettext('Link Symbol cannot be empty.');
|
||||
errmsg = errmsg || err['prosrc_c'];
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (_.isUndefined(this.get('prosrc')) || String(this.get('prosrc')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
err['prosrc'] = gettext('Code cannot be empty.');
|
||||
errmsg = errmsg || err['prosrc'];
|
||||
}
|
||||
}
|
||||
|
||||
if (seclabels) {
|
||||
var secLabelsErr;
|
||||
for (var i = 0; i < seclabels.models.length && !secLabelsErr; i++) {
|
||||
secLabelsErr = (seclabels.models[i]).validate.apply(seclabels.models[i]);
|
||||
if (secLabelsErr) {
|
||||
err['seclabels'] = secLabelsErr;
|
||||
errmsg = errmsg || secLabelsErr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.errorModel.clear().set(err);
|
||||
|
||||
if (_.size(err)) {
|
||||
this.trigger('on-status', {msg: errmsg});
|
||||
return errmsg;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
isVisible: function() {
|
||||
if (this.name == 'sysproc') { return false; }
|
||||
return true;
|
||||
},
|
||||
isReadonly: function(m) {
|
||||
switch(this.name){
|
||||
case 'proargs':
|
||||
case 'proargtypenames':
|
||||
case 'prorettypename':
|
||||
case 'proretset':
|
||||
case 'proiswindow':
|
||||
return !m.isNew();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
isDisabled: function(m) {
|
||||
if(this.node_info && 'catalog' in this.node_info) {
|
||||
return true;
|
||||
}
|
||||
if (this.name === 'prorows'){
|
||||
if(m.get('proretset') == true) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
canVarAdd: function() {
|
||||
return !(this.node_info && 'catalog' in this.node_info);
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -45,37 +45,6 @@ define('pgadmin.node.operator', [
|
||||
|
||||
this.initialized = true;
|
||||
},
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
|
||||
// Default values!
|
||||
initialize: function(attrs, args) {
|
||||
var isNew = (_.size(attrs) === 0);
|
||||
|
||||
if (isNew) {
|
||||
var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
|
||||
var schemaInfo = args.node_info.schema;
|
||||
|
||||
this.set({'owner': userInfo.name}, {silent: true});
|
||||
this.set({'schema': schemaInfo._label}, {silent: true});
|
||||
}
|
||||
pgAdmin.Browser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Operator'), cell: 'string',
|
||||
type: 'text', mode: ['properties', 'create', 'edit'],
|
||||
},{
|
||||
id: 'owner', label: gettext('Owner'), cell: 'string',
|
||||
type: 'text', mode: ['properties', 'create', 'edit'],
|
||||
control: 'node-list-by-name',
|
||||
node: 'role',
|
||||
},{
|
||||
id: 'description', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline', mode: ['properties', 'create', 'edit'],
|
||||
}
|
||||
],
|
||||
}),
|
||||
getSchema: ()=>{
|
||||
return new OperatorSchema();
|
||||
}
|
||||
|
@ -48,16 +48,6 @@ define('pgadmin.node.edbvar', [
|
||||
},
|
||||
canDrop: false,
|
||||
canDropCascade: false,
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text', mode: ['properties'],
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
type: 'text' , mode: ['properties'],
|
||||
}]
|
||||
}),
|
||||
getSchema: () => {
|
||||
return new EDBVarSchema();
|
||||
}
|
||||
|
@ -92,41 +92,6 @@ define('pgadmin.node.package', [
|
||||
// by default we want to allow create menu
|
||||
return true;
|
||||
},
|
||||
// Define the model for package node.
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
initialize: function(attrs, args) {
|
||||
if (_.size(attrs) === 0) {
|
||||
var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
|
||||
var schemaInfo = args.node_info.schema;
|
||||
|
||||
this.set({
|
||||
'owner': userInfo.name, 'schema': schemaInfo._label,
|
||||
}, {silent: true});
|
||||
}
|
||||
pgAdmin.Browser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
// Define the schema for package node.
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text', mode: ['properties', 'create', 'edit'],
|
||||
readonly: function(m) {
|
||||
return !m.isNew();
|
||||
},
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
type: 'text', mode: ['properties'],
|
||||
},{
|
||||
id: 'owner', label: gettext('Owner'), cell: 'string',
|
||||
type: 'text', mode: ['properties', 'create', 'edit'],
|
||||
readonly: true, editable: false, visible: function(m) {
|
||||
return !m.isNew();
|
||||
},
|
||||
},{
|
||||
id: 'description', label: gettext('Comment'), type: 'multiline',
|
||||
mode: ['properties', 'create', 'edit'],
|
||||
}]
|
||||
}),
|
||||
getSchema: (treeNodeInfo, itemNodeData) => {
|
||||
var nodeObj = pgBrowser.Nodes['package'];
|
||||
return new PackageSchema(
|
||||
|
@ -106,41 +106,6 @@ define('pgadmin.node.sequence', [
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
// Define the model for sequence node.
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
|
||||
// Default values!
|
||||
initialize: function(attrs, args) {
|
||||
var isNew = (_.size(attrs) === 0);
|
||||
|
||||
if (isNew) {
|
||||
var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
|
||||
var schemaInfo = args.node_info.schema;
|
||||
|
||||
this.set({'seqowner': userInfo.name}, {silent: true});
|
||||
this.set({'schema': schemaInfo._label}, {silent: true});
|
||||
}
|
||||
pgBrowser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
|
||||
// Define the schema for sequence node.
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text', mode: ['properties', 'create', 'edit'],
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
type: 'text', mode: ['properties'],
|
||||
},{
|
||||
id: 'seqowner', label: gettext('Owner'), cell: 'string',
|
||||
type: 'text', mode: ['properties', 'create', 'edit'], node: 'role',
|
||||
control: Backform.NodeListByNameControl,
|
||||
},{
|
||||
id: 'comment', label: gettext('Comment'), type: 'multiline',
|
||||
mode: ['properties', 'create', 'edit'],
|
||||
}],
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -42,25 +42,6 @@ define('pgadmin.node.catalog', [
|
||||
this.initialized = true;
|
||||
|
||||
},
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
initialize: function(attrs, args) {
|
||||
var isNew = (_.size(attrs) === 0);
|
||||
|
||||
if (isNew) {
|
||||
var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
|
||||
|
||||
this.set({'namespaceowner': userInfo.name}, {silent: true});
|
||||
}
|
||||
pgBrowser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text', readonly: true,
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string', mode: ['properties'],
|
||||
type: 'text',
|
||||
}]
|
||||
}),
|
||||
getSchema: function(treeNodeInfo) {
|
||||
return new CatalogSchema(
|
||||
{
|
||||
|
@ -361,36 +361,6 @@ define('pgadmin.node.schema', [
|
||||
can_create_schema: function(node) {
|
||||
return pgBrowser.Nodes['database'].is_conn_allow.call(this, node);
|
||||
},
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
initialize: function(attrs, args) {
|
||||
var isNew = (_.size(attrs) === 0);
|
||||
|
||||
if (isNew) {
|
||||
var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
|
||||
|
||||
this.set({'namespaceowner': userInfo.name}, {silent: true});
|
||||
}
|
||||
pgBrowser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text',
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
type: 'text', mode: ['properties'],
|
||||
},{
|
||||
id: 'namespaceowner', label: gettext('Owner'), cell: 'string',
|
||||
type: 'text', control: 'node-list-by-name', node: 'role',
|
||||
select2: { allowClear: false },
|
||||
},{
|
||||
id: 'is_sys_obj', label: gettext('System schema?'),
|
||||
cell: 'switch', type: 'switch', mode: ['properties'],
|
||||
},{
|
||||
id: 'description', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline'
|
||||
}]
|
||||
}),
|
||||
getSchema: function(treeNodeInfo, itemNodeData) {
|
||||
var schemaObj = pgBrowser.Nodes['schema'];
|
||||
return new PGSchema(
|
||||
|
@ -100,54 +100,6 @@ define('pgadmin.node.synonym', [
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
isNew: function() {
|
||||
return !this.fetchFromServer;
|
||||
},
|
||||
idAttribute: 'oid',
|
||||
// Default values!
|
||||
initialize: function(attrs, args) {
|
||||
var isNew = (_.size(attrs) === 0);
|
||||
|
||||
if (isNew) {
|
||||
var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
|
||||
var schemaInfo = args.node_info.schema;
|
||||
this.set({
|
||||
'owner': userInfo.name,
|
||||
'synobjschema': schemaInfo._label,
|
||||
'schema': schemaInfo._label,
|
||||
'targettype': 'r',
|
||||
}, {silent: true});
|
||||
} else {
|
||||
this.fetchFromServer = true;
|
||||
}
|
||||
pgAdmin.Browser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
|
||||
},
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text', mode: ['properties', 'create', 'edit'],
|
||||
disabled: 'inSchema', readonly: function(m) { return !m.isNew(); },
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
type: 'text', mode: ['properties'],
|
||||
},{
|
||||
id: 'owner', label: gettext('Owner'), cell: 'string',
|
||||
type: 'text', mode: ['properties', 'create', 'edit'],
|
||||
readonly: true , control: 'node-list-by-name',
|
||||
node: 'role', visible: false,
|
||||
}],
|
||||
|
||||
// We will disable everything if we are under catalog node
|
||||
inSchema: function() {
|
||||
if(this.node_info && 'catalog' in this.node_info)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
}),
|
||||
canCreate: function(itemData, item, data) {
|
||||
//If check is false then , we will allow create menu
|
||||
if (data && data.check == false)
|
||||
|
@ -98,36 +98,6 @@ define('pgadmin.node.column', [
|
||||
getSchema: function(treeNodeInfo, itemNodeData) {
|
||||
return getNodeColumnSchema(treeNodeInfo, itemNodeData, pgBrowser);
|
||||
},
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
idAttribute: 'attnum',
|
||||
|
||||
defaults: {
|
||||
name: undefined,
|
||||
attnum: undefined,
|
||||
description: undefined,
|
||||
},
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text', disabled: 'inSchemaWithColumnCheck',
|
||||
cellHeaderClasses:'width_percent_30',
|
||||
editable: 'editable_check_for_table',
|
||||
},{
|
||||
id: 'attnum', label: gettext('Position'), cell: 'string',
|
||||
type: 'text', disabled: 'notInSchema', mode: ['properties'],
|
||||
},{
|
||||
id: 'description', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline', mode: ['properties', 'create', 'edit'],
|
||||
disabled: 'notInSchema',
|
||||
}],
|
||||
// We will check if we are under schema node & in 'create' mode
|
||||
notInSchema: function() {
|
||||
if(this.node_info && 'catalog' in this.node_info)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
}),
|
||||
// Below function will enable right click menu for creating column
|
||||
canCreate: function(itemData, item, data) {
|
||||
// If check is false then , we will allow create menu
|
||||
|
@ -189,16 +189,6 @@ define('pgadmin.node.compound_trigger', [
|
||||
);
|
||||
},
|
||||
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text',
|
||||
}, {
|
||||
id: 'description', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline', mode: ['properties', 'create', 'edit'],
|
||||
}],
|
||||
}),
|
||||
canCreate: function(itemData, item, data) {
|
||||
//If check is false then , we will allow create menu
|
||||
if (data && data.check == false)
|
||||
|
@ -43,24 +43,6 @@ define('pgadmin.node.constraints', [
|
||||
|
||||
pgBrowser.add_menus([]);
|
||||
},
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
defaults: {
|
||||
name: undefined,
|
||||
oid: undefined,
|
||||
comment: undefined,
|
||||
},
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), type: 'text',
|
||||
mode: ['properties', 'create', 'edit'],
|
||||
},{
|
||||
id: 'oid', label: gettext('Oid'), cell: 'string',
|
||||
type: 'text' , mode: ['properties'],
|
||||
},{
|
||||
id: 'comment', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline', mode: ['properties', 'create', 'edit'],
|
||||
}],
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -92,29 +92,6 @@ define('pgadmin.node.index', [
|
||||
},
|
||||
canDrop: SchemaChildTreeNode.isTreeItemOfChildOfSchema,
|
||||
canDropCascade: SchemaChildTreeNode.isTreeItemOfChildOfSchema,
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
|
||||
defaults: {
|
||||
name: undefined,
|
||||
oid: undefined,
|
||||
nspname: undefined,
|
||||
tabname: undefined,
|
||||
spcname: undefined,
|
||||
amname: 'btree',
|
||||
},
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text', disabled: 'inSchema',
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
type: 'int', readonly: true, mode: ['properties'],
|
||||
}, {
|
||||
id: 'description', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline', mode: ['properties', 'create', 'edit'],
|
||||
disabled: 'inSchema',
|
||||
}],
|
||||
}),
|
||||
// Below function will enable right click menu for creating column
|
||||
canCreate: function(itemData, item, data) {
|
||||
// If check is false then , we will allow create menu
|
||||
|
@ -305,49 +305,6 @@ function(
|
||||
getSchema: function(treeNodeInfo, itemNodeData) {
|
||||
return getNodePartitionTableSchema(treeNodeInfo, itemNodeData, pgBrowser);
|
||||
},
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
defaults: {
|
||||
name: undefined,
|
||||
oid: undefined,
|
||||
description: undefined,
|
||||
is_partitioned: false,
|
||||
partition_value: undefined,
|
||||
},
|
||||
// Default values!
|
||||
initialize: function(attrs, args) {
|
||||
if (_.size(attrs) === 0) {
|
||||
var userInfo = pgBrowser.serverInfo[
|
||||
args.node_info.server._id
|
||||
].user,
|
||||
schemaInfo = args.node_info.schema;
|
||||
|
||||
this.set({
|
||||
'relowner': userInfo.name, 'schema': schemaInfo._label,
|
||||
}, {silent: true});
|
||||
}
|
||||
pgBrowser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
|
||||
},
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), type: 'text',
|
||||
mode: ['properties', 'create', 'edit'],
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), type: 'text', mode: ['properties'],
|
||||
},{
|
||||
id: 'schema', label: gettext('Schema'), type: 'text', node: 'schema',
|
||||
mode: ['create', 'edit', 'properties'],
|
||||
},{
|
||||
id: 'is_partitioned', label:gettext('Partitioned table?'), cell: 'switch',
|
||||
type: 'switch', mode: ['properties', 'create', 'edit'],
|
||||
},{
|
||||
id: 'partition_value', label:gettext('Partition Scheme'),
|
||||
type: 'text', visible: false,
|
||||
},{
|
||||
id: 'description', label: gettext('Comment'), type: 'multiline',
|
||||
mode: ['properties', 'create', 'edit'],
|
||||
}],
|
||||
}),
|
||||
canCreate: SchemaChildTreeNode.isTreeItemOfChildOfSchema,
|
||||
// Check to whether table has disable trigger(s)
|
||||
canCreate_with_trigger_enable: function(itemData, item, data) {
|
||||
|
@ -88,64 +88,6 @@ define('pgadmin.node.row_security_policy', [
|
||||
}
|
||||
);
|
||||
},
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
defaults: {
|
||||
name: undefined,
|
||||
},
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text', readonly: true, cellHeaderClasses: 'width_percent_50',
|
||||
mode: ['properties']
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
editable: false, type: 'text', mode: ['properties'],
|
||||
}],
|
||||
validate: function(keys) {
|
||||
var msg;
|
||||
this.errorModel.clear();
|
||||
// If nothing to validate
|
||||
if (keys && keys.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if(_.isUndefined(this.get('name'))
|
||||
|| String(this.get('name')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Name cannot be empty.');
|
||||
this.errorModel.set('name', msg);
|
||||
return msg;
|
||||
}
|
||||
if (!this.isNew() && !_.isNull(this.get('using_orig')) && this.get('using_orig') != '' && String(this.get('using')).replace(/^\s+|\s+$/g, '') == ''){
|
||||
msg = gettext('"USING" can not be empty once the value is set');
|
||||
this.errorModel.set('using', msg);
|
||||
return msg;
|
||||
}
|
||||
if (!this.isNew() && !_.isNull(this.get('withcheck_orig')) && this.get('withcheck_orig') != '' && String(this.get('withcheck')).replace(/^\s+|\s+$/g, '') == ''){
|
||||
msg = gettext('"Withcheck" can not be empty once the value is set');
|
||||
this.errorModel.set('withcheck', msg);
|
||||
return msg;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
disableWithCheck: function(m){
|
||||
var event = m.get('event');
|
||||
if ((event == 'SELECT') || (event == 'DELETE')){
|
||||
m.set('withcheck', '');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
disableUsing: function(m){
|
||||
var event = m.get('event');
|
||||
|
||||
if (event == 'INSERT'){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
}),
|
||||
canCreate: function(itemData, item) {
|
||||
|
||||
var treeData = pgBrowser.tree.getTreeNodeHierarchy(item),
|
||||
|
@ -207,54 +207,6 @@ define('pgadmin.node.rule', [
|
||||
}
|
||||
);
|
||||
},
|
||||
/**
|
||||
Define model for the rule node and specify the node
|
||||
properties of the model in schema.
|
||||
*/
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'),
|
||||
type: 'text', disabled: function(m) {
|
||||
// disable name field it it is system rule
|
||||
if (m && m.get('name') == '_RETURN') {
|
||||
return true;
|
||||
}
|
||||
if (m.isNew && m.isNew() || m.node_info && m.node_info.server.version >= 90400) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'oid', label: gettext('OID'),
|
||||
type: 'text', mode: ['properties'],
|
||||
},
|
||||
{
|
||||
id: 'comment', label: gettext('Comment'), cell: 'string', type: 'multiline',
|
||||
},
|
||||
],
|
||||
validate: function() {
|
||||
|
||||
// Triggers specific error messages for fields
|
||||
var err = {},
|
||||
errmsg,
|
||||
field_name = this.get('name');
|
||||
if (_.isUndefined(field_name) || _.isNull(field_name) ||
|
||||
String(field_name).replace(/^\s+|\s+$/g, '') === '')
|
||||
{
|
||||
err['name'] = gettext('Please specify name.');
|
||||
errmsg = err['name'];
|
||||
this.errorModel.set('name', errmsg);
|
||||
return errmsg;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.errorModel.unset('name');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
}),
|
||||
|
||||
// Show or hide create rule menu option on parent node
|
||||
canCreate: function(itemData, item, data) {
|
||||
|
@ -187,468 +187,6 @@ define('pgadmin.node.trigger', [
|
||||
},
|
||||
);
|
||||
},
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
defaults: {
|
||||
name: undefined,
|
||||
is_row_trigger: true,
|
||||
fires: 'BEFORE',
|
||||
},
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text', disabled: 'inSchema',
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
type: 'int', mode: ['properties'],
|
||||
},{
|
||||
id: 'is_enable_trigger', label: gettext('Trigger enabled?'),
|
||||
mode: ['edit', 'properties'], group: gettext('Definition'),
|
||||
disabled: function() {
|
||||
if(this.node_info && ('catalog' in this.node_info || 'view' in this.node_info)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
options: [
|
||||
{label: gettext('Enable'), value: 'O'},
|
||||
{label: gettext('Enable Replica'), value: 'R'},
|
||||
{label: gettext('Enable Always'), value: 'A'},
|
||||
{label: gettext('Disable'), value: 'D'},
|
||||
],
|
||||
control: 'select2', select2: { allowClear: false, width: '100%' },
|
||||
},{
|
||||
id: 'is_row_trigger', label: gettext('Row trigger?'),
|
||||
type: 'switch', group: gettext('Definition'),
|
||||
mode: ['create','edit', 'properties'],
|
||||
deps: ['is_constraint_trigger'],
|
||||
disabled: function(m) {
|
||||
// Disabled if table is a partitioned table.
|
||||
if (!m.isNew())
|
||||
return true;
|
||||
|
||||
if (_.has(m, 'node_info') && _.has(m.node_info, 'table') &&
|
||||
_.has(m.node_info.table, 'is_partitioned') &&
|
||||
m.node_info.table.is_partitioned && m.node_info.server.version < 110000
|
||||
)
|
||||
{
|
||||
setTimeout(function(){
|
||||
m.set('is_row_trigger', false);
|
||||
},10);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// If constraint trigger is set to True then row trigger will
|
||||
// automatically set to True and becomes disable
|
||||
var is_constraint_trigger = m.get('is_constraint_trigger');
|
||||
if(!m.inSchemaWithModelCheck.apply(this, [m])) {
|
||||
if(!_.isUndefined(is_constraint_trigger) &&
|
||||
is_constraint_trigger === true) {
|
||||
// change it's model value
|
||||
setTimeout(function() { m.set('is_row_trigger', true); }, 10);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Check if it is row trigger then enabled it.
|
||||
var is_row_trigger = m.get('is_row_trigger');
|
||||
if (!_.isUndefined(is_row_trigger) && m.node_info['server']['server_type'] == 'ppas') {
|
||||
return false;
|
||||
}
|
||||
// Disable it
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},{
|
||||
id: 'is_constraint_trigger', label: gettext('Constraint trigger?'),
|
||||
type: 'switch',
|
||||
mode: ['create','edit', 'properties'],
|
||||
group: gettext('Definition'),
|
||||
deps: ['tfunction'],
|
||||
disabled: function(m) {
|
||||
// Disabled if table is a partitioned table.
|
||||
var tfunction = m.get('tfunction');
|
||||
if ((_.has(m, 'node_info') && _.has(m.node_info, 'table') &&
|
||||
_.has(m.node_info.table, 'is_partitioned') &&
|
||||
m.node_info.table.is_partitioned) ||
|
||||
_.indexOf(Object.keys(m.node_info), 'view') != -1 ||
|
||||
(m.node_info.server.server_type === 'ppas' &&
|
||||
!_.isUndefined(tfunction) &&
|
||||
tfunction === 'Inline EDB-SPL')) {
|
||||
setTimeout(function(){
|
||||
m.set('is_constraint_trigger', false);
|
||||
},10);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return m.inSchemaWithModelCheck.apply(this, [m]);
|
||||
},
|
||||
},{
|
||||
id: 'tgdeferrable', label: gettext('Deferrable?'),
|
||||
type: 'switch', group: gettext('Definition'),
|
||||
mode: ['create','edit', 'properties'],
|
||||
deps: ['is_constraint_trigger'],
|
||||
disabled: function(m) {
|
||||
// If constraint trigger is set to True then only enable it
|
||||
var is_constraint_trigger = m.get('is_constraint_trigger');
|
||||
if(!m.inSchemaWithModelCheck.apply(this, [m])) {
|
||||
if(!_.isUndefined(is_constraint_trigger) &&
|
||||
is_constraint_trigger === true) {
|
||||
return false;
|
||||
} else {
|
||||
// If value is already set then reset it to false
|
||||
if(m.get('tgdeferrable')) {
|
||||
setTimeout(function() { m.set('tgdeferrable', false); }, 10);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Disable it
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},{
|
||||
id: 'tginitdeferred', label: gettext('Deferred?'),
|
||||
type: 'switch', group: gettext('Definition'),
|
||||
mode: ['create','edit', 'properties'],
|
||||
deps: ['tgdeferrable', 'is_constraint_trigger'],
|
||||
disabled: function(m) {
|
||||
// If Deferrable is set to True then only enable it
|
||||
var tgdeferrable = m.get('tgdeferrable');
|
||||
if(!m.inSchemaWithModelCheck.apply(this, [m])) {
|
||||
if(!_.isUndefined(tgdeferrable) &&
|
||||
tgdeferrable) {
|
||||
return false;
|
||||
} else {
|
||||
// If value is already set then reset it to false
|
||||
if(m.get('tginitdeferred')) {
|
||||
setTimeout(function() { m.set('tginitdeferred', false); }, 10);
|
||||
}
|
||||
// If constraint trigger is set then do not disable
|
||||
return m.get('is_constraint_trigger') ? false : true;
|
||||
}
|
||||
} else {
|
||||
// Disable it
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},{
|
||||
id: 'tfunction', label: gettext('Trigger function'),
|
||||
type: 'text', disabled: 'inSchemaWithModelCheck',
|
||||
mode: ['create','edit', 'properties'], group: gettext('Definition'),
|
||||
control: 'node-ajax-options', url: 'get_triggerfunctions', url_jump_after_node: 'schema',
|
||||
cache_node: 'trigger_function',
|
||||
},{
|
||||
id: 'tgargs', label: gettext('Arguments'), cell: 'string',
|
||||
group: gettext('Definition'),
|
||||
type: 'text',mode: ['create','edit', 'properties'], deps: ['tfunction'],
|
||||
disabled: function(m) {
|
||||
// We will disable it when EDB PPAS and trigger function is
|
||||
// set to Inline EDB-SPL
|
||||
var tfunction = m.get('tfunction'),
|
||||
server_type = m.node_info['server']['server_type'];
|
||||
if(!m.inSchemaWithModelCheck.apply(this, [m])) {
|
||||
if(server_type === 'ppas' &&
|
||||
!_.isUndefined(tfunction) &&
|
||||
tfunction === 'Inline EDB-SPL') {
|
||||
// Disable and clear its value
|
||||
m.set('tgargs', undefined);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Disable it
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},{
|
||||
id: 'fires', label: gettext('Fires'), deps: ['is_constraint_trigger'],
|
||||
mode: ['create','edit', 'properties'], group: gettext('Events'),
|
||||
options: function(control) {
|
||||
var table_options = [
|
||||
{label: 'BEFORE', value: 'BEFORE'},
|
||||
{label: 'AFTER', value: 'AFTER'}],
|
||||
view_options = [
|
||||
{label: 'BEFORE', value: 'BEFORE'},
|
||||
{label: 'AFTER', value: 'AFTER'},
|
||||
{label: 'INSTEAD OF', value: 'INSTEAD OF'}];
|
||||
// If we are under table then show table specific options
|
||||
if(_.indexOf(Object.keys(control.model.node_info), 'table') != -1) {
|
||||
return table_options;
|
||||
} else {
|
||||
return view_options;
|
||||
}
|
||||
},
|
||||
control: 'select2', select2: { allowClear: false, width: '100%' },
|
||||
disabled: function(m) {
|
||||
if (!m.isNew())
|
||||
return true;
|
||||
// If contraint trigger is set to True then only enable it
|
||||
var is_constraint_trigger = m.get('is_constraint_trigger');
|
||||
if(!m.inSchemaWithModelCheck.apply(this, [m])) {
|
||||
if(!_.isUndefined(is_constraint_trigger) &&
|
||||
is_constraint_trigger === true) {
|
||||
setTimeout(function() { m.set('fires', 'AFTER'); }, 10);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Check if it is row trigger then enabled it.
|
||||
var fires_ = m.get('fires');
|
||||
if (!_.isUndefined(fires_) && m.node_info['server']['server_type'] == 'ppas') {
|
||||
return false;
|
||||
}
|
||||
// Disable it
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},{
|
||||
type: 'nested', control: 'fieldset', mode: ['create','edit', 'properties'],
|
||||
label: gettext('Events'), group: gettext('Events'), contentClass: 'row',
|
||||
schema:[{
|
||||
id: 'evnt_insert', label: gettext('INSERT'),
|
||||
type: 'switch', mode: ['create','edit', 'properties'],
|
||||
group: gettext('Events'),
|
||||
extraToggleClasses: 'pg-el-sm-6',
|
||||
controlLabelClassName: 'control-label pg-el-sm-5 pg-el-12',
|
||||
controlsClassName: 'pgadmin-controls pg-el-sm-7 pg-el-12',
|
||||
disabled: function(m) {
|
||||
var evn_insert = m.get('evnt_insert');
|
||||
if (!_.isUndefined(evn_insert) && m.node_info['server']['server_type'] == 'ppas' && m.isNew())
|
||||
return false;
|
||||
return m.inSchemaWithModelCheck.apply(this, [m]);
|
||||
},
|
||||
},{
|
||||
id: 'evnt_update', label: gettext('UPDATE'),
|
||||
type: 'switch', mode: ['create','edit', 'properties'],
|
||||
group: gettext('Events'),
|
||||
extraToggleClasses: 'pg-el-sm-6',
|
||||
controlLabelClassName: 'control-label pg-el-sm-5 pg-el-12',
|
||||
controlsClassName: 'pgadmin-controls pg-el-sm-7 pg-el-12',
|
||||
disabled: function(m) {
|
||||
var evn_update = m.get('evnt_update');
|
||||
if (!_.isUndefined(evn_update) && m.node_info['server']['server_type'] == 'ppas' && m.isNew())
|
||||
return false;
|
||||
return m.inSchemaWithModelCheck.apply(this, [m]);
|
||||
},
|
||||
},{
|
||||
id: 'evnt_delete', label: gettext('DELETE'),
|
||||
type: 'switch', mode: ['create','edit', 'properties'],
|
||||
group: gettext('Events'),
|
||||
extraToggleClasses: 'pg-el-sm-6',
|
||||
controlLabelClassName: 'control-label pg-el-sm-5 pg-el-12',
|
||||
controlsClassName: 'pgadmin-controls pg-el-sm-7 pg-el-12',
|
||||
disabled: function(m) {
|
||||
var evn_delete = m.get('evnt_delete');
|
||||
if (!_.isUndefined(evn_delete) && m.node_info['server']['server_type'] == 'ppas' && m.isNew())
|
||||
return false;
|
||||
return m.inSchemaWithModelCheck.apply(this, [m]);
|
||||
},
|
||||
},{
|
||||
id: 'evnt_truncate', label: gettext('TRUNCATE'),
|
||||
type: 'switch', group: gettext('Events'),deps: ['is_row_trigger', 'is_constraint_trigger'],
|
||||
extraToggleClasses: 'pg-el-sm-6',
|
||||
controlLabelClassName: 'control-label pg-el-sm-5 pg-el-12',
|
||||
controlsClassName: 'pgadmin-controls pg-el-sm-7 pg-el-12',
|
||||
disabled: function(m) {
|
||||
var is_constraint_trigger = m.get('is_constraint_trigger'),
|
||||
is_row_trigger = m.get('is_row_trigger'),
|
||||
server_type = m.node_info['server']['server_type'];
|
||||
if (is_row_trigger == true){
|
||||
setTimeout(function(){
|
||||
m.set('evnt_truncate', false);
|
||||
},10);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (server_type === 'ppas' &&
|
||||
!_.isUndefined(is_constraint_trigger) &&
|
||||
!_.isUndefined(is_row_trigger) &&
|
||||
is_constraint_trigger === false && m.isNew())
|
||||
return false;
|
||||
return m.inSchemaWithModelCheck.apply(this, [m]);
|
||||
},
|
||||
}],
|
||||
},{
|
||||
id: 'whenclause', label: gettext('When'),
|
||||
type: 'text', disabled: 'inSchemaWithModelCheck',
|
||||
mode: ['create', 'edit', 'properties'],
|
||||
control: 'sql-field', visible: true, group: gettext('Events'),
|
||||
},{
|
||||
id: 'columns', label: gettext('Columns'), url: 'nodes',
|
||||
control: 'node-list-by-name', cache_node: 'column', type: 'array',
|
||||
select2: {'multiple': true},
|
||||
deps: ['evnt_update'], node: 'column', group: gettext('Events'),
|
||||
disabled: function(m) {
|
||||
if(this.node_info && 'catalog' in this.node_info) {
|
||||
return true;
|
||||
}
|
||||
//Disable in edit mode
|
||||
if (!m.isNew()) {
|
||||
return true;
|
||||
}
|
||||
// Enable column only if update event is set true
|
||||
var isUpdate = m.get('evnt_update');
|
||||
if(!_.isUndefined(isUpdate) && isUpdate) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},{
|
||||
id: 'tgoldtable', label: gettext('Old table'),
|
||||
type: 'text', group: gettext('Transition'),
|
||||
cell: 'string', mode: ['create', 'edit', 'properties'],
|
||||
deps: ['fires', 'is_constraint_trigger', 'evnt_insert', 'evnt_update', 'evnt_delete', 'columns'],
|
||||
disabled: 'disableTransition',
|
||||
},{
|
||||
id: 'tgnewtable', label: gettext('New table'),
|
||||
type: 'text', group: gettext('Transition'),
|
||||
cell: 'string', mode: ['create', 'edit', 'properties'],
|
||||
deps: ['fires', 'is_constraint_trigger', 'evnt_insert', 'evnt_update', 'evnt_delete', 'columns'],
|
||||
disabled: 'disableTransition',
|
||||
},{
|
||||
id: 'prosrc', label: gettext('Code'), group: gettext('Code'),
|
||||
type: 'text', mode: ['create', 'edit'], deps: ['tfunction'],
|
||||
tabPanelCodeClass: 'sql-code-control',
|
||||
control: Backform.SqlCodeControl,
|
||||
visible: true,
|
||||
disabled: function(m) {
|
||||
// We will enable it only when EDB PPAS and trigger function is
|
||||
// set to Inline EDB-SPL
|
||||
var tfunction = m.get('tfunction'),
|
||||
server_type = m.node_info['server']['server_type'];
|
||||
|
||||
return (server_type !== 'ppas' ||
|
||||
_.isUndefined(tfunction) ||
|
||||
tfunction !== 'Inline EDB-SPL');
|
||||
},
|
||||
},{
|
||||
id: 'is_sys_trigger', label: gettext('System trigger?'), cell: 'string',
|
||||
type: 'switch', disabled: 'inSchemaWithModelCheck', mode: ['properties'],
|
||||
},{
|
||||
id: 'description', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline', mode: ['properties', 'create', 'edit'],
|
||||
disabled: 'inSchema',
|
||||
}],
|
||||
validate: function(keys) {
|
||||
var msg;
|
||||
this.errorModel.clear();
|
||||
|
||||
// If nothing to validate
|
||||
if (keys && keys.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if(_.isUndefined(this.get('name'))
|
||||
|| String(this.get('name')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Name cannot be empty.');
|
||||
this.errorModel.set('name', msg);
|
||||
return msg;
|
||||
}
|
||||
if(_.isUndefined(this.get('tfunction'))
|
||||
|| String(this.get('tfunction')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Trigger function cannot be empty.');
|
||||
this.errorModel.set('tfunction', msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
if(!this.get('evnt_truncate') && !this.get('evnt_delete') &&
|
||||
!this.get('evnt_update') && !this.get('evnt_insert')) {
|
||||
msg = gettext('Specify at least one event.');
|
||||
this.errorModel.set('evnt_truncate', ' ');
|
||||
this.errorModel.set('evnt_delete', ' ');
|
||||
this.errorModel.set('evnt_update', ' ');
|
||||
this.errorModel.set('evnt_insert', msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
if(!_.isUndefined(this.get('tfunction')) &&
|
||||
this.get('tfunction') === 'Inline EDB-SPL' &&
|
||||
(_.isUndefined(this.get('prosrc'))
|
||||
|| String(this.get('prosrc')).replace(/^\s+|\s+$/g, '') == ''))
|
||||
{
|
||||
msg = gettext('Trigger code cannot be empty.');
|
||||
this.errorModel.set('prosrc', msg);
|
||||
return msg;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
// We will check if we are under schema node & in 'create' mode
|
||||
inSchema: function() {
|
||||
if(this.node_info && 'catalog' in this.node_info) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
// We will check if we are under schema node & in 'create' mode
|
||||
inSchemaWithModelCheck: function(m) {
|
||||
if(this.node_info && 'schema' in this.node_info) {
|
||||
// We will disable control if it's in 'edit' mode
|
||||
return !m.isNew();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
// Checks weather to enable/disable control
|
||||
inSchemaWithColumnCheck: function(m) {
|
||||
if(this.node_info && 'schema' in this.node_info) {
|
||||
// We will disable control if it's system columns
|
||||
// ie: it's position is less then 1
|
||||
if (m.isNew()) {
|
||||
return false;
|
||||
} else {
|
||||
// if we are in edit mode
|
||||
return (_.isUndefined(m.get('attnum')) || m.get('attnum') < 1 );
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
// Disable/Enable Transition tables
|
||||
disableTransition: function(m) {
|
||||
if (!m.isNew())
|
||||
return true;
|
||||
var flag = false,
|
||||
evnt = null,
|
||||
name = this.name,
|
||||
evnt_count = 0;
|
||||
|
||||
// Disable transition tables for view trigger and PG version < 100000
|
||||
if(_.indexOf(Object.keys(m.node_info), 'table') == -1 ||
|
||||
m.node_info.server.version < 100000) return true;
|
||||
|
||||
if (name == 'tgoldtable') evnt = 'evnt_delete';
|
||||
else if (name == 'tgnewtable') evnt = 'evnt_insert';
|
||||
|
||||
if(m.get('evnt_insert')) evnt_count++;
|
||||
if(m.get('evnt_update')) evnt_count++;
|
||||
if(m.get('evnt_delete')) evnt_count++;
|
||||
|
||||
|
||||
// Disable transition tables if
|
||||
// - It is a constraint trigger
|
||||
// - Fires other than AFTER
|
||||
// - More than one events enabled
|
||||
// - Update event with the column list
|
||||
|
||||
// Disable Old transition table if both UPDATE and DELETE events are disabled
|
||||
// Disable New transition table if both UPDATE and INSERT events are disabled
|
||||
if(!m.get('is_constraint_trigger') && m.get('fires') == 'AFTER' &&
|
||||
(m.get('evnt_update') || m.get(evnt)) && evnt_count == 1) {
|
||||
flag = (m.get('evnt_update') && (_.size(m.get('columns')) >= 1 && m.get('columns')[0] != ''));
|
||||
}
|
||||
|
||||
flag && setTimeout(function() {
|
||||
if(m.get(name)) {
|
||||
m.set(name, null);
|
||||
}
|
||||
},10);
|
||||
|
||||
return flag;
|
||||
},
|
||||
}),
|
||||
canCreate: SchemaChildTreeNode.isTreeItemOfChildOfSchema,
|
||||
// Check to whether trigger is disable ?
|
||||
canCreate_with_trigger_enable: function(itemData, item, data) {
|
||||
|
@ -74,48 +74,6 @@ define('pgadmin.node.type', [
|
||||
|
||||
},
|
||||
ext_funcs: undefined,
|
||||
/* Few fields are kept since the properties tab for collection is not
|
||||
yet migrated to new react schema. Once the properties for collection
|
||||
is removed, remove this model */
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
defaults: {
|
||||
name: undefined,
|
||||
is_sys_type: false,
|
||||
typtype: undefined,
|
||||
},
|
||||
|
||||
// Default values!
|
||||
initialize: function(attrs, args) {
|
||||
if (_.size(attrs) === 0) {
|
||||
var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user,
|
||||
schemaInfo = args.node_info.schema;
|
||||
|
||||
this.set({
|
||||
'typeowner': userInfo.name, 'schema': schemaInfo._label,
|
||||
}, {silent: true});
|
||||
}
|
||||
pgBrowser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text', mode: ['properties', 'create', 'edit'],
|
||||
disabled: 'schemaCheck',
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
type: 'text' , mode: ['properties'],
|
||||
},{
|
||||
id: 'typeowner', label: gettext('Owner'), cell: 'string',
|
||||
control: 'node-list-by-name',
|
||||
type: 'text', mode: ['properties', 'create', 'edit'], node: 'role',
|
||||
disabled: 'inSchema', select2: {allowClear: false},
|
||||
}, {
|
||||
id: 'description', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline', mode: ['properties', 'create', 'edit'],
|
||||
disabled: 'inSchema',
|
||||
}]
|
||||
}),
|
||||
getSchema: (treeNodeInfo, itemNodeData) => {
|
||||
let nodeObj = pgAdmin.Browser.Nodes['type'];
|
||||
return new TypeSchema(
|
||||
|
@ -146,93 +146,6 @@ define('pgadmin.node.mview', [
|
||||
}
|
||||
);
|
||||
},
|
||||
/**
|
||||
Define model for the view node and specify the
|
||||
properties of the model in schema.
|
||||
*/
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
initialize: function(attrs, args) {
|
||||
if (_.size(attrs) === 0) {
|
||||
// Set Selected Schema and Current User
|
||||
var schemaLabel = args.node_info.schema._label || 'public',
|
||||
userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
|
||||
this.set({
|
||||
'schema': schemaLabel, 'owner': userInfo.name,
|
||||
}, {silent: true});
|
||||
}
|
||||
pgBrowser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
defaults: {
|
||||
spcname: undefined,
|
||||
},
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text', disabled: 'inSchema',
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
type: 'text', mode: ['properties'],
|
||||
},{
|
||||
id: 'owner', label: gettext('Owner'), cell: 'string',
|
||||
control: 'node-list-by-name', select2: { allowClear: false },
|
||||
node: 'role', disabled: 'inSchema',
|
||||
},{
|
||||
id: 'comment', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline',
|
||||
}],
|
||||
sessChanged: function() {
|
||||
/* If only custom autovacuum option is enabled the check if the options table is also changed. */
|
||||
if(_.size(this.sessAttrs) == 2 && this.sessAttrs['autovacuum_custom'] && this.sessAttrs['toast_autovacuum']) {
|
||||
return this.get('vacuum_table').sessChanged() || this.get('vacuum_toast').sessChanged();
|
||||
}
|
||||
if(_.size(this.sessAttrs) == 1 && (this.sessAttrs['autovacuum_custom'] || this.sessAttrs['toast_autovacuum'])) {
|
||||
return this.get('vacuum_table').sessChanged() || this.get('vacuum_toast').sessChanged();
|
||||
}
|
||||
return pgBrowser.DataModel.prototype.sessChanged.apply(this);
|
||||
},
|
||||
validate: function(keys) {
|
||||
|
||||
// Triggers specific error messages for fields
|
||||
var err = {},
|
||||
errmsg,
|
||||
field_name = this.get('name'),
|
||||
field_def = this.get('definition');
|
||||
|
||||
if(_.indexOf(keys, 'autovacuum_custom'))
|
||||
if (_.indexOf(keys, 'autovacuum_enabled') != -1 ||
|
||||
_.indexOf(keys, 'toast_autovacuum_enabled') != -1 )
|
||||
return null;
|
||||
|
||||
if (_.isUndefined(field_name) || _.isNull(field_name) ||
|
||||
String(field_name).replace(/^\s+|\s+$/g, '') == '') {
|
||||
err['name'] = gettext('Please specify name.');
|
||||
errmsg = err['name'];
|
||||
this.errorModel.set('name', errmsg);
|
||||
return errmsg;
|
||||
}else{
|
||||
this.errorModel.unset('name');
|
||||
}
|
||||
if (_.isUndefined(field_def) || _.isNull(field_def) ||
|
||||
String(field_def).replace(/^\s+|\s+$/g, '') == '') {
|
||||
err['definition'] = gettext('Please enter view definition.');
|
||||
errmsg = err['definition'];
|
||||
this.errorModel.set('definition', errmsg);
|
||||
return errmsg;
|
||||
}else{
|
||||
this.errorModel.unset('definition');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
// We will disable everything if we are under catalog node
|
||||
inSchema: function() {
|
||||
if(this.node_info && 'catalog' in this.node_info)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
}),
|
||||
|
||||
refresh_mview: function(args) {
|
||||
var input = args || {},
|
||||
|
@ -108,163 +108,6 @@ define('pgadmin.node.view', [
|
||||
}
|
||||
);
|
||||
},
|
||||
/**
|
||||
Define model for the view node and specify the
|
||||
properties of the model in schema.
|
||||
*/
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
initialize: function(attrs, args) {
|
||||
if (_.size(attrs) === 0) {
|
||||
// Set Selected Schema and, Current User
|
||||
var schemaLabel = args.node_info.schema._label || 'public',
|
||||
userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
|
||||
this.set({
|
||||
'schema': schemaLabel, 'owner': userInfo.name,
|
||||
}, {silent: true});
|
||||
}
|
||||
pgBrowser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text', disabled: 'notInSchema',
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
type: 'text', mode: ['properties'],
|
||||
},{
|
||||
id: 'owner', label: gettext('Owner'), cell: 'string', control: 'node-list-by-name',
|
||||
node: 'role', disabled: 'notInSchema', select2: { allowClear: false },
|
||||
},{
|
||||
id: 'schema', label: gettext('Schema'), cell: 'string', first_empty: false,
|
||||
control: 'node-list-by-name', type: 'text', cache_level: 'database',
|
||||
node: 'schema', disabled: 'notInSchema', mode: ['create', 'edit'],
|
||||
select2: { allowClear: false }, cache_node: 'database',
|
||||
},{
|
||||
id: 'system_view', label: gettext('System view?'), cell: 'string',
|
||||
type: 'switch', mode: ['properties'],
|
||||
},{
|
||||
id: 'acl', label: gettext('Privileges'),
|
||||
mode: ['properties'], type: 'text', group: gettext('Security'),
|
||||
},{
|
||||
id: 'comment', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline', disabled: 'notInSchema',
|
||||
},{
|
||||
id: 'security_barrier', label: gettext('Security barrier?'),
|
||||
type: 'switch', min_version: '90200', group: gettext('Definition'),
|
||||
disabled: 'notInSchema',
|
||||
},{
|
||||
id: 'check_option', label: gettext('Check options'),
|
||||
control: 'select2', group: gettext('Definition'), type: 'text',
|
||||
min_version: '90400', mode:['properties', 'create', 'edit'],
|
||||
select2: {
|
||||
// Set select2 option width to 100%
|
||||
allowClear: false,
|
||||
}, disabled: 'notInSchema',
|
||||
options:[{
|
||||
label: gettext('No'), value: 'no',
|
||||
},{
|
||||
label: gettext('Local'), value: 'local',
|
||||
},{
|
||||
label: gettext('Cascaded'), value: 'cascaded',
|
||||
}],
|
||||
},{
|
||||
id: 'definition', label: gettext('Code'), cell: 'string',
|
||||
type: 'text', mode: ['create', 'edit'], group: gettext('Code'),
|
||||
tabPanelCodeClass: 'sql-code-control',
|
||||
disabled: 'notInSchema',
|
||||
control: Backform.SqlCodeControl.extend({
|
||||
onChange: function() {
|
||||
Backform.SqlCodeControl.prototype.onChange.apply(this, arguments);
|
||||
|
||||
if (!this.model || !(
|
||||
this.model.changed &&
|
||||
this.model.node_info.server.server_type == 'pg' &&
|
||||
// No need to check this when creating a view
|
||||
this.model.get('oid') !== undefined
|
||||
) || !(
|
||||
this.model.origSessAttrs &&
|
||||
this.model.changed.definition != this.model.origSessAttrs.definition
|
||||
)) {
|
||||
this.model.warn_text = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
let old_def = this.model.origSessAttrs.definition &&
|
||||
this.model.origSessAttrs.definition.replace(
|
||||
/\s/gi, ''
|
||||
).split('FROM'),
|
||||
new_def = [];
|
||||
|
||||
if (this.model.changed.definition !== undefined) {
|
||||
new_def = this.model.changed.definition.replace(
|
||||
/\s/gi, ''
|
||||
).split('FROM');
|
||||
}
|
||||
|
||||
if ((old_def.length != new_def.length) || (
|
||||
old_def.length > 1 && (
|
||||
old_def[0] != new_def[0]
|
||||
)
|
||||
)) {
|
||||
this.model.warn_text = gettext(
|
||||
'Changing the columns in a view requires dropping and re-creating the view. This may fail if other objects are dependent upon this view, or may cause procedural functions to fail if they are not modified to take account of the changes.'
|
||||
) + '<br><br><b>' + gettext('Do you wish to continue?') +
|
||||
'</b>';
|
||||
} else {
|
||||
this.model.warn_text = undefined;
|
||||
}
|
||||
},
|
||||
}),
|
||||
}, pgBrowser.SecurityGroupSchema, {
|
||||
// Add Privilege Control
|
||||
id: 'datacl', label: gettext('Privileges'), type: 'collection',
|
||||
model: pgBrowser.Node.PrivilegeRoleModel.extend({
|
||||
privileges: ['a', 'r', 'w', 'd', 'D', 'x', 't'],
|
||||
}), uniqueCol : ['grantee'], editable: false, group: 'security',
|
||||
mode: ['edit', 'create'], canAdd: true, canDelete: true,
|
||||
control: 'unique-col-collection', disabled: 'notInSchema',
|
||||
},{
|
||||
// Add Security Labels Control
|
||||
id: 'seclabels', label: gettext('Security labels'),
|
||||
model: pgBrowser.SecLabelModel, editable: false, type: 'collection',
|
||||
canEdit: false, group: 'security', canDelete: true,
|
||||
mode: ['edit', 'create'], canAdd: true, disabled: 'notInSchema',
|
||||
control: 'unique-col-collection', uniqueCol : ['provider'],
|
||||
}],
|
||||
validate: function() {
|
||||
// Triggers specific error messages for fields
|
||||
var err = {},
|
||||
errmsg,
|
||||
field_name = this.get('name'),
|
||||
field_def = this.get('definition');
|
||||
if (_.isUndefined(field_name) || _.isNull(field_name) ||
|
||||
String(field_name).replace(/^\s+|\s+$/g, '') == '') {
|
||||
err['name'] = gettext('Please specify name.');
|
||||
errmsg = err['name'];
|
||||
this.errorModel.set('name', errmsg);
|
||||
return errmsg;
|
||||
}else{
|
||||
this.errorModel.unset('name');
|
||||
}
|
||||
if (_.isUndefined(field_def) || _.isNull(field_def) ||
|
||||
String(field_def).replace(/^\s+|\s+$/g, '') == '') {
|
||||
err['definition'] = gettext('Please enter view code.');
|
||||
errmsg = err['definition'];
|
||||
this.errorModel.set('definition', errmsg);
|
||||
return errmsg;
|
||||
}else{
|
||||
this.errorModel.unset('definition');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
// We will disable everything if we are under catalog node
|
||||
notInSchema: function() {
|
||||
if(this.node_info && 'catalog' in this.node_info) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -351,45 +351,6 @@ define('pgadmin.node.database', [
|
||||
}
|
||||
);
|
||||
},
|
||||
/* Few fields are kept since the properties tab for collection is not
|
||||
yet migrated to new react schema. Once the properties for collection
|
||||
is removed, remove this model */
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
idAttribute: 'did',
|
||||
defaults: {
|
||||
name: undefined,
|
||||
owner: undefined,
|
||||
comment: undefined,
|
||||
},
|
||||
|
||||
// Default values!
|
||||
initialize: function(attrs, args) {
|
||||
var isNew = (_.size(attrs) === 0);
|
||||
|
||||
if (isNew) {
|
||||
var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
|
||||
this.set({'datowner': userInfo.name}, {silent: true});
|
||||
}
|
||||
pgBrowser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
|
||||
schema: [
|
||||
{
|
||||
id: 'name', label: gettext('Database'), cell: 'string',
|
||||
editable: false, type: 'text',
|
||||
},{
|
||||
id: 'did', label: gettext('OID'), cell: 'string', mode: ['properties'],
|
||||
editable: false, type: 'text',
|
||||
},{
|
||||
id: 'datowner', label: gettext('Owner'),
|
||||
editable: false, type: 'text', node: 'role',
|
||||
control: Backform.NodeListByNameControl, select2: { allowClear: false },
|
||||
},{
|
||||
id: 'comments', label: gettext('Comment'),
|
||||
editable: false, type: 'multiline',
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
pgBrowser.SecurityGroupSchema = {
|
||||
|
@ -95,20 +95,20 @@ export default class DatabaseSchema extends BaseUISchema {
|
||||
return [
|
||||
{
|
||||
id: 'name', label: gettext('Database'), cell: 'text',
|
||||
editable: false, type: 'text', noEmpty: true,
|
||||
editable: false, type: 'text', noEmpty: true, isCollectionProperty: true,
|
||||
},{
|
||||
id: 'did', label: gettext('OID'), cell: 'text', mode: ['properties'],
|
||||
editable: false, type: 'text',
|
||||
},{
|
||||
id: 'datowner', label: gettext('Owner'),
|
||||
editable: false, type: 'select', options: this.fieldOptions.role,
|
||||
controlProps: { allowClear: false },
|
||||
controlProps: { allowClear: false }, isCollectionProperty: true,
|
||||
},{
|
||||
id: 'is_sys_obj', label: gettext('System database?'),
|
||||
cell: 'switch', type: 'switch', mode: ['properties'],
|
||||
},{
|
||||
id: 'comments', label: gettext('Comment'),
|
||||
editable: false, type: 'multiline',
|
||||
editable: false, type: 'multiline', isCollectionProperty: true,
|
||||
},{
|
||||
id: 'encoding', label: gettext('Encoding'),
|
||||
editable: false, type: 'select', group: gettext('Definition'),
|
||||
|
@ -14,8 +14,8 @@ import Notify from '../../../../../../../static/js/helpers/Notifier';
|
||||
|
||||
define('pgadmin.node.subscription', [
|
||||
'sources/gettext', 'sources/url_for', 'jquery',
|
||||
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform', 'pgadmin.browser.collection',
|
||||
], function(gettext, url_for, $, pgAdmin, pgBrowser, Backform) {
|
||||
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.browser.collection',
|
||||
], function(gettext, url_for, $, pgAdmin, pgBrowser) {
|
||||
|
||||
// Extend the browser's collection class for subscriptions collection
|
||||
if (!pgBrowser.Nodes['coll-subscription']) {
|
||||
@ -24,7 +24,7 @@ define('pgadmin.node.subscription', [
|
||||
node: 'subscription',
|
||||
label: gettext('Subscriptions'),
|
||||
type: 'coll-subscription',
|
||||
columns: ['name', 'subowner', 'pub', 'enabled'],
|
||||
columns: ['name', 'subowner', 'proppub', 'enabled'],
|
||||
hasStatistics: true,
|
||||
});
|
||||
}
|
||||
@ -74,101 +74,6 @@ define('pgadmin.node.subscription', [
|
||||
enable: 'canCreate',
|
||||
}]);
|
||||
},
|
||||
// Define the model for subscription node
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
defaults: {
|
||||
name: undefined,
|
||||
subowner: undefined,
|
||||
pubtable: undefined,
|
||||
connect_timeout: 10,
|
||||
pub:[],
|
||||
enabled:true,
|
||||
create_slot: true,
|
||||
copy_data:true,
|
||||
connect:true,
|
||||
copy_data_after_refresh:false,
|
||||
sync:'off',
|
||||
refresh_pub: false,
|
||||
password: '',
|
||||
sslmode: 'prefer',
|
||||
sslcompression: false,
|
||||
sslcert: '',
|
||||
sslkey: '',
|
||||
sslrootcert: '',
|
||||
sslcrl: '',
|
||||
host: '',
|
||||
hostaddr: '',
|
||||
port: 5432,
|
||||
db: 'postgres',
|
||||
},
|
||||
|
||||
// Default values!
|
||||
initialize: function(attrs, args) {
|
||||
var isNew = (_.size(attrs) === 0);
|
||||
if (isNew) {
|
||||
var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
|
||||
|
||||
this.set({'subowner': userInfo.name}, {silent: true});
|
||||
}
|
||||
pgBrowser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
|
||||
// Define the schema for the subscription node
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), type: 'text',
|
||||
mode: ['properties', 'create', 'edit'],
|
||||
visible: function() {
|
||||
if(!_.isUndefined(this.node_info) && !_.isUndefined(this.node_info.server)
|
||||
&& !_.isUndefined(this.node_info.server.version) &&
|
||||
this.node_info.server.version >= 100000) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string', mode: ['properties'],
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
id: 'subowner', label: gettext('Owner'), type: 'text',
|
||||
control: Backform.NodeListByNameControl, node: 'role',
|
||||
mode: ['edit', 'properties', 'create'], select2: { allowClear: false},
|
||||
disabled: function(m){
|
||||
if(m.isNew())
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'enabled', label: gettext('Enabled?'),
|
||||
type: 'switch', mode: ['properties'],
|
||||
group: gettext('With'),
|
||||
readonly: 'isConnect', deps :['connect'],
|
||||
helpMessage: gettext('Specifies whether the subscription should be actively replicating, or whether it should be just setup but not started yet.'),
|
||||
},
|
||||
{
|
||||
id: 'pub', label: gettext('Publication'), type: 'text', group: gettext('Connection'),
|
||||
mode: ['properties'],
|
||||
},
|
||||
],
|
||||
sessChanged: function() {
|
||||
if (!this.isNew() && _.isUndefined(this.attributes['refresh_pub']))
|
||||
return false;
|
||||
return pgBrowser.DataModel.prototype.sessChanged.apply(this);
|
||||
},
|
||||
canCreate: function(itemData, item) {
|
||||
var treeData = pgBrowser.tree.getTreeNodeHierarchy(item),
|
||||
server = treeData['server'];
|
||||
|
||||
// If server is less than 10 then do not allow 'create' menu
|
||||
if (server && server.version < 100000)
|
||||
return false;
|
||||
|
||||
// by default we want to allow create menu
|
||||
return true;
|
||||
},
|
||||
}),
|
||||
getSchema: function(treeNodeInfo, itemNodeData){
|
||||
return new SubscriptionSchema(
|
||||
{
|
||||
|
@ -176,7 +176,7 @@ export default class SubscriptionSchema extends BaseUISchema{
|
||||
mode: ['properties', 'edit', 'create'],
|
||||
},
|
||||
{
|
||||
id: 'pub', label: gettext('Publication'), type: 'text', group: gettext('Connection'),
|
||||
id: 'proppub', label: gettext('Publication'), type: 'text', group: gettext('Connection'),
|
||||
mode: ['properties'],
|
||||
},
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
SELECT sub.oid as oid,
|
||||
subname as name,
|
||||
subpublications as pub,
|
||||
subpublications as proppub,
|
||||
sub.subsynccommit as sync,
|
||||
pga.rolname as subowner,
|
||||
subslotname as slot_name,
|
||||
|
@ -582,152 +582,6 @@ define('pgadmin.node.role', [
|
||||
},
|
||||
);
|
||||
},
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
defaults: {
|
||||
oid: null,
|
||||
rolname: undefined,
|
||||
rolcanlogin: false,
|
||||
rolconnlimit: -1,
|
||||
rolsuper: false,
|
||||
rolcreaterole: false,
|
||||
rolcreatedb: false,
|
||||
rolinherit: true,
|
||||
rolcatupdate: false,
|
||||
rolreplication: false,
|
||||
rolvaliduntil: null,
|
||||
},
|
||||
schema: [{
|
||||
id: 'rolname', label: gettext('Name'), type: 'text',
|
||||
readonly: 'readonly',
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string', mode: ['properties'],
|
||||
editable: false, type: 'text', visible: true,
|
||||
},{
|
||||
id: 'rolvaliduntil', readonly: 'readonly', type: 'text',
|
||||
group: gettext('Definition'), label: gettext('Account expires'),
|
||||
mode: ['properties', 'edit', 'create'], control: 'datetimepicker',
|
||||
deps: ['rolcanlogin'],
|
||||
placeholder: gettext('No Expiry'),
|
||||
helpMessage: gettext('Please note that if you leave this field blank, then password will never expire.'),
|
||||
setMinDate: false,
|
||||
},{
|
||||
id: 'rolconnlimit', type: 'int', group: gettext('Definition'),
|
||||
label: gettext('Connection limit'), cell: 'integer', min : -1,
|
||||
mode: ['properties', 'edit', 'create'], readonly: 'readonly',
|
||||
},{
|
||||
id: 'rolcanlogin', label: gettext('Can login?'),
|
||||
type: 'switch',
|
||||
controlLabelClassName: 'control-label pg-el-sm-4 pg-el-12',
|
||||
controlsClassName: 'pgadmin-controls pg-el-sm-8 pg-el-12',
|
||||
group: gettext('Privileges'),
|
||||
readonly: 'readonly',
|
||||
},{
|
||||
id: 'rolsuper', label: gettext('Superuser?'),
|
||||
type: 'switch',
|
||||
controlLabelClassName: 'control-label pg-el-sm-4 pg-el-12',
|
||||
controlsClassName: 'pgadmin-controls pg-el-sm-8 pg-el-12',
|
||||
group: gettext('Privileges'),
|
||||
control: Backform.SwitchControl.extend({
|
||||
onChange: function() {
|
||||
Backform.SwitchControl.prototype.onChange.apply(this, arguments);
|
||||
|
||||
this.model.set('rolcatupdate', this.model.get('rolsuper'));
|
||||
this.model.set('rolcreaterole', this.model.get('rolsuper'));
|
||||
this.model.set('rolcreatedb', this.model.get('rolsuper'));
|
||||
},
|
||||
}),
|
||||
readonly: 'readonly',
|
||||
},{
|
||||
id: 'rolcreaterole', label: gettext('Create roles?'),
|
||||
group: gettext('Privileges'),
|
||||
type: 'switch',
|
||||
controlLabelClassName: 'control-label pg-el-sm-4 pg-el-12',
|
||||
controlsClassName: 'pgadmin-controls pg-el-sm-8 pg-el-12',
|
||||
readonly: 'readonly',
|
||||
},{
|
||||
id: 'is_sys_obj', label: gettext('System role?'),
|
||||
cell:'boolean', type: 'switch', mode: ['properties'],
|
||||
},{
|
||||
id: 'description', label: gettext('Comments'), type: 'multiline',
|
||||
group: null, mode: ['properties', 'edit', 'create'],
|
||||
readonly: 'readonly',
|
||||
},{
|
||||
id: 'rolcreatedb', label: gettext('Create databases?'),
|
||||
group: gettext('Privileges'),
|
||||
type: 'switch',
|
||||
controlLabelClassName: 'control-label pg-el-sm-4 pg-el-12',
|
||||
controlsClassName: 'pgadmin-controls pg-el-sm-8 pg-el-12',
|
||||
readonly: 'readonly',
|
||||
},{
|
||||
id: 'rolcatupdate', label: gettext('Update catalog?'),
|
||||
type: 'switch',
|
||||
controlLabelClassName: 'control-label pg-el-sm-4 pg-el-12',
|
||||
controlsClassName: 'pgadmin-controls pg-el-sm-8 pg-el-12',
|
||||
max_version: 90400,
|
||||
group: gettext('Privileges'), readonly: function(m) {
|
||||
return m.get('read_only');
|
||||
},
|
||||
disabled: function(m) {
|
||||
return !m.get('rolsuper');
|
||||
},
|
||||
},{
|
||||
id: 'rolinherit', group: gettext('Privileges'),
|
||||
label: gettext('Inherit rights from the parent roles?'),
|
||||
type: 'switch',
|
||||
controlLabelClassName: 'control-label pg-el-sm-4 pg-el-12',
|
||||
controlsClassName: 'pgadmin-controls pg-el-sm-8 pg-el-12',
|
||||
readonly: 'readonly',
|
||||
},{
|
||||
id: 'rolreplication', group: gettext('Privileges'),
|
||||
label: gettext('Can initiate streaming replication and backups?'),
|
||||
type: 'switch',
|
||||
controlLabelClassName: 'control-label pg-el-sm-4 pg-el-12',
|
||||
controlsClassName: 'pgadmin-controls pg-el-sm-8 pg-el-12',
|
||||
min_version: 90100,
|
||||
readonly: 'readonly',
|
||||
}],
|
||||
readonly: function(m) {
|
||||
if (!m.has('read_only')) {
|
||||
var user = this.node_info.server.user;
|
||||
|
||||
m.set('read_only', !(user.is_superuser || user.can_create_role));
|
||||
}
|
||||
|
||||
return m.get('read_only');
|
||||
},
|
||||
validate: function()
|
||||
{
|
||||
var err = {},
|
||||
errmsg,
|
||||
seclabels = this.get('seclabels');
|
||||
|
||||
if (_.isUndefined(this.get('rolname')) || String(this.get('rolname')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
err['name'] = gettext('Name cannot be empty.');
|
||||
errmsg = err['name'];
|
||||
}
|
||||
|
||||
if (seclabels) {
|
||||
var secLabelsErr;
|
||||
for (var i = 0; i < seclabels.models.length && !secLabelsErr; i++) {
|
||||
secLabelsErr = (seclabels.models[i]).validate.apply(seclabels.models[i]);
|
||||
if (secLabelsErr) {
|
||||
err['seclabels'] = secLabelsErr;
|
||||
errmsg = errmsg || secLabelsErr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.errorModel.clear().set(err);
|
||||
|
||||
if (_.size(err)) {
|
||||
this.trigger('on-status', {msg: errmsg});
|
||||
return errmsg;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -7,8 +7,9 @@
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import {removeNodeView} from './node_view';
|
||||
import Notify from '../../../static/js/helpers/Notifier';
|
||||
// import {removeNodeView} from './node_view';
|
||||
// import Notify from '../../../static/js/helpers/Notifier';
|
||||
import {getPanelView} from './panel_view';
|
||||
|
||||
define([
|
||||
'sources/gettext', 'jquery', 'underscore', 'sources/pgadmin',
|
||||
@ -91,399 +92,14 @@ define([
|
||||
canDropCascade: true,
|
||||
selectParentNodeOnDelete: false,
|
||||
showProperties: function(item, data, panel) {
|
||||
var that = this,
|
||||
j = panel.$container.find('.obj_properties').first(),
|
||||
view = j.data('obj-view'),
|
||||
content = $('<div></div>')
|
||||
.addClass('pg-prop-content col-12 has-pg-prop-btn-group'),
|
||||
node = pgBrowser.Nodes[that.node],
|
||||
$msgContainer = '',
|
||||
// This will be the URL, used for object manipulation.
|
||||
urlBase = this.generate_url(item, 'properties', data),
|
||||
info = pgBrowser.tree.getTreeNodeHierarchy(item),
|
||||
gridSchema = Backform.generateGridColumnsFromModel(
|
||||
info, node.model, 'properties', that.columns
|
||||
),
|
||||
createButtons = function(buttonsList, location, extraClasses) {
|
||||
// Arguments must be non-zero length array of type
|
||||
// object, which contains following attributes:
|
||||
// label, type, extraClasses, register
|
||||
if (buttonsList && _.isArray(buttonsList) && buttonsList.length > 0) {
|
||||
// All buttons will be created within a single
|
||||
// div area.
|
||||
var 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%>">',
|
||||
'<span class="<%= icon %>" role="img"></span><% if (label != "") { %> <%-label%><% } %><span class="sr-only"><%-tooltip%></span></button>',
|
||||
].join(' '));
|
||||
if (location == 'header') {
|
||||
btnGroup.appendTo(that.header);
|
||||
} else {
|
||||
btnGroup.appendTo(that.footer);
|
||||
}
|
||||
if (extraClasses) {
|
||||
btnGroup.addClass(extraClasses);
|
||||
}
|
||||
_.each(buttonsList, function(btn) {
|
||||
// Create the actual button, and append to
|
||||
// the group div
|
||||
|
||||
// icon may not present for this button
|
||||
if (!btn.icon) {
|
||||
btn.icon = '';
|
||||
}
|
||||
var 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);
|
||||
|
||||
that.collection = new (node.Collection.extend({
|
||||
url: urlBase,
|
||||
model: node.model,
|
||||
}))();
|
||||
// Add the new column for the multi-select menus
|
||||
if((_.isFunction(that.canDrop) ?
|
||||
that.canDrop.apply(that, [data, item]) : that.canDrop) ||
|
||||
(_.isFunction(that.canDropCascade) ?
|
||||
that.canDropCascade.apply(that, [data, item]) : that.canDropCascade)) {
|
||||
gridSchema.columns.unshift({
|
||||
name: 'oid',
|
||||
cell: Backgrid.Extension.SelectRowCell.extend({
|
||||
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 width_percent_3');
|
||||
|
||||
this.listenTo(model, 'backgrid:select', this.toggleCheckbox);
|
||||
},
|
||||
toggleCheckbox: function(model, selected) {
|
||||
if (this.checkbox().prop('disabled') === false) {
|
||||
this.checkbox().prop('checked', selected).change();
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
let model = this.model.toJSON();
|
||||
// canDrop can be set to false for individual row from the server side to disable the checkbox
|
||||
let disabled = ('canDrop' in model && model.canDrop === false);
|
||||
let id = `row-${_.uniqueId(model.oid || model.name)}`;
|
||||
|
||||
this.$el.empty().append(`
|
||||
<div class="custom-control custom-checkbox custom-checkbox-no-label">
|
||||
<input tabindex="-1" type="checkbox" class="custom-control-input" id="${id}" ${disabled?'disabled':''}/>
|
||||
<label class="custom-control-label" for="${id}">
|
||||
<span class="sr-only">` + gettext('Select') + `<span>
|
||||
</label>
|
||||
</div>
|
||||
`);
|
||||
this.delegateEvents();
|
||||
return this;
|
||||
},
|
||||
}),
|
||||
headerCell: Backgrid.Extension.SelectAllHeaderCell,
|
||||
});
|
||||
}
|
||||
/* Columns should be always non-editable */
|
||||
gridSchema.columns.forEach((col)=>{
|
||||
col.disabled = true;
|
||||
});
|
||||
|
||||
// Get the list of selected models, before initializing the grid
|
||||
// again.
|
||||
var selectedModels = [];
|
||||
if(!_.isUndefined(that.grid) && 'collection' in that.grid){
|
||||
selectedModels = that.grid.getSelectedModels();
|
||||
}
|
||||
|
||||
// Initialize a new Grid instance
|
||||
that.grid = new Backgrid.Grid({
|
||||
emptyText: gettext('No data found'),
|
||||
columns: gridSchema.columns,
|
||||
collection: that.collection,
|
||||
className: 'backgrid table presentation table-bordered table-noouter-border table-hover',
|
||||
});
|
||||
|
||||
var gridView = {
|
||||
'remove': function() {
|
||||
if (this.grid) {
|
||||
if (this.grid.collection) {
|
||||
this.grid.collection.reset([], {silent: true});
|
||||
delete (this.grid.collection);
|
||||
}
|
||||
delete (this.grid);
|
||||
this.grid = null;
|
||||
}
|
||||
},
|
||||
grid: that.grid,
|
||||
};
|
||||
|
||||
if (view) {
|
||||
// Cache the current IDs for next time
|
||||
$(panel).data('node-prop', urlBase);
|
||||
|
||||
// Reset the data object
|
||||
j.data('obj-view', null);
|
||||
}
|
||||
|
||||
/* Remove any dom rendered by getNodeView */
|
||||
removeNodeView(j[0]);
|
||||
// Make sure the HTML element is empty.
|
||||
j.empty();
|
||||
j.data('obj-view', gridView);
|
||||
|
||||
$msgContainer = '<div role="status" class="pg-panel-message pg-panel-properties-message">' +
|
||||
gettext('Retrieving data from the server...') + '</div>';
|
||||
|
||||
$msgContainer = $($msgContainer).appendTo(j);
|
||||
|
||||
that.header = $('<div></div>').addClass(
|
||||
'pg-prop-header'
|
||||
let container = panel.$container[0];
|
||||
getPanelView(
|
||||
pgBrowser.tree,
|
||||
container,
|
||||
pgBrowser,
|
||||
panel._type
|
||||
);
|
||||
|
||||
// Render the buttons
|
||||
var buttons = [];
|
||||
|
||||
buttons.push({
|
||||
label: '',
|
||||
type: 'delete',
|
||||
tooltip: gettext('Delete/Drop'),
|
||||
extraClasses: ['btn-primary-icon m-1', 'delete_multiple'],
|
||||
icon: 'fa fa-trash-alt',
|
||||
disabled: (_.isFunction(that.canDrop)) ? !(that.canDrop.apply(self, [data, item])) : (!that.canDrop),
|
||||
register: function(btn) {
|
||||
btn.on('click',() => {
|
||||
onDrop('drop');
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
buttons.push({
|
||||
label: '',
|
||||
type: 'delete',
|
||||
tooltip: gettext('Drop Cascade'),
|
||||
extraClasses: ['btn-primary-icon m-1', 'delete_multiple_cascade'],
|
||||
icon: 'pg-font-icon icon-drop_cascade',
|
||||
disabled: (_.isFunction(that.canDropCascade)) ? !(that.canDropCascade.apply(self, [data, item])) : (!that.canDropCascade),
|
||||
register: function(btn) {
|
||||
btn.on('click',() => {
|
||||
onDrop('dropCascade');
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
createButtons(buttons, 'header', 'pg-prop-btn-group-above');
|
||||
|
||||
// Render subNode grid
|
||||
content.append('<div class="pg-prop-coll-container"></div>');
|
||||
content.find('.pg-prop-coll-container').append(that.grid.render().$el);
|
||||
|
||||
var timer;
|
||||
var getAjaxHook = function() {
|
||||
$.ajax({
|
||||
url: urlBase,
|
||||
type: 'GET',
|
||||
beforeSend: function(xhr) {
|
||||
xhr.setRequestHeader(pgAdmin.csrf_token_header, pgAdmin.csrf_token);
|
||||
// Generate a timer for the request
|
||||
timer = setTimeout(function() {
|
||||
// notify user if request is taking longer than 1 second
|
||||
|
||||
if (!$msgContainer.text()== 'Failed to retrieve data from the server.')
|
||||
$msgContainer.text(gettext('Retrieving data from the server...'));
|
||||
$msgContainer.removeClass('d-none');
|
||||
if (self.grid) {
|
||||
self.grid.remove();
|
||||
}
|
||||
}, 1000);
|
||||
},
|
||||
}).done(function(res) {
|
||||
clearTimeout(timer);
|
||||
|
||||
if (_.isUndefined(that.grid) || _.isNull(that.grid)) return;
|
||||
|
||||
that.data = res;
|
||||
|
||||
if (that.data.length > 0) {
|
||||
|
||||
if (!$msgContainer.hasClass('d-none')) {
|
||||
$msgContainer.addClass('d-none');
|
||||
}
|
||||
that.header.appendTo(j);
|
||||
j.append(content);
|
||||
|
||||
// Listen scroll event to load more rows
|
||||
$('.pg-prop-content').on('scroll', that.__loadMoreRows.bind(that));
|
||||
|
||||
that.collection.reset(that.data.splice(0, 50));
|
||||
|
||||
// Listen to select all checkbox event
|
||||
that.collection.on('backgrid:select-all', that.__loadAllRows.bind(that));
|
||||
|
||||
// Trigger the backgrid:select event for already selected items
|
||||
// as we have created a new grid instance.
|
||||
if(selectedModels.length > 0) {
|
||||
that.collection.each(function (model) {
|
||||
for(let model_val of selectedModels){
|
||||
if (model_val.id == model.id){
|
||||
model.trigger('backgrid:select', model, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Do not listen the scroll event
|
||||
$('.pg-prop-content').off('scroll', that.__loadMoreRows);
|
||||
|
||||
$msgContainer.text(gettext('No properties are available for the selected object.'));
|
||||
|
||||
}
|
||||
selectedModels = [];
|
||||
}).fail(function(xhr, error) {
|
||||
pgBrowser.Events.trigger(
|
||||
'pgadmin:node:retrieval:error', 'properties', xhr, error.message, item, that
|
||||
);
|
||||
if (!Alertify.pgHandleItemError(xhr, error.message, {
|
||||
item: item,
|
||||
info: info,
|
||||
})) {
|
||||
Notify.pgNotifier(
|
||||
error, xhr, gettext('Error retrieving properties - %s', error.message || that.label),
|
||||
function(msg) {
|
||||
if(msg === 'CRYPTKEY_SET') {
|
||||
getAjaxHook();
|
||||
} else {
|
||||
console.warn(arguments);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
// show failed message.
|
||||
$msgContainer.text(gettext('Failed to retrieve data from the server.'));
|
||||
});
|
||||
};
|
||||
getAjaxHook();
|
||||
|
||||
var onDrop = function(type, confirm=true) {
|
||||
let sel_row_models = this.grid.getSelectedModels(),
|
||||
sel_rows = [],
|
||||
sel_item = pgBrowser.tree.selected(),
|
||||
d = sel_item ? pgBrowser.tree.itemData(sel_item) : null,
|
||||
sel_node = d && pgBrowser.Nodes[d._type],
|
||||
url = undefined,
|
||||
msg = undefined,
|
||||
title = undefined;
|
||||
|
||||
if (sel_node && sel_node.type && sel_node.type == 'coll-constraints') {
|
||||
// In order to identify the constraint type, the type should be passed to the server
|
||||
sel_rows = sel_row_models.map(row => ({id: row.get('oid'), _type: row.get('_type')}));
|
||||
}
|
||||
else {
|
||||
sel_rows = sel_row_models.map(row => row.id);
|
||||
}
|
||||
|
||||
if (sel_rows.length === 0) {
|
||||
Notify.alert(gettext('Drop Multiple'),
|
||||
gettext('Please select at least one object to delete.')
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sel_node)
|
||||
return;
|
||||
|
||||
if (type === 'dropCascade') {
|
||||
url = sel_node.generate_url(sel_item, 'delete');
|
||||
msg = gettext('Are you sure you want to drop all the selected objects and all the objects that depend on them?');
|
||||
title = gettext('DROP CASCADE multiple objects?');
|
||||
} else {
|
||||
url = sel_node.generate_url(sel_item, 'drop');
|
||||
msg = gettext('Are you sure you want to drop all the selected objects?');
|
||||
title = gettext('DROP multiple objects?');
|
||||
}
|
||||
|
||||
let dropAjaxHook = function() {
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'DELETE',
|
||||
data: JSON.stringify({'ids': sel_rows}),
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
}).done(function(res) {
|
||||
if (res.success == 0) {
|
||||
pgBrowser.report_error(res.errormsg, res.info);
|
||||
} else {
|
||||
$(pgBrowser.panels['properties'].panel).removeData('node-prop');
|
||||
pgBrowser.Events.trigger(
|
||||
'pgadmin:browser:tree:refresh', sel_item || pgBrowser.tree.selected(), {
|
||||
success: function() {
|
||||
setTimeout(function() {
|
||||
pgBrowser.tree.select(sel_item);
|
||||
sel_node.callbacks.selected.apply(sel_node, [sel_item]);
|
||||
}, 100);
|
||||
},
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}).fail(function(xhr, error) {
|
||||
Notify.pgNotifier(
|
||||
error, xhr,
|
||||
gettext('Error dropping %s', d._label.toLowerCase()),
|
||||
function(alertMsg) {
|
||||
if (alertMsg == 'CRYPTKEY_SET') {
|
||||
onDrop(type, false);
|
||||
} else {
|
||||
$(pgBrowser.panels['properties'].panel).removeData('node-prop');
|
||||
pgBrowser.Events.trigger(
|
||||
'pgadmin:browser:tree:refresh', sel_item || pgBrowser.tree.selected(), {
|
||||
success: function() {
|
||||
sel_node.callbacks.selected.apply(sel_node, [sel_item]);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
if(confirm) {
|
||||
Notify.confirm(title, msg, dropAjaxHook, null);
|
||||
} else {
|
||||
dropAjaxHook();
|
||||
}
|
||||
}.bind(that);
|
||||
},
|
||||
__loadMoreRows: function(e) {
|
||||
let elem = e.currentTarget;
|
||||
if ((elem.scrollHeight - 10) < elem.scrollTop + elem.offsetHeight) {
|
||||
if (this.data.length > 0) {
|
||||
this.collection.add(this.data.splice(0, 50));
|
||||
}
|
||||
}
|
||||
},
|
||||
__loadAllRows: function(tmp, checked) {
|
||||
if (this.data.length > 0) {
|
||||
this.collection.add(this.data);
|
||||
this.collection.each(function (model) {
|
||||
model.trigger('backgrid:select', model, checked);
|
||||
});
|
||||
}
|
||||
},
|
||||
generate_url: function(item, type) {
|
||||
/*
|
||||
|
@ -1082,8 +1082,6 @@ define('pgadmin.browser.node', [
|
||||
b.panels['properties'] &&
|
||||
b.panels['properties'].panel &&
|
||||
b.panels['properties'].panel.isVisible()) {
|
||||
// Show object properties (only when the 'properties' tab
|
||||
// is active).
|
||||
this.showProperties(item, d, b.panels['properties'].panel);
|
||||
}
|
||||
}
|
||||
@ -1259,20 +1257,20 @@ define('pgadmin.browser.node', [
|
||||
d = i && tree.itemData(i),
|
||||
treeHierarchy = tree.getTreeNodeHierarchy(i);
|
||||
|
||||
if (_.isEqual($(this).data('node-prop'), treeHierarchy)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache the current IDs for next time
|
||||
$(this).data('node-prop', treeHierarchy);
|
||||
|
||||
/* Remove any dom rendered by getNodeView */
|
||||
removeNodeView(j[0]);
|
||||
/* getSchema is a schema for React. Get the react node view */
|
||||
removeNodeView(pgBrowser.panels['properties'].panel.$container[0]);
|
||||
|
||||
var containerProperties = pgBrowser.panels['properties'].panel.$container;
|
||||
containerProperties.addClass('pg-panel-content pg-no-overflow pg-el-container');
|
||||
|
||||
|
||||
if(that.getSchema) {
|
||||
let treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item);
|
||||
getNodeView(
|
||||
that.type, treeNodeInfo, 'properties', data, 'tab', j[0], this, onEdit
|
||||
that.type, treeNodeInfo, 'properties', data, 'tab', containerProperties[0], this, onEdit
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
@ -25,8 +25,9 @@ export function generateCollectionURL(item, type) {
|
||||
};
|
||||
var treeInfo = pgAdmin.Browser.tree.getTreeNodeHierarchy(item);
|
||||
var actionType = type in opURL ? opURL[type] : type;
|
||||
var nodeType = type === 'properties' ? nodeObj.type : nodeObj.node;
|
||||
return generate_url(
|
||||
pgAdmin.Browser.URL, treeInfo, actionType, nodeObj.node,
|
||||
pgAdmin.Browser.URL, treeInfo, actionType, nodeType,
|
||||
collectionPickFunction
|
||||
);
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ import {getHelpUrl, getEPASHelpUrl} from 'pgadmin.help';
|
||||
import SchemaView from 'sources/SchemaView';
|
||||
import { generateNodeUrl } from './node_ajax';
|
||||
import Notify from '../../../static/js/helpers/Notifier';
|
||||
|
||||
import gettext from 'sources/gettext';
|
||||
import 'wcdocker';
|
||||
|
||||
|
@ -122,14 +122,32 @@ define(
|
||||
that.resizedContainer.apply(myPanel);
|
||||
}
|
||||
|
||||
// Bind events only if they are configurable
|
||||
if (that.canHide) {
|
||||
_.each([wcDocker.EVENT.CLOSED, wcDocker.EVENT.VISIBILITY_CHANGED],
|
||||
function(ev) {
|
||||
myPanel.on(ev, that.handleVisibility.bind(myPanel, ev));
|
||||
});
|
||||
|
||||
|
||||
if (myPanel._type == 'dashboard') {
|
||||
getPanelView(
|
||||
pgBrowser.tree,
|
||||
$container[0],
|
||||
pgBrowser,
|
||||
myPanel._type
|
||||
);
|
||||
}
|
||||
|
||||
// Rerender the dashboard panel when preference value 'show graph' gets changed.
|
||||
pgBrowser.onPreferencesChange('dashboards', function() {
|
||||
getPanelView(
|
||||
pgBrowser.tree,
|
||||
$container[0],
|
||||
pgBrowser,
|
||||
myPanel._type
|
||||
);
|
||||
});
|
||||
|
||||
_.each([wcDocker.EVENT.CLOSED, wcDocker.EVENT.VISIBILITY_CHANGED],
|
||||
function(ev) {
|
||||
myPanel.on(ev, that.handleVisibility.bind(myPanel, ev));
|
||||
});
|
||||
|
||||
pgBrowser.Events.on('pgadmin-browser:tree:selected', () => {
|
||||
|
||||
if(myPanel.isVisible()) {
|
||||
@ -141,6 +159,18 @@ define(
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
pgBrowser.Events.on('pgadmin-browser:tree:refreshing', () => {
|
||||
|
||||
if(myPanel.isVisible()) {
|
||||
getPanelView(
|
||||
pgBrowser.tree,
|
||||
$container[0],
|
||||
pgBrowser,
|
||||
myPanel._type
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -205,11 +235,6 @@ define(
|
||||
}
|
||||
},
|
||||
handleVisibility: function(eventName) {
|
||||
// Supported modules
|
||||
let type_module = {
|
||||
dashboard: pgAdmin.Dashboard,
|
||||
};
|
||||
let module = type_module[this._type];
|
||||
if (_.isNull(pgBrowser.tree)) return;
|
||||
|
||||
let selectedPanel = pgBrowser.docker.findPanels(this._type)[0];
|
||||
@ -219,18 +244,6 @@ define(
|
||||
.scene()
|
||||
.find('.pg-panel-content');
|
||||
|
||||
if (this._type === 'dashboard') {
|
||||
if (eventName == 'panelClosed') {
|
||||
module.toggleVisibility.call(module, false, true);
|
||||
} else if (eventName == 'panelVisibilityChanged') {
|
||||
module.toggleVisibility.call(
|
||||
module,
|
||||
pgBrowser.docker.findPanels(this._type)[0].isVisible(),
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isPanelVisible) {
|
||||
if (eventName == 'panelClosed') {
|
||||
removePanelView($container[0]);
|
||||
|
@ -13,6 +13,11 @@ import Theme from 'sources/Theme';
|
||||
import Dependencies from '../../../misc/dependencies/static/js/Dependencies';
|
||||
import Dependents from '../../../misc/dependents/static/js/Dependents';
|
||||
import Statistics from '../../../misc/statistics/static/js/Statistics';
|
||||
import SQL from '../../../misc/sql/static/js/SQL';
|
||||
import Dashboard from '../../../dashboard/static/js/Dashboard';
|
||||
import _ from 'lodash';
|
||||
import { CollectionNodeView } from '../../../misc/properties/CollectionNodeProperties';
|
||||
|
||||
|
||||
/* The entry point for rendering React based view in properties, called in node.js */
|
||||
export function getPanelView(
|
||||
@ -21,10 +26,33 @@ export function getPanelView(
|
||||
pgBrowser,
|
||||
panelType
|
||||
) {
|
||||
let item = tree.selected(),
|
||||
nodeData = item && tree.itemData(item),
|
||||
node = item && nodeData && pgBrowser.Nodes[nodeData._type],
|
||||
let item = !_.isNull(tree)? tree.selected(): null,
|
||||
|
||||
nodeData, node, treeNodeInfo, preferences;
|
||||
if (item){
|
||||
nodeData = tree.itemData(item);
|
||||
node = nodeData && pgBrowser.Nodes[nodeData._type];
|
||||
treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item);
|
||||
preferences = pgBrowser.get_preferences_for_module('dashboards');
|
||||
}
|
||||
if (panelType == 'dashboard') {
|
||||
ReactDOM.render(
|
||||
<Theme>
|
||||
<Dashboard
|
||||
treeNodeInfo={treeNodeInfo}
|
||||
pgBrowser={pgBrowser}
|
||||
nodeData={nodeData}
|
||||
node={node}
|
||||
item={item}
|
||||
preferences={preferences}
|
||||
did={((!_.isUndefined(treeNodeInfo)) && (!_.isUndefined(treeNodeInfo['database']))) ? treeNodeInfo['database']._id: 0}
|
||||
sid={!_.isUndefined(treeNodeInfo) && !_.isUndefined(treeNodeInfo['server']) ? treeNodeInfo['server']._id : ''}
|
||||
serverConnected={!_.isUndefined(treeNodeInfo) && !_.isUndefined(treeNodeInfo['server']) ? treeNodeInfo.server.connected: false}
|
||||
/>
|
||||
</Theme>,
|
||||
container
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (panelType == 'statistics') {
|
||||
@ -41,6 +69,20 @@ export function getPanelView(
|
||||
container
|
||||
);
|
||||
}
|
||||
if (panelType == 'properties') {
|
||||
ReactDOM.render(
|
||||
<Theme>
|
||||
<CollectionNodeView
|
||||
treeNodeInfo={treeNodeInfo}
|
||||
item={item}
|
||||
itemNodeData={nodeData}
|
||||
node={node}
|
||||
pgBrowser={pgBrowser}
|
||||
/>
|
||||
</Theme>,
|
||||
container
|
||||
);
|
||||
}
|
||||
if (panelType == 'dependencies') {
|
||||
ReactDOM.render(
|
||||
<Theme>
|
||||
@ -69,6 +111,20 @@ export function getPanelView(
|
||||
container
|
||||
);
|
||||
}
|
||||
if (panelType == 'sql') {
|
||||
ReactDOM.render(
|
||||
<Theme>
|
||||
<SQL
|
||||
treeNodeInfo={treeNodeInfo}
|
||||
pgBrowser={pgBrowser}
|
||||
nodeData={nodeData}
|
||||
node={node}
|
||||
item={item}
|
||||
/>
|
||||
</Theme>,
|
||||
container
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* When switching from normal node to collection node, clean up the React mounted DOM */
|
||||
|
67
web/pgadmin/dashboard/static/js/ActiveQuery.ui.js
Normal file
67
web/pgadmin/dashboard/static/js/ActiveQuery.ui.js
Normal file
@ -0,0 +1,67 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import gettext from 'sources/gettext';
|
||||
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
|
||||
|
||||
export default class ActiveQuery extends BaseUISchema {
|
||||
constructor(initValues) {
|
||||
super({
|
||||
...initValues,
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [
|
||||
|
||||
{
|
||||
id: 'backend_type',
|
||||
label: gettext('Backend type'),
|
||||
type: 'text',
|
||||
editable: true,
|
||||
noEmpty: true,
|
||||
readonly: true,
|
||||
mode: ['properties'],
|
||||
group: gettext('Details'),
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
id: 'query_start',
|
||||
label: gettext('Query started at'),
|
||||
type: 'text',
|
||||
editable: false,
|
||||
readonly: true,
|
||||
group: gettext('Details'),
|
||||
disabled: true
|
||||
|
||||
},
|
||||
{
|
||||
id: 'state_change',
|
||||
label: gettext('Last state changed at'),
|
||||
type: 'text',
|
||||
editable: false,
|
||||
readonly: true,
|
||||
group: gettext('Details'),
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
id: 'query',
|
||||
label: gettext('SQL'),
|
||||
cell: 'string',
|
||||
editable: false,
|
||||
readonly: true,
|
||||
type: 'sql',
|
||||
group: gettext('Details'),
|
||||
},
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
}
|
907
web/pgadmin/dashboard/static/js/Dashboard.jsx
Normal file
907
web/pgadmin/dashboard/static/js/Dashboard.jsx
Normal file
@ -0,0 +1,907 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
// eslint-disable-next-line react/display-name
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import gettext from 'sources/gettext';
|
||||
import PropTypes from 'prop-types';
|
||||
import getApiInstance from 'sources/api_instance';
|
||||
import PgTable from 'sources/components/PgTable';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import url_for from 'sources/url_for';
|
||||
import Graphs from './Graphs';
|
||||
import Notify from '../../../static/js/helpers/Notifier';
|
||||
import { Box, Tab, Tabs } from '@material-ui/core';
|
||||
import { PgIconButton } from '../../../static/js/components/Buttons';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import SquareIcon from '@mui/icons-material/Square';
|
||||
import ArrowRightOutlinedIcon from '@mui/icons-material/ArrowRightOutlined';
|
||||
import ArrowDropDownOutlinedIcon from '@mui/icons-material/ArrowDropDownOutlined';
|
||||
import WelcomeDashboard from './WelcomeDashboard';
|
||||
import ActiveQuery from './ActiveQuery.ui';
|
||||
import _ from 'lodash';
|
||||
|
||||
function parseData(data) {
|
||||
var res = [];
|
||||
|
||||
data.forEach((row) => {
|
||||
res.push({ ...row, icon: '' });
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
emptyPanel: {
|
||||
height: '100%',
|
||||
background: theme.palette.grey[400],
|
||||
overflow: 'auto',
|
||||
padding: '8px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
},
|
||||
fixedSizeList: {
|
||||
overflowX: 'hidden !important',
|
||||
overflow: 'overlay !important',
|
||||
height: 'auto !important',
|
||||
},
|
||||
dashboardPanel: {
|
||||
height: '100%',
|
||||
background: theme.palette.grey[400],
|
||||
},
|
||||
cardHeader: {
|
||||
padding: '0.25rem 0.5rem',
|
||||
fontWeight: 'bold',
|
||||
backgroundColor: theme.otherVars.tableBg,
|
||||
borderBottom: '1px solid',
|
||||
borderBottomColor: theme.otherVars.borderColor,
|
||||
},
|
||||
searchPadding: {
|
||||
display: 'flex',
|
||||
flex: 2.5,
|
||||
},
|
||||
component: {
|
||||
padding: '8px',
|
||||
},
|
||||
searchInput: {
|
||||
flex: 1,
|
||||
},
|
||||
panelIcon: {
|
||||
width: '80%',
|
||||
margin: '0 auto',
|
||||
marginTop: '25px !important',
|
||||
position: 'relative',
|
||||
textAlign: 'center',
|
||||
},
|
||||
panelMessage: {
|
||||
marginLeft: '0.5rem',
|
||||
fontSize: '0.875rem',
|
||||
},
|
||||
panelContent: {
|
||||
...theme.mixins.panelBorder,
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden !important',
|
||||
flexGrow: 1
|
||||
},
|
||||
terminateButton: {
|
||||
color: theme.palette.error.main
|
||||
},
|
||||
buttonClick: {
|
||||
backgroundColor: theme.palette.grey[400]
|
||||
}
|
||||
}));
|
||||
|
||||
/* eslint-disable react/display-name */
|
||||
export default function Dashboard({
|
||||
nodeData,
|
||||
node,
|
||||
item,
|
||||
pgBrowser,
|
||||
preferences,
|
||||
sid,
|
||||
did,
|
||||
treeNodeInfo,
|
||||
...props
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
let tab = ['Sessions', 'Locks', 'Prepared Transactions'];
|
||||
const [dashData, setdashData] = useState([]);
|
||||
const [msg, setMsg] = useState('');
|
||||
const[infoMsg, setInfo] = useState('');
|
||||
const [val, setVal] = useState(0);
|
||||
const [schemaDict, setSchemaDict] = React.useState({});
|
||||
|
||||
if (!did) {
|
||||
tab.push('Configuration');
|
||||
}
|
||||
val == 3 && did && setVal(0);
|
||||
const tabChanged = (e, tabVal) => {
|
||||
setVal(tabVal);
|
||||
};
|
||||
|
||||
const serverConfigColumns = [
|
||||
{
|
||||
accessor: 'name',
|
||||
Header: gettext('Name'),
|
||||
sortble: true,
|
||||
resizable: true,
|
||||
disableGlobalFilter: false,
|
||||
minWidth: 50,
|
||||
maxWidth: 110,
|
||||
},
|
||||
{
|
||||
accessor: 'category',
|
||||
Header: gettext('Category'),
|
||||
sortble: true,
|
||||
resizable: true,
|
||||
disableGlobalFilter: false,
|
||||
minWidth: 50,
|
||||
maxWidth: 150,
|
||||
},
|
||||
{
|
||||
accessor: 'setting',
|
||||
Header: gettext('Setting'),
|
||||
sortble: true,
|
||||
resizable: true,
|
||||
disableGlobalFilter: false,
|
||||
minWidth: 50,
|
||||
maxWidth: 50,
|
||||
},
|
||||
{
|
||||
accessor: 'unit',
|
||||
Header: gettext('Unit'),
|
||||
sortble: true,
|
||||
resizable: true,
|
||||
disableGlobalFilter: false,
|
||||
minWidth: 26,
|
||||
maxWidth: 30,
|
||||
},
|
||||
{
|
||||
accessor: 'short_desc',
|
||||
Header: gettext('Description'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
},
|
||||
];
|
||||
|
||||
const activityColumns = [
|
||||
{
|
||||
accessor: 'terminate_query',
|
||||
Header: () => null,
|
||||
sortble: true,
|
||||
resizable: true,
|
||||
disableGlobalFilter: false,
|
||||
minWidth: 16,
|
||||
maxWidth: 30,
|
||||
id: 'btn-terminate',
|
||||
// eslint-disable-next-line react/display-name
|
||||
Cell: ({ row }) => {
|
||||
var terminate_session_url =
|
||||
url_for('dashboard.index') + 'terminate_session' + '/' + sid,
|
||||
title = gettext('Terminate Session?'),
|
||||
txtConfirm = gettext(
|
||||
'Are you sure you wish to terminate the session?'
|
||||
),
|
||||
txtSuccess = gettext('Session terminated successfully.'),
|
||||
txtError = gettext(
|
||||
'An error occurred whilst terminating the active query.'
|
||||
);
|
||||
const action_url = did
|
||||
? terminate_session_url + '/' + did
|
||||
: terminate_session_url;
|
||||
|
||||
const api = getApiInstance();
|
||||
|
||||
return (
|
||||
<PgIconButton
|
||||
size="xs"
|
||||
noBorder
|
||||
icon={<CancelIcon />}
|
||||
className={classes.terminateButton}
|
||||
onClick={() => {
|
||||
if (
|
||||
!canTakeAction(row, 'terminate')
|
||||
)
|
||||
return;
|
||||
let url = action_url + '/' + row.values.pid;
|
||||
Notify.confirm(
|
||||
title,
|
||||
txtConfirm,
|
||||
function () {
|
||||
api
|
||||
.delete(url)
|
||||
.then(function (res) {
|
||||
if (res.data == gettext('Success')) {
|
||||
setInfo(txtSuccess);
|
||||
Notify.success(txtSuccess);
|
||||
} else {
|
||||
setInfo(txtError);
|
||||
Notify.error(txtError);
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
Notify.alert(
|
||||
gettext('Failed to retrieve data from the server.'),
|
||||
gettext(error.message)
|
||||
);
|
||||
});
|
||||
},
|
||||
function () {
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}}
|
||||
color="default"
|
||||
aria-label="Terminate Session?"
|
||||
title={gettext('Terminate Session?')}
|
||||
></PgIconButton>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessor: 'cancel_Query',
|
||||
Header: () => null,
|
||||
sortble: true,
|
||||
resizable: true,
|
||||
disableGlobalFilter: false,
|
||||
minWidth: 16,
|
||||
maxWidth: 30,
|
||||
id: 'btn-cancel',
|
||||
Cell: ({ row }) => {
|
||||
var cancel_query_url =
|
||||
url_for('dashboard.index') + 'cancel_query' + '/' + sid,
|
||||
title = gettext('Cancel Active Query?'),
|
||||
txtConfirm = gettext(
|
||||
'Are you sure you wish to cancel the active query?'
|
||||
),
|
||||
txtSuccess = gettext('Active query cancelled successfully.'),
|
||||
txtError = gettext(
|
||||
'An error occurred whilst cancelling the active query.'
|
||||
);
|
||||
|
||||
const action_url = did ? cancel_query_url + '/' + did : cancel_query_url;
|
||||
|
||||
const api = getApiInstance();
|
||||
|
||||
return (
|
||||
<PgIconButton
|
||||
size="xs"
|
||||
noBorder
|
||||
icon={<SquareIcon fontSize="small" />}
|
||||
onClick={() => {
|
||||
if (!canTakeAction(row, 'cancel'))
|
||||
return;
|
||||
let url = action_url + '/' + row.values.pid;
|
||||
Notify.confirm(
|
||||
title,
|
||||
txtConfirm,
|
||||
function () {
|
||||
api
|
||||
.delete(url)
|
||||
.then(function (res) {
|
||||
if (res.data == gettext('Success')) {
|
||||
setInfo(txtSuccess);
|
||||
Notify.success(txtSuccess);
|
||||
} else {
|
||||
setInfo(txtError);
|
||||
Notify.error(txtError);
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
Notify.alert(
|
||||
gettext('Failed to retrieve data from the server.'),
|
||||
gettext(error.message)
|
||||
);
|
||||
});
|
||||
},
|
||||
function () {
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}}
|
||||
color="default"
|
||||
aria-label="Cancel the query"
|
||||
title={gettext('Cancel the active query')}
|
||||
></PgIconButton>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessor: 'view_active_query',
|
||||
Header: () => null,
|
||||
sortble: true,
|
||||
resizable: true,
|
||||
disableGlobalFilter: false,
|
||||
minWidth: 16,
|
||||
maxWidth: 30,
|
||||
id: 'btn-edit',
|
||||
Cell: ({ row }) => {
|
||||
let canEditRow = true;
|
||||
return (
|
||||
<PgIconButton
|
||||
className={row.isExpanded ?classes.buttonClick : ''}
|
||||
icon={
|
||||
row.isExpanded ? (
|
||||
<ArrowDropDownOutlinedIcon />
|
||||
) : (
|
||||
<ArrowRightOutlinedIcon />
|
||||
)
|
||||
}
|
||||
size="xs"
|
||||
noBorder
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
row.toggleRowExpanded(!row.isExpanded);
|
||||
if(!(row.id in schemaDict)){
|
||||
let schema = new ActiveQuery({
|
||||
query: row.original.query,
|
||||
backend_type: row.original.backend_type,
|
||||
state_change: row.original.state_change,
|
||||
query_start: row.original.query_start,
|
||||
});
|
||||
setSchemaDict(prevState => ({
|
||||
...prevState,
|
||||
[row.id]: schema
|
||||
}));
|
||||
}
|
||||
}}
|
||||
disabled={!canEditRow}
|
||||
aria-label="View the active session details"
|
||||
title={gettext('View the active session details')}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessor: 'pid',
|
||||
Header: gettext('PID'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
minWidth: 26,
|
||||
maxWidth: 60,
|
||||
},
|
||||
{
|
||||
accessor: 'datname',
|
||||
Header: gettext('Database'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
minWidth: 26,
|
||||
maxWidth: 80,
|
||||
isVisible: !did ? true: false
|
||||
},
|
||||
{
|
||||
accessor: 'usename',
|
||||
Header: gettext('User'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
minWidth: 26,
|
||||
maxWidth: 80,
|
||||
},
|
||||
{
|
||||
accessor: 'application_name',
|
||||
Header: gettext('Application'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
minWidth: 26,
|
||||
maxWidth: 150,
|
||||
},
|
||||
{
|
||||
accessor: 'client_addr',
|
||||
Header: gettext('Client'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
minWidth: 26,
|
||||
maxWidth: 60,
|
||||
},
|
||||
{
|
||||
accessor: 'backend_start',
|
||||
Header: gettext('Backend start'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
minWidth: 26,
|
||||
maxWidth: 150,
|
||||
},
|
||||
{
|
||||
accessor: 'xact_start',
|
||||
Header: gettext('Transaction start'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
minWidth: 26,
|
||||
maxWidth: 150,
|
||||
},
|
||||
{
|
||||
accessor: 'state',
|
||||
Header: gettext('State'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
minWidth: 26,
|
||||
maxWidth: 60,
|
||||
},
|
||||
|
||||
{
|
||||
accessor: 'waiting',
|
||||
Header: gettext('Waiting'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
isVisible: treeNodeInfo?.server?.version < 90600
|
||||
},
|
||||
{
|
||||
accessor: 'wait_event',
|
||||
Header: gettext('Wait event'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
},
|
||||
{
|
||||
accessor: 'blocking_pids',
|
||||
Header: gettext('Blocking PIDs'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
},
|
||||
];
|
||||
|
||||
const databaseLocksColumns = [
|
||||
{
|
||||
accessor: 'pid',
|
||||
Header: gettext('PID'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
minWidth: 26,
|
||||
maxWidth: 50,
|
||||
},
|
||||
{
|
||||
accessor: 'datname',
|
||||
Header: gettext('Database'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
minWidth: 26,
|
||||
maxWidth: 80,
|
||||
isVisible: !did ? true: false
|
||||
},
|
||||
{
|
||||
accessor: 'locktype',
|
||||
Header: gettext('Lock type'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
minWidth: 26,
|
||||
maxWidth: 60,
|
||||
},
|
||||
{
|
||||
accessor: 'relation',
|
||||
Header: gettext('Target relation'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
},
|
||||
{
|
||||
accessor: 'page',
|
||||
Header: gettext('Page'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
minWidth: 26,
|
||||
maxWidth: 50,
|
||||
},
|
||||
{
|
||||
accessor: 'tuple',
|
||||
Header: gettext('Tuple'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
minWidth: 26,
|
||||
maxWidth: 50,
|
||||
},
|
||||
{
|
||||
accessor: 'virtualxid',
|
||||
Header: gettext('vXID (target)'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
minWidth: 100,
|
||||
maxWidth: 100,
|
||||
},
|
||||
{
|
||||
accessor: 'transactionid',
|
||||
Header: gettext('XID (target)'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
minWidth: 50,
|
||||
maxWidth: 100,
|
||||
},
|
||||
{
|
||||
accessor: 'classid',
|
||||
Header: gettext('Class'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
minWidth: 26,
|
||||
maxWidth: 50,
|
||||
},
|
||||
{
|
||||
accessor: 'objid',
|
||||
Header: gettext('Object ID'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
minWidth: 50,
|
||||
maxWidth: 100,
|
||||
},
|
||||
{
|
||||
accessor: 'virtualtransaction',
|
||||
Header: gettext('vXID (owner)'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
minWidth: 50,
|
||||
maxWidth: 80,
|
||||
},
|
||||
{
|
||||
accessor: 'mode',
|
||||
Header: gettext('Mode'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
},
|
||||
{
|
||||
id: 'granted',
|
||||
accessor: 'granted',
|
||||
Header: gettext('Granted?'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
minWidth: 50,
|
||||
maxWidth: 80,
|
||||
Cell: ({ value }) => String(value)
|
||||
},
|
||||
];
|
||||
|
||||
const databasePreparedColumns = [
|
||||
{
|
||||
accessor: 'git',
|
||||
Header: gettext('Name'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
},
|
||||
{
|
||||
accessor: 'datname',
|
||||
Header: gettext('Database'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
minWidth: 26,
|
||||
maxWidth: 80,
|
||||
isVisible: !did ? true: false
|
||||
},
|
||||
{
|
||||
accessor: 'Owner',
|
||||
Header: gettext('Owner'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
},
|
||||
{
|
||||
accessor: 'transaction',
|
||||
Header: gettext('XID'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
},
|
||||
{
|
||||
accessor: 'prepared',
|
||||
Header: gettext('Prepared at'),
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
},
|
||||
];
|
||||
|
||||
const canTakeAction = (row, cellAction) => {
|
||||
// We will validate if user is allowed to cancel the active query
|
||||
// If there is only one active session means it probably our main
|
||||
// connection session
|
||||
cellAction = cellAction || null;
|
||||
var pg_version = treeNodeInfo.server.version || null,
|
||||
is_cancel_session = cellAction === 'cancel',
|
||||
txtMessage,
|
||||
maintenance_database = treeNodeInfo.server.db,
|
||||
is_super_user,
|
||||
current_user;
|
||||
|
||||
var can_signal_backend =
|
||||
treeNodeInfo.server && treeNodeInfo.server.user
|
||||
? treeNodeInfo.server.user.can_signal_backend
|
||||
: false;
|
||||
|
||||
if (
|
||||
treeNodeInfo.server &&
|
||||
treeNodeInfo.server.user &&
|
||||
treeNodeInfo.server.user.is_superuser
|
||||
) {
|
||||
is_super_user = true;
|
||||
} else {
|
||||
is_super_user = false;
|
||||
current_user =
|
||||
treeNodeInfo.server && treeNodeInfo.server.user
|
||||
? treeNodeInfo.server.user.name
|
||||
: null;
|
||||
}
|
||||
|
||||
// With PG10, We have background process showing on dashboard
|
||||
// We will not allow user to cancel them as they will fail with error
|
||||
// anyway, so better usability we will throw our on notification
|
||||
|
||||
// Background processes do not have database field populated
|
||||
if (pg_version && pg_version >= 100000 && !row.original.datname) {
|
||||
if (is_cancel_session) {
|
||||
txtMessage = gettext('You cannot cancel background worker processes.');
|
||||
} else {
|
||||
txtMessage = gettext(
|
||||
'You cannot terminate background worker processes.'
|
||||
);
|
||||
}
|
||||
Notify.info(txtMessage);
|
||||
return false;
|
||||
// If it is the last active connection on maintenance db then error out
|
||||
} else if (
|
||||
maintenance_database == row.original.datname &&
|
||||
row.original.state == 'active'
|
||||
) {
|
||||
if (is_cancel_session) {
|
||||
txtMessage = gettext(
|
||||
'You are not allowed to cancel the main active session.'
|
||||
);
|
||||
} else {
|
||||
txtMessage = gettext(
|
||||
'You are not allowed to terminate the main active session.'
|
||||
);
|
||||
}
|
||||
Notify.error(txtMessage);
|
||||
return false;
|
||||
} else if (is_cancel_session && row.original.state == 'idle') {
|
||||
// If this session is already idle then do nothing
|
||||
Notify.info(gettext('The session is already in idle state.'));
|
||||
return false;
|
||||
} else if (can_signal_backend) {
|
||||
// user with membership of 'pg_signal_backend' can terminate the session of non admin user.
|
||||
return true;
|
||||
} else if (is_super_user) {
|
||||
// Super user can do anything
|
||||
return true;
|
||||
} else if (current_user && current_user == treeNodeInfo.server.user) {
|
||||
// Non-super user can cancel only their active queries
|
||||
return true;
|
||||
} else {
|
||||
// Do not allow to cancel someone else session to non-super user
|
||||
if (is_cancel_session) {
|
||||
txtMessage = gettext(
|
||||
'Superuser privileges are required to cancel another users query.'
|
||||
);
|
||||
} else {
|
||||
txtMessage = gettext(
|
||||
'Superuser privileges are required to terminate another users query.'
|
||||
);
|
||||
}
|
||||
Notify.error(txtMessage);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let url,
|
||||
message = gettext(
|
||||
'Please connect to the selected server to view the dashboard.'
|
||||
);
|
||||
|
||||
if (sid && props.serverConnected) {
|
||||
|
||||
if (val === 0) {
|
||||
url = url_for('dashboard.activity');
|
||||
} else if (val === 1) {
|
||||
url = url_for('dashboard.locks');
|
||||
} else if (val === 2) {
|
||||
url = url_for('dashboard.prepared');
|
||||
} else {
|
||||
url = url_for('dashboard.config');
|
||||
}
|
||||
|
||||
message = gettext('Loading dashboard...');
|
||||
if (did) url += sid + '/' + did;
|
||||
else url += sid;
|
||||
|
||||
const api = getApiInstance();
|
||||
if (node) {
|
||||
api({
|
||||
url: url,
|
||||
type: 'GET',
|
||||
})
|
||||
.then((res) => {
|
||||
setdashData(parseData(res.data));
|
||||
})
|
||||
.catch((e) => {
|
||||
Notify.alert(
|
||||
gettext('Failed to retrieve data from the server.'),
|
||||
gettext(e.message)
|
||||
);
|
||||
// show failed message.
|
||||
setMsg(gettext('Failed to retrieve data from the server.'));
|
||||
});
|
||||
} else {
|
||||
setMsg(message);
|
||||
}
|
||||
}
|
||||
if (message != '') {
|
||||
setMsg(message);
|
||||
}
|
||||
}, [nodeData, val, did, preferences, infoMsg]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{sid && props.serverConnected ? (
|
||||
<Box className={classes.dashboardPanel}>
|
||||
<Box className={classes.emptyPanel}>
|
||||
{!_.isUndefined(preferences) && preferences.show_graphs && (
|
||||
<Graphs
|
||||
preferences={preferences}
|
||||
sid={sid}
|
||||
did={did}
|
||||
pageVisible={true}
|
||||
></Graphs>
|
||||
)}
|
||||
<Box className={classes.panelContent}>
|
||||
<Box
|
||||
className={classes.cardHeader}
|
||||
title={gettext('Server activity')}
|
||||
>
|
||||
{gettext('Server activity')}{' '}
|
||||
</Box>
|
||||
<Tabs
|
||||
value={val}
|
||||
onChange={tabChanged}
|
||||
className={classes.searchInput}
|
||||
>
|
||||
{tab.map((tabValue, i) => {
|
||||
return <Tab key={i} label={tabValue} />;
|
||||
})}
|
||||
</Tabs>
|
||||
|
||||
<PgTable
|
||||
columns={
|
||||
val === 0
|
||||
? activityColumns
|
||||
: val === 1
|
||||
? databaseLocksColumns
|
||||
: val == 2
|
||||
? databasePreparedColumns
|
||||
: serverConfigColumns
|
||||
}
|
||||
data={dashData}
|
||||
schema={schemaDict}
|
||||
offset={145}
|
||||
></PgTable>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
) : sid && !props.serverConnected ? (
|
||||
<Box className={classes.dashboardPanel}>
|
||||
<div className={classes.emptyPanel}>
|
||||
<div className={classes.panelIcon}>
|
||||
<i className="fa fa-exclamation-circle"></i>
|
||||
<span className={classes.panelMessage}>{gettext(msg)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
) : (
|
||||
<WelcomeDashboard
|
||||
pgBrowser={pgBrowser}
|
||||
node={node}
|
||||
itemData={nodeData}
|
||||
item={item}
|
||||
sid={sid}
|
||||
did={did}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Dashboard.propTypes = {
|
||||
node: PropTypes.func,
|
||||
itemData: PropTypes.object,
|
||||
nodeData: PropTypes.object,
|
||||
treeNodeInfo: PropTypes.object,
|
||||
item: PropTypes.object,
|
||||
pgBrowser: PropTypes.object,
|
||||
preferences: PropTypes.object,
|
||||
sid: PropTypes.string,
|
||||
did: PropTypes.oneOfType([PropTypes.bool, PropTypes.number]),
|
||||
row: PropTypes.object,
|
||||
serverConnected: PropTypes.bool,
|
||||
};
|
||||
|
||||
export function ChartContainer(props) {
|
||||
return (
|
||||
<div
|
||||
className="card dashboard-graph"
|
||||
role="object-document"
|
||||
tabIndex="0"
|
||||
aria-labelledby={props.id}
|
||||
>
|
||||
<div className="card-header">
|
||||
<div className="d-flex">
|
||||
<div id={props.id}>{props.title}</div>
|
||||
<div className="ml-auto my-auto legend" ref={props.legendRef}></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-body dashboard-graph-body">
|
||||
<div className={'chart-wrapper ' + (props.errorMsg ? 'd-none' : '')}>
|
||||
{props.children}
|
||||
</div>
|
||||
<ChartError message={props.errorMsg} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ChartContainer.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
legendRef: PropTypes.oneOfType([
|
||||
PropTypes.func,
|
||||
PropTypes.shape({ current: PropTypes.any }),
|
||||
]).isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
errorMsg: PropTypes.string,
|
||||
};
|
||||
|
||||
export function ChartError(props) {
|
||||
if (props.message === null) {
|
||||
return <></>;
|
||||
}
|
||||
return (
|
||||
<div className="pg-panel-error pg-panel-message" role="alert">
|
||||
{props.message}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ChartError.propTypes = {
|
||||
message: PropTypes.string,
|
||||
};
|
||||
|
||||
export function DashboardRow({ children }) {
|
||||
return <div className="row dashboard-row">{children}</div>;
|
||||
}
|
||||
DashboardRow.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
export function DashboardRowCol({ breakpoint, parts, children }) {
|
||||
return <div className={`col-${breakpoint}-${parts}`}>{children}</div>;
|
||||
}
|
||||
|
||||
DashboardRowCol.propTypes = {
|
||||
breakpoint: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl']).isRequired,
|
||||
parts: PropTypes.number.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
@ -8,7 +8,7 @@
|
||||
//////////////////////////////////////////////////////////////
|
||||
import React, { useEffect, useRef, useState, useReducer, useCallback, useMemo } from 'react';
|
||||
import {LineChart} from 'sources/chartjs';
|
||||
import {ChartContainer, DashboardRowCol, DashboardRow} from './dashboard_components';
|
||||
import {ChartContainer, DashboardRowCol, DashboardRow} from './Dashboard';
|
||||
import url_for from 'sources/url_for';
|
||||
import axios from 'axios';
|
||||
import gettext from 'sources/gettext';
|
||||
|
219
web/pgadmin/dashboard/static/js/PgAdminLogo.jsx
Normal file
219
web/pgadmin/dashboard/static/js/PgAdminLogo.jsx
Normal file
@ -0,0 +1,219 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
import React from 'react';
|
||||
|
||||
export default function PgAdminLogo() {
|
||||
|
||||
return (
|
||||
<div className="welcome-logo" aria-hidden="true">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 130">
|
||||
<defs>
|
||||
<style>{'.cls-1{stroke:#000;stroke-width:10.19px;}.cls-2{fill:#336791;}.cls-3,.cls-4,.cls-9{fill:none;}.cls-3,.cls-4,.cls-5,.cls-6{stroke:#fff;}.cls-3,.cls-4{stroke-linecap:round;stroke-width:3.4px;}.cls-3{stroke-linejoin:round;}.cls-4{stroke-linejoin:bevel;}.cls-5,.cls-6{fill:#fff;}.cls-5{stroke-width:1.13px;}.cls-6{stroke-width:0.57px;}.cls-7{fill:#2775b6;}.cls-8{fill:#333;}.cls-9{stroke:#333;stroke-width:3px;}'}</style>
|
||||
</defs>
|
||||
<title>pgAdmin_PostgreSQL</title>
|
||||
<g id="Layer_1" data-name="Layer 1">
|
||||
<g id="Layer_3">
|
||||
<path
|
||||
className="cls-1"
|
||||
d="M95.59,93.65c.77-6.44.54-7.38,5.33-6.34l1.21.11a27.6,27.6,0,0,0,11.34-1.91c6.09-2.83,9.71-7.55,3.7-6.31-13.71,2.83-14.65-1.81-14.65-1.81C117,55.91,123,28.64,117.82,22,103.57,3.76,78.91,12.37,78.5,12.6l-.13,0a48.65,48.65,0,0,0-9.15-.95C63,11.57,58.31,13.29,54.74,16c0,0-44-18.12-41.95,22.8.44,8.7,12.48,65.86,26.84,48.6C44.88,81.08,50,75.75,50,75.75A13.39,13.39,0,0,0,58.65,78l.25-.21a9,9,0,0,0,.1,2.46c-3.7,4.13-2.62,4.86-10,6.38s-3.09,4.29-.22,5c3.48.87,11.53,2.1,17-5.52l-.22.87c1.46,1.16,1.36,8.35,1.56,13.48s.55,9.93,1.6,12.75,2.28,10.1,12,8C88.81,119.46,95,117,95.59,93.65"
|
||||
/>
|
||||
<path
|
||||
className="cls-2"
|
||||
d="M117.17,79.2c-13.71,2.83-14.65-1.81-14.65-1.81C117,55.91,123,28.64,117.82,22,103.57,3.76,78.91,12.37,78.5,12.6l-.13,0a48.65,48.65,0,0,0-9.15-.95C63,11.57,58.31,13.29,54.74,16c0,0-44-18.12-41.95,22.8.44,8.7,12.48,65.86,26.84,48.6C44.88,81.08,50,75.75,50,75.75A13.39,13.39,0,0,0,58.65,78l.25-.21A9.41,9.41,0,0,0,59,80.22c-3.7,4.13-2.61,4.86-10,6.38s-3.08,4.29-.21,5c3.48.87,11.53,2.1,17-5.52l-.22.87c1.45,1.16,2.47,7.56,2.3,13.35s-.28,9.77.86,12.88,2.28,10.1,12,8C88.81,119.46,93,115,93.6,107.42,94,102.07,95,102.87,95,98.08l.75-2.26c.87-7.26.14-9.6,5.15-8.51l1.21.11a27.6,27.6,0,0,0,11.34-1.91c6.09-2.83,9.71-7.55,3.7-6.31Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-3"
|
||||
d="M66.33,83.36c-.38,13.5.09,27.09,1.41,30.39s4.15,9.73,13.88,7.64c8.12-1.74,11.08-5.11,12.36-12.55.94-5.47,2.77-20.67,3-23.79"
|
||||
/>
|
||||
<path
|
||||
className="cls-3"
|
||||
d="M54.67,15.7s-44-18-42,22.93c.44,8.7,12.48,65.87,26.84,48.6,5.25-6.32,10-11.27,10-11.27"
|
||||
/>
|
||||
<path
|
||||
className="cls-3"
|
||||
d="M78.45,12.42c-1.52.47,24.49-9.51,39.28,9.38,5.22,6.67-.83,33.94-15.31,55.42"
|
||||
/>
|
||||
<path
|
||||
className="cls-4"
|
||||
d="M102.42,77.22s.94,4.64,14.65,1.81c6-1.24,2.4,3.48-3.7,6.31-5,2.32-16.21,2.92-16.39-.29-.47-8.27,5.9-5.76,5.44-7.83-.42-1.87-3.26-3.7-5.15-8.27-1.64-4-22.57-34.58,5.8-30,1-.22-7.4-27-33.95-27.42S43.45,44.14,43.45,44.14"
|
||||
/>
|
||||
<path
|
||||
className="cls-3"
|
||||
d="M58.9,80.05c-3.7,4.13-2.61,4.86-10,6.38s-3.09,4.29-.22,5c3.48.87,11.53,2.1,17-5.52,1.66-2.32,0-6-2.28-7-1.1-.46-2.57-1-4.46,1.09Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-3"
|
||||
d="M58.66,80c-.38-2.44.79-5.33,2.05-8.71C62.6,66.19,67,61.11,63.47,45c-2.6-12-20-2.5-20-.87a81.48,81.48,0,0,1-.29,16c-1.41,10.06,6.4,18.57,15.39,17.7"
|
||||
/>
|
||||
<path
|
||||
className="cls-5"
|
||||
d="M54.51,43.9c-.08.55,1,2,2.45,2.23a2.62,2.62,0,0,0,2.72-1.51c.08-.56-1-1.17-2.44-1.37s-2.65.09-2.73.65Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-6"
|
||||
d="M98,42.76c.07.56-1,2-2.45,2.24a2.64,2.64,0,0,1-2.73-1.52c-.07-.55,1-1.16,2.45-1.36s2.65.09,2.73.64Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-3"
|
||||
d="M103.07,38.92c.24,4.36-.94,7.33-1.08,12-.22,6.74,3.21,14.46-2,22.19"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
className="cls-7 app-name"
|
||||
d="M154.72,28.15h5.16v4.16A12.84,12.84,0,0,1,163.35,29a11.17,11.17,0,0,1,6.28-1.76,11.84,11.84,0,0,1,9.08,4.09c2.48,2.72,3.73,6.62,3.73,11.67q0,10.26-5.38,14.65a12.2,12.2,0,0,1-7.95,2.79,10.78,10.78,0,0,1-6-1.56,13.55,13.55,0,0,1-3.14-3v16h-5.28Zm19.84,24.6Q177,49.65,177,43.5a17,17,0,0,0-1.09-6.44,7.51,7.51,0,0,0-7.53-5.19q-5.49,0-7.52,5.48a21.49,21.49,0,0,0-1.09,7.44A15.64,15.64,0,0,0,160.88,51a8,8,0,0,0,13.68,1.78Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-7 app-name"
|
||||
d="M206,29.26a14.6,14.6,0,0,1,3,3V28.3h4.86V56.83c0,4-.58,7.13-1.75,9.44q-3.27,6.38-12.35,6.38a15.07,15.07,0,0,1-8.5-2.27,8.86,8.86,0,0,1-3.85-7.1h5.36a6,6,0,0,0,1.52,3.25q1.77,1.75,5.59,1.76,6,0,7.9-4.28,1.1-2.52,1-9a10.39,10.39,0,0,1-3.8,3.57,13.56,13.56,0,0,1-14.75-2.45q-3.81-3.62-3.81-12,0-7.89,3.84-12.31a11.85,11.85,0,0,1,9.27-4.42A11.37,11.37,0,0,1,206,29.26Zm.64,5.66a7.61,7.61,0,0,0-6.09-2.81A7.52,7.52,0,0,0,193,37.32a20.56,20.56,0,0,0-1.08,7.3c0,3.53.72,6.22,2.14,8.07a6.93,6.93,0,0,0,5.76,2.77,8.09,8.09,0,0,0,8-5.13A16.72,16.72,0,0,0,209,43.56Q209,37.73,206.62,34.92Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-7 app-name"
|
||||
d="M235.16,16.34h6.58l15.62,43H251l-4.5-12.89H229.6l-4.67,12.89h-6Zm9.67,25.4-6.63-19-6.88,19Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-7 app-name"
|
||||
d="M279.16,29a14.3,14.3,0,0,1,3.18,3.08V16.2h5.07V59.38h-4.75V55a11.33,11.33,0,0,1-4.35,4.19,12.51,12.51,0,0,1-5.75,1.28,11.61,11.61,0,0,1-9-4.4q-3.82-4.41-3.83-11.74a20.35,20.35,0,0,1,3.49-11.88,11.41,11.41,0,0,1,10-5A11.15,11.15,0,0,1,279.16,29ZM267.39,52.5q2.13,3.39,6.82,3.39a7.17,7.17,0,0,0,6-3.14c1.56-2.1,2.35-5.12,2.35-9s-.81-6.9-2.42-8.81a7.56,7.56,0,0,0-6-2.85,7.88,7.88,0,0,0-6.43,3c-1.64,2-2.46,5-2.46,9A15.62,15.62,0,0,0,267.39,52.5Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-7 app-name"
|
||||
d="M295.29,28h5.21v4.46a17.4,17.4,0,0,1,3.4-3.37,10.24,10.24,0,0,1,5.92-1.79,9.34,9.34,0,0,1,6,1.85,9.61,9.61,0,0,1,2.34,3.1,11.37,11.37,0,0,1,4.13-3.73,11.52,11.52,0,0,1,5.33-1.22q6.33,0,8.62,4.57a15,15,0,0,1,1.23,6.62V59.38H332V37.58c0-2.09-.52-3.52-1.57-4.3a6.2,6.2,0,0,0-3.82-1.17,7.58,7.58,0,0,0-5.35,2.08c-1.49,1.38-2.24,3.7-2.24,6.94V59.38h-5.36V38.9a10.78,10.78,0,0,0-.76-4.66q-1.2-2.19-4.49-2.19A7.73,7.73,0,0,0,303,34.36c-1.63,1.54-2.45,4.34-2.45,8.38V59.38h-5.27Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-7 app-name"
|
||||
d="M345.27,16.34h5.36v6h-5.36Zm0,11.81h5.36V59.38h-5.36Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-7 app-name"
|
||||
d="M358.6,28h5v4.46a14,14,0,0,1,4.72-4,12.56,12.56,0,0,1,5.53-1.2c4.46,0,7.46,1.55,9,4.66a16.52,16.52,0,0,1,1.29,7.29V59.38h-5.37V39.61A10.8,10.8,0,0,0,378,35a5.15,5.15,0,0,0-5.1-2.93,10.21,10.21,0,0,0-3.08.38A8,8,0,0,0,366,35a7.66,7.66,0,0,0-1.71,3.2,21.84,21.84,0,0,0-.4,4.74V59.38H358.6Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M155.24,86.87h3.9l5.77,17,5.74-17h3.87V107h-2.6V95.1q0-.61,0-2c0-.94,0-2,0-3L166.24,107h-2.7L157.75,90v.61c0,.49,0,1.24,0,2.25s.05,1.75.05,2.22V107h-2.6Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M186.15,98.09a1.35,1.35,0,0,0,1.14-.71,2.31,2.31,0,0,0,.16-.94,2,2,0,0,0-.89-1.84A4.79,4.79,0,0,0,184,94a3.21,3.21,0,0,0-2.73,1,3.44,3.44,0,0,0-.59,1.72h-2.3A4.28,4.28,0,0,1,180.14,93,7.16,7.16,0,0,1,184.05,92a8,8,0,0,1,4.19,1,3.34,3.34,0,0,1,1.6,3.06v8.44a1.06,1.06,0,0,0,.16.62.77.77,0,0,0,.66.23l.37,0,.44-.07V107a7.38,7.38,0,0,1-.88.21,5.92,5.92,0,0,1-.82,0,2,2,0,0,1-1.84-.9,3.63,3.63,0,0,1-.43-1.36,6.16,6.16,0,0,1-2.16,1.71,6.56,6.56,0,0,1-3.1.73,4.59,4.59,0,0,1-3.33-1.24,4.09,4.09,0,0,1-1.29-3.09,4,4,0,0,1,1.27-3.16,6.16,6.16,0,0,1,3.34-1.38ZM181,104.74a2.88,2.88,0,0,0,1.84.62,5.51,5.51,0,0,0,2.52-.61,3.37,3.37,0,0,0,2-3.26v-2a3.79,3.79,0,0,1-1.16.48,10.37,10.37,0,0,1-1.39.28l-1.49.19a5.68,5.68,0,0,0-2,.56,2.18,2.18,0,0,0-1.14,2A2,2,0,0,0,181,104.74Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M193.88,92.31h2.33v2.08a6.73,6.73,0,0,1,2.2-1.85A6,6,0,0,1,201,92q3.12,0,4.21,2.18a7.73,7.73,0,0,1,.6,3.4V107h-2.5V97.73a4.87,4.87,0,0,0-.4-2.16,2.41,2.41,0,0,0-2.38-1.37,4.75,4.75,0,0,0-1.43.18,3.68,3.68,0,0,0-1.78,1.2,3.55,3.55,0,0,0-.8,1.5,10.3,10.3,0,0,0-.18,2.21V107h-2.46Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M217.29,98.09a1.33,1.33,0,0,0,1.14-.71,2.15,2.15,0,0,0,.16-.94,2,2,0,0,0-.89-1.84,4.79,4.79,0,0,0-2.56-.56,3.24,3.24,0,0,0-2.73,1,3.44,3.44,0,0,0-.58,1.72h-2.3A4.27,4.27,0,0,1,211.28,93,7.19,7.19,0,0,1,215.2,92a8,8,0,0,1,4.19,1A3.36,3.36,0,0,1,221,96v8.44a1.14,1.14,0,0,0,.15.62.79.79,0,0,0,.67.23l.37,0,.43-.07V107a7.32,7.32,0,0,1-.87.21,6,6,0,0,1-.82,0,2,2,0,0,1-1.85-.9,3.46,3.46,0,0,1-.42-1.36,6.16,6.16,0,0,1-2.16,1.71,6.63,6.63,0,0,1-3.11.73,4.58,4.58,0,0,1-3.32-1.24,4.06,4.06,0,0,1-1.3-3.09A4,4,0,0,1,210,100a6.13,6.13,0,0,1,3.33-1.38Zm-5.18,6.65a2.91,2.91,0,0,0,1.85.62,5.47,5.47,0,0,0,2.51-.61,3.38,3.38,0,0,0,2.06-3.26v-2a3.9,3.9,0,0,1-1.16.48,10.51,10.51,0,0,1-1.4.28l-1.48.19a5.55,5.55,0,0,0-2,.56,2.17,2.17,0,0,0-1.15,2A2,2,0,0,0,212.11,104.74Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M233.16,92.9a7.05,7.05,0,0,1,1.42,1.39V92.45h2.27v13.32a10,10,0,0,1-.82,4.4c-1,2-2.94,3-5.77,3a7.09,7.09,0,0,1-4-1.06,4.15,4.15,0,0,1-1.8-3.32H227a2.81,2.81,0,0,0,.71,1.52,3.57,3.57,0,0,0,2.61.82c1.88,0,3.1-.66,3.68-2a11.15,11.15,0,0,0,.48-4.2,4.84,4.84,0,0,1-1.77,1.67,5.93,5.93,0,0,1-2.74.54,5.79,5.79,0,0,1-4.14-1.69q-1.79-1.68-1.78-5.58a8.49,8.49,0,0,1,1.79-5.74,5.51,5.51,0,0,1,4.32-2.07A5.33,5.33,0,0,1,233.16,92.9Zm.3,2.64a3.77,3.77,0,0,0-6.38,1.12,9.73,9.73,0,0,0-.5,3.4,6.05,6.05,0,0,0,1,3.77,3.21,3.21,0,0,0,2.68,1.29,3.77,3.77,0,0,0,3.72-2.39,7.71,7.71,0,0,0,.6-3.16A6.13,6.13,0,0,0,233.46,95.54Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M249.64,92.72a5.44,5.44,0,0,1,2.21,1.89,6.52,6.52,0,0,1,1,2.58,17.44,17.44,0,0,1,.22,3.23H242.4a6.34,6.34,0,0,0,1,3.59,3.49,3.49,0,0,0,3,1.35,3.9,3.9,0,0,0,3.05-1.28,4.5,4.5,0,0,0,.9-1.72h2.42a5,5,0,0,1-.63,1.8,6.58,6.58,0,0,1-1.21,1.62,5.71,5.71,0,0,1-2.75,1.48,8.71,8.71,0,0,1-2,.21,6.11,6.11,0,0,1-4.6-2,7.79,7.79,0,0,1-1.89-5.58,8.41,8.41,0,0,1,1.9-5.72,6.26,6.26,0,0,1,5-2.21A6.59,6.59,0,0,1,249.64,92.72Zm.88,5.74a6.46,6.46,0,0,0-.69-2.55,3.54,3.54,0,0,0-3.35-1.78,3.72,3.72,0,0,0-2.82,1.22,4.69,4.69,0,0,0-1.21,3.11Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M256.16,92.31h2.44v2.08a8.23,8.23,0,0,1,1.58-1.57A4.79,4.79,0,0,1,263,92a4.31,4.31,0,0,1,2.81.87,4.5,4.5,0,0,1,1.1,1.44,5.27,5.27,0,0,1,1.92-1.74,5.37,5.37,0,0,1,2.49-.57,4.08,4.08,0,0,1,4,2.14,7,7,0,0,1,.58,3.09V107h-2.56V96.78a2.41,2.41,0,0,0-.73-2,2.93,2.93,0,0,0-1.79-.54,3.53,3.53,0,0,0-2.49,1,4.23,4.23,0,0,0-1.05,3.24V107h-2.5V97.4a5,5,0,0,0-.36-2.18,2.17,2.17,0,0,0-2.09-1,3.59,3.59,0,0,0-2.53,1.08c-.76.72-1.14,2-1.14,3.91V107h-2.47Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M288.53,92.72a5.47,5.47,0,0,1,2.22,1.89,6.67,6.67,0,0,1,1,2.58,17.66,17.66,0,0,1,.21,3.23H281.29a6.42,6.42,0,0,0,1,3.59,3.48,3.48,0,0,0,3,1.35,3.9,3.9,0,0,0,3.06-1.28,4.5,4.5,0,0,0,.9-1.72h2.42a5.23,5.23,0,0,1-.64,1.8,6.56,6.56,0,0,1-1.2,1.62,5.7,5.7,0,0,1-2.76,1.48,8.62,8.62,0,0,1-2,.21,6.14,6.14,0,0,1-4.61-2,7.79,7.79,0,0,1-1.89-5.58,8.41,8.41,0,0,1,1.91-5.72,6.24,6.24,0,0,1,5-2.21A6.58,6.58,0,0,1,288.53,92.72Zm.88,5.74a6.46,6.46,0,0,0-.69-2.55,3.53,3.53,0,0,0-3.35-1.78,3.72,3.72,0,0,0-2.82,1.22,4.63,4.63,0,0,0-1.2,3.11Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M295.06,92.31h2.34v2.08a6.63,6.63,0,0,1,2.2-1.85,5.91,5.91,0,0,1,2.58-.56c2.08,0,3.49.73,4.21,2.18a7.57,7.57,0,0,1,.61,3.4V107h-2.51V97.73a5,5,0,0,0-.39-2.16,2.41,2.41,0,0,0-2.38-1.37,4.86,4.86,0,0,0-1.44.18,3.7,3.7,0,0,0-1.77,1.2,3.55,3.55,0,0,0-.8,1.5,9.58,9.58,0,0,0-.19,2.21V107h-2.46Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M311.13,88.22h2.48v4.09H316v2h-2.34v9.56a1,1,0,0,0,.52,1,2.21,2.21,0,0,0,1,.15h.38l.48,0v2a4.16,4.16,0,0,1-.88.18,7.74,7.74,0,0,1-1,.06,2.69,2.69,0,0,1-2.34-.88,3.94,3.94,0,0,1-.61-2.29v-9.7h-2v-2h2Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M340.63,86.87v2.39h-6.77V107h-2.75V89.26h-6.76V86.87Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M350.31,93.77a7.42,7.42,0,0,1,1.94,5.55,9.57,9.57,0,0,1-1.71,5.85,6.19,6.19,0,0,1-5.31,2.3,6,6,0,0,1-4.76-2A8.08,8.08,0,0,1,338.7,100a8.78,8.78,0,0,1,1.86-5.88,6.25,6.25,0,0,1,5-2.18A6.6,6.6,0,0,1,350.31,93.77Zm-1.53,9.74a9.32,9.32,0,0,0,.9-4.12,7.39,7.39,0,0,0-.65-3.33,3.63,3.63,0,0,0-3.54-2,3.49,3.49,0,0,0-3.25,1.72,8.07,8.07,0,0,0-1,4.15,7.05,7.05,0,0,0,1,3.89,3.56,3.56,0,0,0,3.22,1.56A3.35,3.35,0,0,0,348.78,103.51Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M365.88,93.77a7.42,7.42,0,0,1,1.94,5.55,9.57,9.57,0,0,1-1.71,5.85,6.19,6.19,0,0,1-5.31,2.3,6,6,0,0,1-4.76-2,8.08,8.08,0,0,1-1.77-5.48,8.78,8.78,0,0,1,1.86-5.88,6.25,6.25,0,0,1,5-2.18A6.58,6.58,0,0,1,365.88,93.77Zm-1.53,9.74a9.32,9.32,0,0,0,.9-4.12,7.26,7.26,0,0,0-.65-3.33,3.63,3.63,0,0,0-3.54-2,3.46,3.46,0,0,0-3.24,1.72,8,8,0,0,0-1,4.15,7,7,0,0,0,1,3.89,3.54,3.54,0,0,0,3.21,1.56A3.35,3.35,0,0,0,364.35,103.51Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M370.91,86.87h2.46V107h-2.46Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M378.53,102.36a3.47,3.47,0,0,0,.63,1.89,4,4,0,0,0,3.29,1.19,4.86,4.86,0,0,0,2.45-.6A2,2,0,0,0,386,103a1.56,1.56,0,0,0-.84-1.43,10.63,10.63,0,0,0-2.14-.7l-2-.49a10,10,0,0,1-2.81-1,3.11,3.11,0,0,1-1.61-2.76,4.21,4.21,0,0,1,1.52-3.37,6.13,6.13,0,0,1,4.08-1.28q3.36,0,4.83,1.94a4.24,4.24,0,0,1,.91,2.65h-2.33A2.72,2.72,0,0,0,385,95a3.92,3.92,0,0,0-3-1,3.7,3.7,0,0,0-2.16.53,1.65,1.65,0,0,0-.74,1.4,1.73,1.73,0,0,0,1,1.53,5.69,5.69,0,0,0,1.64.6l1.66.4A12.73,12.73,0,0,1,387,99.75a3.3,3.3,0,0,1,1.44,3,4.48,4.48,0,0,1-1.5,3.37,6.45,6.45,0,0,1-4.58,1.43c-2.2,0-3.77-.5-4.68-1.49a5.59,5.59,0,0,1-1.48-3.67Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M400,87.84c.58-.84,1.68-1.26,3.32-1.26l.48,0,.56,0v2.24l-.56,0h-.32c-.76,0-1.21.19-1.36.58a11.75,11.75,0,0,0-.22,3h2.46v1.94h-2.46V107h-2.43V94.32h-2V92.38h2v-2.3A4.43,4.43,0,0,1,400,87.84Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M417.23,93.77a7.38,7.38,0,0,1,1.94,5.55,9.57,9.57,0,0,1-1.71,5.85,6.18,6.18,0,0,1-5.3,2.3,6,6,0,0,1-4.77-2,8.07,8.07,0,0,1-1.76-5.48,8.83,8.83,0,0,1,1.85-5.88,6.25,6.25,0,0,1,5-2.18A6.58,6.58,0,0,1,417.23,93.77Zm-1.53,9.74a9.32,9.32,0,0,0,.9-4.12,7.26,7.26,0,0,0-.65-3.33,3.63,3.63,0,0,0-3.54-2,3.47,3.47,0,0,0-3.24,1.72,8,8,0,0,0-1,4.15,7,7,0,0,0,1,3.89,3.55,3.55,0,0,0,3.22,1.56A3.35,3.35,0,0,0,415.7,103.51Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M422.26,92.31h2.34v2.53A5.61,5.61,0,0,1,426,93,3.68,3.68,0,0,1,428.59,92l.24,0,.56,0v2.6a2.08,2.08,0,0,0-.41-.05l-.4,0a3.52,3.52,0,0,0-2.86,1.2,4.2,4.2,0,0,0-1,2.75V107h-2.46Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M439.89,86.87h9a6.09,6.09,0,0,1,4.31,1.51,5.5,5.5,0,0,1,1.64,4.25,6.15,6.15,0,0,1-1.47,4.09,5.5,5.5,0,0,1-4.47,1.74h-6.27V107h-2.72Zm10.55,2.76a5.92,5.92,0,0,0-2.46-.42h-5.37v7H448a5.07,5.07,0,0,0,2.95-.78,3.1,3.1,0,0,0,1.14-2.75A3,3,0,0,0,450.44,89.63Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M468.58,93.77a7.38,7.38,0,0,1,1.95,5.55,9.51,9.51,0,0,1-1.72,5.85,6.17,6.17,0,0,1-5.3,2.3,6,6,0,0,1-4.77-2A8.07,8.07,0,0,1,457,100a8.78,8.78,0,0,1,1.86-5.88,6.23,6.23,0,0,1,5-2.18A6.56,6.56,0,0,1,468.58,93.77Zm-1.52,9.74a9.32,9.32,0,0,0,.9-4.12,7.39,7.39,0,0,0-.65-3.33,3.65,3.65,0,0,0-3.55-2,3.48,3.48,0,0,0-3.24,1.72,8,8,0,0,0-1,4.15,7,7,0,0,0,1,3.89,3.56,3.56,0,0,0,3.22,1.56A3.36,3.36,0,0,0,467.06,103.51Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M475,102.36a3.47,3.47,0,0,0,.63,1.89,4,4,0,0,0,3.29,1.19,4.93,4.93,0,0,0,2.46-.6,2,2,0,0,0,1.06-1.84,1.55,1.55,0,0,0-.85-1.43,10.4,10.4,0,0,0-2.14-.7l-2-.49a9.78,9.78,0,0,1-2.8-1,3.1,3.1,0,0,1-1.62-2.76,4.21,4.21,0,0,1,1.52-3.37,6.13,6.13,0,0,1,4.08-1.28q3.36,0,4.84,1.94a4.17,4.17,0,0,1,.9,2.65h-2.33a2.72,2.72,0,0,0-.6-1.51,3.92,3.92,0,0,0-3-1,3.7,3.7,0,0,0-2.16.53,1.64,1.64,0,0,0-.73,1.4,1.72,1.72,0,0,0,1,1.53,5.66,5.66,0,0,0,1.65.6l1.65.4a12.83,12.83,0,0,1,3.63,1.24,3.31,3.31,0,0,1,1.43,3,4.48,4.48,0,0,1-1.5,3.37,6.45,6.45,0,0,1-4.58,1.43q-3.3,0-4.68-1.49a5.59,5.59,0,0,1-1.48-3.67Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M488,88.22h2.49v4.09h2.34v2h-2.34v9.56a1,1,0,0,0,.52,1,2.19,2.19,0,0,0,.95.15h.39l.48,0v2a4.39,4.39,0,0,1-.89.18,7.52,7.52,0,0,1-1,.06,2.69,2.69,0,0,1-2.34-.88A3.94,3.94,0,0,1,488,104v-9.7h-2v-2h2Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M503.47,92.9a6.78,6.78,0,0,1,1.41,1.39V92.45h2.27v13.32a10,10,0,0,1-.81,4.4c-1,2-2.94,3-5.77,3a7.09,7.09,0,0,1-4-1.06,4.1,4.1,0,0,1-1.8-3.32h2.5a2.74,2.74,0,0,0,.71,1.52,3.57,3.57,0,0,0,2.61.82c1.87,0,3.1-.66,3.68-2a11.37,11.37,0,0,0,.48-4.2,4.84,4.84,0,0,1-1.77,1.67,6,6,0,0,1-2.74.54,5.83,5.83,0,0,1-4.15-1.69q-1.77-1.68-1.77-5.58a8.43,8.43,0,0,1,1.79-5.74,5.51,5.51,0,0,1,4.32-2.07A5.38,5.38,0,0,1,503.47,92.9Zm.3,2.64a3.56,3.56,0,0,0-2.85-1.31,3.5,3.5,0,0,0-3.53,2.43,9.48,9.48,0,0,0-.51,3.4,6.12,6.12,0,0,0,1,3.77,3.24,3.24,0,0,0,2.69,1.29,3.75,3.75,0,0,0,3.71-2.39,7.71,7.71,0,0,0,.6-3.16A6.14,6.14,0,0,0,503.77,95.54Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M511,92.31h2.33v2.53a5.91,5.91,0,0,1,1.41-1.8A3.69,3.69,0,0,1,517.3,92l.23,0,.56,0v2.6a2.08,2.08,0,0,0-.4-.05l-.41,0a3.48,3.48,0,0,0-2.85,1.2,4.14,4.14,0,0,0-1,2.75V107H511Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M529.27,92.72a5.44,5.44,0,0,1,2.21,1.89,6.52,6.52,0,0,1,1,2.58,16.6,16.6,0,0,1,.22,3.23H522a6.34,6.34,0,0,0,1,3.59,3.49,3.49,0,0,0,3,1.35,3.9,3.9,0,0,0,3-1.28,4.37,4.37,0,0,0,.9-1.72h2.42a5,5,0,0,1-.63,1.8,6.34,6.34,0,0,1-1.21,1.62,5.71,5.71,0,0,1-2.75,1.48,8.65,8.65,0,0,1-2,.21,6.11,6.11,0,0,1-4.6-2,7.79,7.79,0,0,1-1.89-5.58,8.41,8.41,0,0,1,1.9-5.72,6.26,6.26,0,0,1,5-2.21A6.59,6.59,0,0,1,529.27,92.72Zm.88,5.74a6.46,6.46,0,0,0-.69-2.55,3.54,3.54,0,0,0-3.35-1.78,3.72,3.72,0,0,0-2.82,1.22,4.69,4.69,0,0,0-1.21,3.11Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M537.9,100.47a5.6,5.6,0,0,0,.78,2.78q1.3,2,4.59,2a7.9,7.9,0,0,0,2.69-.44,3.09,3.09,0,0,0,2.34-3,2.64,2.64,0,0,0-1-2.33,9.55,9.55,0,0,0-3.15-1.19l-2.64-.62a11.41,11.41,0,0,1-3.65-1.33A4.23,4.23,0,0,1,536,92.54a5.86,5.86,0,0,1,1.83-4.44A7.16,7.16,0,0,1,543,86.37a8.75,8.75,0,0,1,5.22,1.52,5.57,5.57,0,0,1,2.15,4.87h-2.56a5.12,5.12,0,0,0-.84-2.47c-.79-1.05-2.14-1.57-4.05-1.57a4.51,4.51,0,0,0-3.31,1,3.2,3.2,0,0,0-1,2.35,2.33,2.33,0,0,0,1.19,2.16,16.76,16.76,0,0,0,3.54,1.09l2.73.65a8.15,8.15,0,0,1,3,1.27,4.81,4.81,0,0,1,1.86,4.09,5.14,5.14,0,0,1-2.37,4.77,10.46,10.46,0,0,1-5.5,1.43,8.07,8.07,0,0,1-5.72-1.91,6.57,6.57,0,0,1-2-5.16Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M573.17,106.9l-1.36,1.65-3.11-2.36a11.33,11.33,0,0,1-2.43,1,10.29,10.29,0,0,1-2.85.37,9.18,9.18,0,0,1-7.32-3.06A11.71,11.71,0,0,1,553.76,97a11.9,11.9,0,0,1,2-7,8.78,8.78,0,0,1,7.69-3.72c3.54,0,6.17,1.14,7.87,3.42a11.08,11.08,0,0,1,2,6.82,14.28,14.28,0,0,1-.48,3.73,9.67,9.67,0,0,1-2.44,4.46ZM565.35,105a3.36,3.36,0,0,0,1.29-.47l-2.22-1.72,1.37-1.68,2.62,2A7.5,7.5,0,0,0,570.1,100a13.76,13.76,0,0,0,.45-3.39,8.48,8.48,0,0,0-1.85-5.7,6.35,6.35,0,0,0-5.07-2.17,6.6,6.6,0,0,0-5.15,2.08q-1.9,2.07-1.9,6.38A8.64,8.64,0,0,0,558.4,103a6.63,6.63,0,0,0,5.36,2.15A11.24,11.24,0,0,0,565.35,105Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-8 app-tagline"
|
||||
d="M576.58,86.87h2.72v17.69h10.08V107h-12.8Z"
|
||||
/>
|
||||
<line
|
||||
className="cls-9 app-name-underline"
|
||||
x1="219.17"
|
||||
y1="66.5"
|
||||
x2="384.17"
|
||||
y2="66.5"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>);
|
||||
|
||||
|
||||
}
|
249
web/pgadmin/dashboard/static/js/WelcomeDashboard.jsx
Normal file
249
web/pgadmin/dashboard/static/js/WelcomeDashboard.jsx
Normal file
@ -0,0 +1,249 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import React from 'react';
|
||||
import gettext from 'sources/gettext';
|
||||
import _ from 'lodash';
|
||||
import { Link, BrowserRouter } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import PgAdminLogo from './PgAdminLogo';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
emptyPanel: {
|
||||
background: theme.palette.grey[400],
|
||||
overflow: 'hidden',
|
||||
padding: '8px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
height: '100%'
|
||||
|
||||
},
|
||||
dashboardContainer: {
|
||||
paddingBottom: '8px',
|
||||
minHeight: '100%'
|
||||
},
|
||||
card: {
|
||||
position: 'relative',
|
||||
minWidth: 0,
|
||||
wordWrap: 'break-word',
|
||||
backgroundColor: theme.otherVars.tableBg,
|
||||
backgroundClip: 'border-box',
|
||||
border: '1px solid' + theme.otherVars.borderColor,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
marginTop: 8
|
||||
},
|
||||
row: {
|
||||
marginRight: '-8px',
|
||||
marginLeft: '-8px'
|
||||
},
|
||||
rowContent: {
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
marginRight: '-7.5px',
|
||||
marginLeft: '-7.5px'
|
||||
},
|
||||
cardHeader: {
|
||||
padding: '0.25rem 0.5rem',
|
||||
fontWeight: 'bold',
|
||||
backgroundColor: theme.otherVars.tableBg,
|
||||
borderBottom: '1px solid',
|
||||
borderBottomColor: theme.otherVars.borderColor,
|
||||
},
|
||||
dashboardLink: {
|
||||
color: theme.otherVars.colorFg + '!important',
|
||||
flex: '0 0 50%',
|
||||
maxWidth: '50%',
|
||||
textAlign: 'center',
|
||||
cursor: 'pointer'
|
||||
},
|
||||
gettingStartedLink: {
|
||||
flex: '0 0 25%',
|
||||
maxWidth: '50%',
|
||||
textAlign: 'center',
|
||||
cursor: 'pointer'
|
||||
},
|
||||
link: {
|
||||
color: theme.otherVars.colorFg + '!important',
|
||||
},
|
||||
cardColumn: {
|
||||
flex: '0 0 100%',
|
||||
maxWidth: '100%',
|
||||
margin: '8px'
|
||||
},
|
||||
cardBody: {
|
||||
flex: '1 1 auto',
|
||||
minHeight: '1px',
|
||||
padding: '0.5rem !important',
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
function AddNewServer(pgBrowser) {
|
||||
if (pgBrowser && pgBrowser.tree) {
|
||||
var i = _.isUndefined(pgBrowser.tree.selected()) ?
|
||||
pgBrowser.tree.first(null, false) :
|
||||
pgBrowser.tree.selected(),
|
||||
serverModule = pgAdmin.Browser.Nodes.server,
|
||||
itemData = pgBrowser.tree.itemData(i);
|
||||
|
||||
while (itemData && itemData._type != 'server_group') {
|
||||
i = pgBrowser.tree.next(i);
|
||||
itemData = pgBrowser.tree.itemData(i);
|
||||
}
|
||||
|
||||
if (!itemData) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (serverModule) {
|
||||
serverModule.callbacks.show_obj_properties.apply(
|
||||
serverModule, [{
|
||||
action: 'create',
|
||||
}, i]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function WelcomeDashboard({ pgBrowser }) {
|
||||
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<div className={classes.emptyPanel}>
|
||||
<div className={classes.dashboardContainer}>
|
||||
<div className={classes.row}>
|
||||
<div className={classes.cardColumn}>
|
||||
<div className={classes.card}>
|
||||
<div className={classes.cardHeader}>{gettext('Welcome')}</div>
|
||||
<div className={classes.cardBody}>
|
||||
<PgAdminLogo />
|
||||
<h4>
|
||||
{gettext('Feature rich')} | {gettext('Maximises PostgreSQL')}{' '}
|
||||
| {gettext('Open Source')}{' '}
|
||||
</h4>
|
||||
<p>
|
||||
{gettext(
|
||||
'pgAdmin is an Open Source administration and management tool for the PostgreSQL database. It includes a graphical administration interface, an SQL query tool, a procedural code debugger and much more. The tool is designed to answer the needs of developers, DBAs and system administrators alike.'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.row}>
|
||||
<div className={classes.cardColumn}>
|
||||
<div className={classes.card}>
|
||||
<div className={classes.cardHeader}>{gettext('Quick Links')}</div>
|
||||
<div className={classes.cardBody}>
|
||||
<div className={classes.rowContent}>
|
||||
<div className={classes.dashboardLink}>
|
||||
<Link to="#" onClick={() => { AddNewServer(pgBrowser); }} className={classes.link}>
|
||||
<span
|
||||
className="fa fa-4x dashboard-icon fa-server"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<br />
|
||||
{gettext('Add New Server')}
|
||||
</Link>
|
||||
</div>
|
||||
<div className={classes.dashboardLink}>
|
||||
<Link to="#" onClick={() => pgAdmin.Preferences.show()} className={classes.link}>
|
||||
<span
|
||||
id="mnu_preferences"
|
||||
className="fa fa-4x dashboard-icon fa-cogs"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<br />
|
||||
{gettext('Configure pgAdmin')}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.row}>
|
||||
<div className={classes.cardColumn}>
|
||||
<div className={classes.card}>
|
||||
<div className={classes.cardHeader}>{gettext('Getting Started')}</div>
|
||||
<div className={classes.cardBody}>
|
||||
<div className={classes.rowContent}>
|
||||
<div className={classes.gettingStartedLink}>
|
||||
<a
|
||||
href="http://www.postgresql.org/docs"
|
||||
target="postgres_help"
|
||||
className={classes.link}
|
||||
>
|
||||
<span
|
||||
className="fa fa-4x dashboard-icon dashboard-pg-doc"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<br />
|
||||
{gettext('PostgreSQL Documentation')}
|
||||
</a>
|
||||
</div>
|
||||
<div className={classes.gettingStartedLink}>
|
||||
<a href="https://www.pgadmin.org" target="pgadmin_website" className={classes.link}>
|
||||
<span
|
||||
className="fa fa-4x dashboard-icon fa-globe"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<br />
|
||||
{gettext('pgAdmin Website')}
|
||||
</a>
|
||||
</div>
|
||||
<div className={classes.gettingStartedLink}>
|
||||
<a
|
||||
href="http://planet.postgresql.org"
|
||||
target="planet_website"
|
||||
className={classes.link}
|
||||
>
|
||||
<span
|
||||
className="fa fa-4x dashboard-icon fa-book"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<br />
|
||||
{gettext('Planet PostgreSQL')}
|
||||
</a>
|
||||
</div>
|
||||
<div className={classes.gettingStartedLink}>
|
||||
<a
|
||||
href="http://www.postgresql.org/community"
|
||||
target="postgres_website"
|
||||
className={classes.link}
|
||||
>
|
||||
<span
|
||||
className="fa fa-4x dashboard-icon fa-users"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<br />
|
||||
{gettext('Community Support')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
WelcomeDashboard.propTypes = {
|
||||
pgBrowser: PropTypes.object.isRequired
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,74 +0,0 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export function ChartContainer(props) {
|
||||
return (
|
||||
<div className="card dashboard-graph" role="object-document" tabIndex="0" aria-labelledby={props.id}>
|
||||
<div className="card-header">
|
||||
<div className="d-flex">
|
||||
<div id={props.id}>{props.title}</div>
|
||||
<div className="ml-auto my-auto legend" ref={props.legendRef}></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-body dashboard-graph-body">
|
||||
<div className={'chart-wrapper ' + (props.errorMsg ? 'd-none': '')}>
|
||||
{props.children}
|
||||
</div>
|
||||
<ChartError message={props.errorMsg} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ChartContainer.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
legendRef: PropTypes.oneOfType([
|
||||
PropTypes.func,
|
||||
PropTypes.shape({ current: PropTypes.any }),
|
||||
]).isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
errorMsg: PropTypes.string,
|
||||
};
|
||||
|
||||
export function ChartError(props) {
|
||||
if(props.message === null) {
|
||||
return <></>;
|
||||
}
|
||||
return (
|
||||
<div className="pg-panel-error pg-panel-message" role="alert">{props.message}</div>
|
||||
);
|
||||
}
|
||||
|
||||
ChartError.propTypes = {
|
||||
message: PropTypes.string,
|
||||
};
|
||||
|
||||
export function DashboardRow({children}) {
|
||||
return (
|
||||
<div className="row dashboard-row">{children}</div>
|
||||
);
|
||||
}
|
||||
DashboardRow.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
export function DashboardRowCol({breakpoint, parts, children}) {
|
||||
return (
|
||||
<div className={`col-${breakpoint}-${parts}`}>{children}</div>
|
||||
);
|
||||
}
|
||||
|
||||
DashboardRowCol.propTypes = {
|
||||
breakpoint: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl']).isRequired,
|
||||
parts: PropTypes.number.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
@ -1,59 +0,0 @@
|
||||
<div class="container-fluid dashboard-container negative-space">
|
||||
<div id="dashboard-graphs"></div>
|
||||
<div id="dashboard-activity" class="card dashboard-row dashboard-hidden">
|
||||
<div class="card-header">
|
||||
<span id="dashboard-activity-header">{{ _('Server activity') }}</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-9 col-12 pr-0">
|
||||
<ul class="nav nav-tabs" role="tablist" aria-labelledby="dashboard-activity-header">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active show" id="tab_panel_database_activity_tab" href="#tab_panel_database_activity" aria-controls="tab_database_activity"
|
||||
role="tab" data-toggle="tab">{{ _('Sessions') }}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="tab_panel_database_locks_tab" href="#tab_panel_database_locks" aria-controls="tab_database_locks"
|
||||
role="tab" data-toggle="tab">{{ _('Locks') }}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="tab_panel_database_prepared_tab" href="#tab_panel_database_prepared" aria-controls="tab_database_prepared"
|
||||
role="tab" data-toggle="tab">{{ _('Prepared Transactions') }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-3 col-12 pl-0 text-right">
|
||||
<div class="navtab-inline-controls">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text fa fa-search" id="labelSearch" aria-label="{{ _('Search') }}"></span>
|
||||
</div>
|
||||
<input type="search" class="form-control" id="txtGridSearch" placeholder="{{ _('Search') }}" aria-describedby="labelSearch" aria-labelledby="labelSearch">
|
||||
</div>
|
||||
<button id="btn_refresh" type="button" class="btn btn-primary-icon btn-navtab-inline" title="{{ _('Refresh') }}" aria-label="{{ _('Refresh') }}">
|
||||
<span class="fa fa-sync-alt" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Nav tabs -->
|
||||
|
||||
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane negative-space p-2 active show" id="tab_panel_database_activity" aria-labelledby="tab_panel_database_activity_tab">
|
||||
<div id="database_activity" class="grid-container"></div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane negative-space p-2" id="tab_panel_database_locks" aria-labelledby="tab_panel_database_locks_tab">
|
||||
<div id="database_locks" class="grid-container"></div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane negative-space p-2" id="tab_panel_database_prepared" aria-labelledby="tab_panel_database_prepared_tab">
|
||||
<div id="database_prepared" class="grid-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="dashboard-none-show" class="alert alert-info pg-panel-message dashboard-hidden" role="alert">
|
||||
{{ _('All Dashboard elements are currently disabled.') }}
|
||||
</div>
|
||||
</div>
|
@ -1,67 +0,0 @@
|
||||
<div class="container-fluid dashboard-container negative-space">
|
||||
<div id="dashboard-graphs">
|
||||
</div>
|
||||
<div id="dashboard-activity" class="card dashboard-row dashboard-hidden">
|
||||
<div class="card-header">
|
||||
<span id="server-activity-header">{{ _('Server activity') }}</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-9 col-12 pr-0">
|
||||
<ul class="nav nav-tabs" role="tablist" aria-labelledby="server-activity-header">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active show" id="tab_panel_server_activity_tab" href="#tab_panel_server_activity" aria-controls="tab_server_activity"
|
||||
role="tab" data-toggle="tab">{{_('Sessions') }}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="tab_panel_server_locks_tab" href="#tab_panel_server_locks" aria-controls="tab_server_locks"
|
||||
role="tab" data-toggle="tab">{{ _('Locks') }}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="tab_panel_server_prepared_tab" href="#tab_panel_server_prepared" aria-controls="tab_server_prepared"
|
||||
role="tab" data-toggle="tab">{{ _('Prepared Transactions') }}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="tab_panel_server_config_tab" href="#tab_panel_server_config" aria-controls="tab_server_config"
|
||||
role="tab" data-toggle="tab">{{ _('Configuration') }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-3 col-12 pl-0 text-right">
|
||||
<div class="navtab-inline-controls">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text fa fa-search" id="labelSearch"></span>
|
||||
</div>
|
||||
<input type="search" class="form-control" id="txtGridSearch" placeholder="{{ _('Search') }}" aria-label="{{ _('Search') }}" aria-describedby="labelSearch">
|
||||
</div>
|
||||
<button id="btn_refresh" type="button" class="btn btn-primary-icon btn-navtab-inline" title="{{ _('Refresh') }}" aria-label="{{ _('Refresh') }}">
|
||||
<span class="fa fa-sync-alt" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Nav tabs -->
|
||||
|
||||
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane negative-space p-2 active show" id="tab_panel_server_activity" aria-labelledby="tab_panel_server_activity_tab">
|
||||
<div id="server_activity" class="grid-container"></div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane negative-space p-2" id="tab_panel_server_locks" aria-labelledby="tab_panel_server_locks_tab">
|
||||
<div id="server_locks" class="grid-container"></div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane negative-space p-2" id="tab_panel_server_prepared" aria-labelledby="tab_panel_server_prepared_tab">
|
||||
<div id="server_prepared" class="grid-container"></div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane negative-space p-2" id="tab_panel_server_config" aria-labelledby="tab_panel_server_config_tab">
|
||||
<div id="server_config" class="grid-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="dashboard-none-show" class="alert alert-info pg-panel-message dashboard-hidden" role="alert">
|
||||
{{ _('All Dashboard elements are currently disabled.') }}
|
||||
</div>
|
||||
</div>
|
@ -12,6 +12,7 @@ SELECT
|
||||
query,
|
||||
pg_catalog.to_char(state_change, 'YYYY-MM-DD HH24:MI:SS TZ') AS state_change,
|
||||
pg_catalog.to_char(query_start, 'YYYY-MM-DD HH24:MI:SS TZ') AS query_start,
|
||||
pg_catalog.to_char(xact_start, 'YYYY-MM-DD HH24:MI:SS TZ') AS xact_start,
|
||||
backend_type,
|
||||
CASE WHEN state = 'active' THEN ROUND((extract(epoch from now() - query_start) / 60)::numeric, 2) ELSE 0 END AS active_since
|
||||
FROM
|
||||
|
@ -11,6 +11,7 @@ SELECT
|
||||
pg_catalog.pg_blocking_pids(pid) AS blocking_pids,
|
||||
query,
|
||||
pg_catalog.to_char(state_change, 'YYYY-MM-DD HH24:MI:SS TZ') AS state_change,
|
||||
pg_catalog.to_char(xact_start, 'YYYY-MM-DD HH24:MI:SS TZ') AS xact_start,
|
||||
pg_catalog.to_char(query_start, 'YYYY-MM-DD HH24:MI:SS TZ') AS query_start,
|
||||
CASE WHEN state = 'active' THEN ROUND((extract(epoch from now() - query_start) / 60)::numeric, 2) ELSE 0 END AS active_since
|
||||
FROM
|
||||
|
@ -10,6 +10,7 @@ SELECT
|
||||
CASE WHEN waiting THEN '{{ _('yes') }}' ELSE '{{ _('no') }}' END AS waiting,
|
||||
query,
|
||||
pg_catalog.to_char(state_change, 'YYYY-MM-DD HH24:MI:SS TZ') AS state_change,
|
||||
pg_catalog.to_char(xact_start, 'YYYY-MM-DD HH24:MI:SS TZ') AS xact_start,
|
||||
pg_catalog.to_char(query_start, 'YYYY-MM-DD HH24:MI:SS TZ') AS query_start,
|
||||
CASE WHEN state = 'active' THEN ROUND((extract(epoch from now() - query_start) / 60)::numeric, 2) ELSE 0 END AS active_since
|
||||
FROM
|
||||
|
@ -1,135 +0,0 @@
|
||||
<div class="container-fluid negative-space">
|
||||
<div class="dashboard-container">
|
||||
<div class="row mb-2">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">{{ _('Welcome') }}</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="welcome-logo" aria-hidden="true">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 130">
|
||||
<defs>
|
||||
<style>.cls-1{stroke:#000;stroke-width:10.19px;}.cls-2{fill:#336791;}.cls-3,.cls-4,.cls-9{fill:none;}.cls-3,.cls-4,.cls-5,.cls-6{stroke:#fff;}.cls-3,.cls-4{stroke-linecap:round;stroke-width:3.4px;}.cls-3{stroke-linejoin:round;}.cls-4{stroke-linejoin:bevel;}.cls-5,.cls-6{fill:#fff;}.cls-5{stroke-width:1.13px;}.cls-6{stroke-width:0.57px;}.cls-7{fill:#2775b6;}.cls-8{fill:#333;}.cls-9{stroke:#333;stroke-width:3px;}</style>
|
||||
</defs>
|
||||
<title>pgAdmin_PostgreSQL</title>
|
||||
<g id="Layer_1" data-name="Layer 1">
|
||||
<g id="Layer_3">
|
||||
<path class="cls-1" d="M95.59,93.65c.77-6.44.54-7.38,5.33-6.34l1.21.11a27.6,27.6,0,0,0,11.34-1.91c6.09-2.83,9.71-7.55,3.7-6.31-13.71,2.83-14.65-1.81-14.65-1.81C117,55.91,123,28.64,117.82,22,103.57,3.76,78.91,12.37,78.5,12.6l-.13,0a48.65,48.65,0,0,0-9.15-.95C63,11.57,58.31,13.29,54.74,16c0,0-44-18.12-41.95,22.8.44,8.7,12.48,65.86,26.84,48.6C44.88,81.08,50,75.75,50,75.75A13.39,13.39,0,0,0,58.65,78l.25-.21a9,9,0,0,0,.1,2.46c-3.7,4.13-2.62,4.86-10,6.38s-3.09,4.29-.22,5c3.48.87,11.53,2.1,17-5.52l-.22.87c1.46,1.16,1.36,8.35,1.56,13.48s.55,9.93,1.6,12.75,2.28,10.1,12,8C88.81,119.46,95,117,95.59,93.65" />
|
||||
<path class="cls-2" d="M117.17,79.2c-13.71,2.83-14.65-1.81-14.65-1.81C117,55.91,123,28.64,117.82,22,103.57,3.76,78.91,12.37,78.5,12.6l-.13,0a48.65,48.65,0,0,0-9.15-.95C63,11.57,58.31,13.29,54.74,16c0,0-44-18.12-41.95,22.8.44,8.7,12.48,65.86,26.84,48.6C44.88,81.08,50,75.75,50,75.75A13.39,13.39,0,0,0,58.65,78l.25-.21A9.41,9.41,0,0,0,59,80.22c-3.7,4.13-2.61,4.86-10,6.38s-3.08,4.29-.21,5c3.48.87,11.53,2.1,17-5.52l-.22.87c1.45,1.16,2.47,7.56,2.3,13.35s-.28,9.77.86,12.88,2.28,10.1,12,8C88.81,119.46,93,115,93.6,107.42,94,102.07,95,102.87,95,98.08l.75-2.26c.87-7.26.14-9.6,5.15-8.51l1.21.11a27.6,27.6,0,0,0,11.34-1.91c6.09-2.83,9.71-7.55,3.7-6.31Z" />
|
||||
<path class="cls-3" d="M66.33,83.36c-.38,13.5.09,27.09,1.41,30.39s4.15,9.73,13.88,7.64c8.12-1.74,11.08-5.11,12.36-12.55.94-5.47,2.77-20.67,3-23.79" />
|
||||
<path class="cls-3" d="M54.67,15.7s-44-18-42,22.93c.44,8.7,12.48,65.87,26.84,48.6,5.25-6.32,10-11.27,10-11.27" />
|
||||
<path class="cls-3" d="M78.45,12.42c-1.52.47,24.49-9.51,39.28,9.38,5.22,6.67-.83,33.94-15.31,55.42" />
|
||||
<path class="cls-4" d="M102.42,77.22s.94,4.64,14.65,1.81c6-1.24,2.4,3.48-3.7,6.31-5,2.32-16.21,2.92-16.39-.29-.47-8.27,5.9-5.76,5.44-7.83-.42-1.87-3.26-3.7-5.15-8.27-1.64-4-22.57-34.58,5.8-30,1-.22-7.4-27-33.95-27.42S43.45,44.14,43.45,44.14" />
|
||||
<path class="cls-3" d="M58.9,80.05c-3.7,4.13-2.61,4.86-10,6.38s-3.09,4.29-.22,5c3.48.87,11.53,2.1,17-5.52,1.66-2.32,0-6-2.28-7-1.1-.46-2.57-1-4.46,1.09Z" />
|
||||
<path class="cls-3" d="M58.66,80c-.38-2.44.79-5.33,2.05-8.71C62.6,66.19,67,61.11,63.47,45c-2.6-12-20-2.5-20-.87a81.48,81.48,0,0,1-.29,16c-1.41,10.06,6.4,18.57,15.39,17.7" />
|
||||
<path class="cls-5" d="M54.51,43.9c-.08.55,1,2,2.45,2.23a2.62,2.62,0,0,0,2.72-1.51c.08-.56-1-1.17-2.44-1.37s-2.65.09-2.73.65Z" />
|
||||
<path class="cls-6" d="M98,42.76c.07.56-1,2-2.45,2.24a2.64,2.64,0,0,1-2.73-1.52c-.07-.55,1-1.16,2.45-1.36s2.65.09,2.73.64Z" />
|
||||
<path class="cls-3" d="M103.07,38.92c.24,4.36-.94,7.33-1.08,12-.22,6.74,3.21,14.46-2,22.19" />
|
||||
</g>
|
||||
<path class="cls-7 app-name" d="M154.72,28.15h5.16v4.16A12.84,12.84,0,0,1,163.35,29a11.17,11.17,0,0,1,6.28-1.76,11.84,11.84,0,0,1,9.08,4.09c2.48,2.72,3.73,6.62,3.73,11.67q0,10.26-5.38,14.65a12.2,12.2,0,0,1-7.95,2.79,10.78,10.78,0,0,1-6-1.56,13.55,13.55,0,0,1-3.14-3v16h-5.28Zm19.84,24.6Q177,49.65,177,43.5a17,17,0,0,0-1.09-6.44,7.51,7.51,0,0,0-7.53-5.19q-5.49,0-7.52,5.48a21.49,21.49,0,0,0-1.09,7.44A15.64,15.64,0,0,0,160.88,51a8,8,0,0,0,13.68,1.78Z" />
|
||||
<path class="cls-7 app-name" d="M206,29.26a14.6,14.6,0,0,1,3,3V28.3h4.86V56.83c0,4-.58,7.13-1.75,9.44q-3.27,6.38-12.35,6.38a15.07,15.07,0,0,1-8.5-2.27,8.86,8.86,0,0,1-3.85-7.1h5.36a6,6,0,0,0,1.52,3.25q1.77,1.75,5.59,1.76,6,0,7.9-4.28,1.1-2.52,1-9a10.39,10.39,0,0,1-3.8,3.57,13.56,13.56,0,0,1-14.75-2.45q-3.81-3.62-3.81-12,0-7.89,3.84-12.31a11.85,11.85,0,0,1,9.27-4.42A11.37,11.37,0,0,1,206,29.26Zm.64,5.66a7.61,7.61,0,0,0-6.09-2.81A7.52,7.52,0,0,0,193,37.32a20.56,20.56,0,0,0-1.08,7.3c0,3.53.72,6.22,2.14,8.07a6.93,6.93,0,0,0,5.76,2.77,8.09,8.09,0,0,0,8-5.13A16.72,16.72,0,0,0,209,43.56Q209,37.73,206.62,34.92Z" />
|
||||
<path class="cls-7 app-name" d="M235.16,16.34h6.58l15.62,43H251l-4.5-12.89H229.6l-4.67,12.89h-6Zm9.67,25.4-6.63-19-6.88,19Z" />
|
||||
<path class="cls-7 app-name" d="M279.16,29a14.3,14.3,0,0,1,3.18,3.08V16.2h5.07V59.38h-4.75V55a11.33,11.33,0,0,1-4.35,4.19,12.51,12.51,0,0,1-5.75,1.28,11.61,11.61,0,0,1-9-4.4q-3.82-4.41-3.83-11.74a20.35,20.35,0,0,1,3.49-11.88,11.41,11.41,0,0,1,10-5A11.15,11.15,0,0,1,279.16,29ZM267.39,52.5q2.13,3.39,6.82,3.39a7.17,7.17,0,0,0,6-3.14c1.56-2.1,2.35-5.12,2.35-9s-.81-6.9-2.42-8.81a7.56,7.56,0,0,0-6-2.85,7.88,7.88,0,0,0-6.43,3c-1.64,2-2.46,5-2.46,9A15.62,15.62,0,0,0,267.39,52.5Z" />
|
||||
<path class="cls-7 app-name" d="M295.29,28h5.21v4.46a17.4,17.4,0,0,1,3.4-3.37,10.24,10.24,0,0,1,5.92-1.79,9.34,9.34,0,0,1,6,1.85,9.61,9.61,0,0,1,2.34,3.1,11.37,11.37,0,0,1,4.13-3.73,11.52,11.52,0,0,1,5.33-1.22q6.33,0,8.62,4.57a15,15,0,0,1,1.23,6.62V59.38H332V37.58c0-2.09-.52-3.52-1.57-4.3a6.2,6.2,0,0,0-3.82-1.17,7.58,7.58,0,0,0-5.35,2.08c-1.49,1.38-2.24,3.7-2.24,6.94V59.38h-5.36V38.9a10.78,10.78,0,0,0-.76-4.66q-1.2-2.19-4.49-2.19A7.73,7.73,0,0,0,303,34.36c-1.63,1.54-2.45,4.34-2.45,8.38V59.38h-5.27Z" />
|
||||
<path class="cls-7 app-name" d="M345.27,16.34h5.36v6h-5.36Zm0,11.81h5.36V59.38h-5.36Z" />
|
||||
<path class="cls-7 app-name" d="M358.6,28h5v4.46a14,14,0,0,1,4.72-4,12.56,12.56,0,0,1,5.53-1.2c4.46,0,7.46,1.55,9,4.66a16.52,16.52,0,0,1,1.29,7.29V59.38h-5.37V39.61A10.8,10.8,0,0,0,378,35a5.15,5.15,0,0,0-5.1-2.93,10.21,10.21,0,0,0-3.08.38A8,8,0,0,0,366,35a7.66,7.66,0,0,0-1.71,3.2,21.84,21.84,0,0,0-.4,4.74V59.38H358.6Z" />
|
||||
<path class="cls-8 app-tagline" d="M155.24,86.87h3.9l5.77,17,5.74-17h3.87V107h-2.6V95.1q0-.61,0-2c0-.94,0-2,0-3L166.24,107h-2.7L157.75,90v.61c0,.49,0,1.24,0,2.25s.05,1.75.05,2.22V107h-2.6Z" />
|
||||
<path class="cls-8 app-tagline" d="M186.15,98.09a1.35,1.35,0,0,0,1.14-.71,2.31,2.31,0,0,0,.16-.94,2,2,0,0,0-.89-1.84A4.79,4.79,0,0,0,184,94a3.21,3.21,0,0,0-2.73,1,3.44,3.44,0,0,0-.59,1.72h-2.3A4.28,4.28,0,0,1,180.14,93,7.16,7.16,0,0,1,184.05,92a8,8,0,0,1,4.19,1,3.34,3.34,0,0,1,1.6,3.06v8.44a1.06,1.06,0,0,0,.16.62.77.77,0,0,0,.66.23l.37,0,.44-.07V107a7.38,7.38,0,0,1-.88.21,5.92,5.92,0,0,1-.82,0,2,2,0,0,1-1.84-.9,3.63,3.63,0,0,1-.43-1.36,6.16,6.16,0,0,1-2.16,1.71,6.56,6.56,0,0,1-3.1.73,4.59,4.59,0,0,1-3.33-1.24,4.09,4.09,0,0,1-1.29-3.09,4,4,0,0,1,1.27-3.16,6.16,6.16,0,0,1,3.34-1.38ZM181,104.74a2.88,2.88,0,0,0,1.84.62,5.51,5.51,0,0,0,2.52-.61,3.37,3.37,0,0,0,2-3.26v-2a3.79,3.79,0,0,1-1.16.48,10.37,10.37,0,0,1-1.39.28l-1.49.19a5.68,5.68,0,0,0-2,.56,2.18,2.18,0,0,0-1.14,2A2,2,0,0,0,181,104.74Z" />
|
||||
<path class="cls-8 app-tagline" d="M193.88,92.31h2.33v2.08a6.73,6.73,0,0,1,2.2-1.85A6,6,0,0,1,201,92q3.12,0,4.21,2.18a7.73,7.73,0,0,1,.6,3.4V107h-2.5V97.73a4.87,4.87,0,0,0-.4-2.16,2.41,2.41,0,0,0-2.38-1.37,4.75,4.75,0,0,0-1.43.18,3.68,3.68,0,0,0-1.78,1.2,3.55,3.55,0,0,0-.8,1.5,10.3,10.3,0,0,0-.18,2.21V107h-2.46Z" />
|
||||
<path class="cls-8 app-tagline" d="M217.29,98.09a1.33,1.33,0,0,0,1.14-.71,2.15,2.15,0,0,0,.16-.94,2,2,0,0,0-.89-1.84,4.79,4.79,0,0,0-2.56-.56,3.24,3.24,0,0,0-2.73,1,3.44,3.44,0,0,0-.58,1.72h-2.3A4.27,4.27,0,0,1,211.28,93,7.19,7.19,0,0,1,215.2,92a8,8,0,0,1,4.19,1A3.36,3.36,0,0,1,221,96v8.44a1.14,1.14,0,0,0,.15.62.79.79,0,0,0,.67.23l.37,0,.43-.07V107a7.32,7.32,0,0,1-.87.21,6,6,0,0,1-.82,0,2,2,0,0,1-1.85-.9,3.46,3.46,0,0,1-.42-1.36,6.16,6.16,0,0,1-2.16,1.71,6.63,6.63,0,0,1-3.11.73,4.58,4.58,0,0,1-3.32-1.24,4.06,4.06,0,0,1-1.3-3.09A4,4,0,0,1,210,100a6.13,6.13,0,0,1,3.33-1.38Zm-5.18,6.65a2.91,2.91,0,0,0,1.85.62,5.47,5.47,0,0,0,2.51-.61,3.38,3.38,0,0,0,2.06-3.26v-2a3.9,3.9,0,0,1-1.16.48,10.51,10.51,0,0,1-1.4.28l-1.48.19a5.55,5.55,0,0,0-2,.56,2.17,2.17,0,0,0-1.15,2A2,2,0,0,0,212.11,104.74Z" />
|
||||
<path class="cls-8 app-tagline" d="M233.16,92.9a7.05,7.05,0,0,1,1.42,1.39V92.45h2.27v13.32a10,10,0,0,1-.82,4.4c-1,2-2.94,3-5.77,3a7.09,7.09,0,0,1-4-1.06,4.15,4.15,0,0,1-1.8-3.32H227a2.81,2.81,0,0,0,.71,1.52,3.57,3.57,0,0,0,2.61.82c1.88,0,3.1-.66,3.68-2a11.15,11.15,0,0,0,.48-4.2,4.84,4.84,0,0,1-1.77,1.67,5.93,5.93,0,0,1-2.74.54,5.79,5.79,0,0,1-4.14-1.69q-1.79-1.68-1.78-5.58a8.49,8.49,0,0,1,1.79-5.74,5.51,5.51,0,0,1,4.32-2.07A5.33,5.33,0,0,1,233.16,92.9Zm.3,2.64a3.77,3.77,0,0,0-6.38,1.12,9.73,9.73,0,0,0-.5,3.4,6.05,6.05,0,0,0,1,3.77,3.21,3.21,0,0,0,2.68,1.29,3.77,3.77,0,0,0,3.72-2.39,7.71,7.71,0,0,0,.6-3.16A6.13,6.13,0,0,0,233.46,95.54Z" />
|
||||
<path class="cls-8 app-tagline" d="M249.64,92.72a5.44,5.44,0,0,1,2.21,1.89,6.52,6.52,0,0,1,1,2.58,17.44,17.44,0,0,1,.22,3.23H242.4a6.34,6.34,0,0,0,1,3.59,3.49,3.49,0,0,0,3,1.35,3.9,3.9,0,0,0,3.05-1.28,4.5,4.5,0,0,0,.9-1.72h2.42a5,5,0,0,1-.63,1.8,6.58,6.58,0,0,1-1.21,1.62,5.71,5.71,0,0,1-2.75,1.48,8.71,8.71,0,0,1-2,.21,6.11,6.11,0,0,1-4.6-2,7.79,7.79,0,0,1-1.89-5.58,8.41,8.41,0,0,1,1.9-5.72,6.26,6.26,0,0,1,5-2.21A6.59,6.59,0,0,1,249.64,92.72Zm.88,5.74a6.46,6.46,0,0,0-.69-2.55,3.54,3.54,0,0,0-3.35-1.78,3.72,3.72,0,0,0-2.82,1.22,4.69,4.69,0,0,0-1.21,3.11Z" />
|
||||
<path class="cls-8 app-tagline" d="M256.16,92.31h2.44v2.08a8.23,8.23,0,0,1,1.58-1.57A4.79,4.79,0,0,1,263,92a4.31,4.31,0,0,1,2.81.87,4.5,4.5,0,0,1,1.1,1.44,5.27,5.27,0,0,1,1.92-1.74,5.37,5.37,0,0,1,2.49-.57,4.08,4.08,0,0,1,4,2.14,7,7,0,0,1,.58,3.09V107h-2.56V96.78a2.41,2.41,0,0,0-.73-2,2.93,2.93,0,0,0-1.79-.54,3.53,3.53,0,0,0-2.49,1,4.23,4.23,0,0,0-1.05,3.24V107h-2.5V97.4a5,5,0,0,0-.36-2.18,2.17,2.17,0,0,0-2.09-1,3.59,3.59,0,0,0-2.53,1.08c-.76.72-1.14,2-1.14,3.91V107h-2.47Z" />
|
||||
<path class="cls-8 app-tagline" d="M288.53,92.72a5.47,5.47,0,0,1,2.22,1.89,6.67,6.67,0,0,1,1,2.58,17.66,17.66,0,0,1,.21,3.23H281.29a6.42,6.42,0,0,0,1,3.59,3.48,3.48,0,0,0,3,1.35,3.9,3.9,0,0,0,3.06-1.28,4.5,4.5,0,0,0,.9-1.72h2.42a5.23,5.23,0,0,1-.64,1.8,6.56,6.56,0,0,1-1.2,1.62,5.7,5.7,0,0,1-2.76,1.48,8.62,8.62,0,0,1-2,.21,6.14,6.14,0,0,1-4.61-2,7.79,7.79,0,0,1-1.89-5.58,8.41,8.41,0,0,1,1.91-5.72,6.24,6.24,0,0,1,5-2.21A6.58,6.58,0,0,1,288.53,92.72Zm.88,5.74a6.46,6.46,0,0,0-.69-2.55,3.53,3.53,0,0,0-3.35-1.78,3.72,3.72,0,0,0-2.82,1.22,4.63,4.63,0,0,0-1.2,3.11Z" />
|
||||
<path class="cls-8 app-tagline" d="M295.06,92.31h2.34v2.08a6.63,6.63,0,0,1,2.2-1.85,5.91,5.91,0,0,1,2.58-.56c2.08,0,3.49.73,4.21,2.18a7.57,7.57,0,0,1,.61,3.4V107h-2.51V97.73a5,5,0,0,0-.39-2.16,2.41,2.41,0,0,0-2.38-1.37,4.86,4.86,0,0,0-1.44.18,3.7,3.7,0,0,0-1.77,1.2,3.55,3.55,0,0,0-.8,1.5,9.58,9.58,0,0,0-.19,2.21V107h-2.46Z" />
|
||||
<path class="cls-8 app-tagline" d="M311.13,88.22h2.48v4.09H316v2h-2.34v9.56a1,1,0,0,0,.52,1,2.21,2.21,0,0,0,1,.15h.38l.48,0v2a4.16,4.16,0,0,1-.88.18,7.74,7.74,0,0,1-1,.06,2.69,2.69,0,0,1-2.34-.88,3.94,3.94,0,0,1-.61-2.29v-9.7h-2v-2h2Z" />
|
||||
<path class="cls-8 app-tagline" d="M340.63,86.87v2.39h-6.77V107h-2.75V89.26h-6.76V86.87Z" />
|
||||
<path class="cls-8 app-tagline" d="M350.31,93.77a7.42,7.42,0,0,1,1.94,5.55,9.57,9.57,0,0,1-1.71,5.85,6.19,6.19,0,0,1-5.31,2.3,6,6,0,0,1-4.76-2A8.08,8.08,0,0,1,338.7,100a8.78,8.78,0,0,1,1.86-5.88,6.25,6.25,0,0,1,5-2.18A6.6,6.6,0,0,1,350.31,93.77Zm-1.53,9.74a9.32,9.32,0,0,0,.9-4.12,7.39,7.39,0,0,0-.65-3.33,3.63,3.63,0,0,0-3.54-2,3.49,3.49,0,0,0-3.25,1.72,8.07,8.07,0,0,0-1,4.15,7.05,7.05,0,0,0,1,3.89,3.56,3.56,0,0,0,3.22,1.56A3.35,3.35,0,0,0,348.78,103.51Z" />
|
||||
<path class="cls-8 app-tagline" d="M365.88,93.77a7.42,7.42,0,0,1,1.94,5.55,9.57,9.57,0,0,1-1.71,5.85,6.19,6.19,0,0,1-5.31,2.3,6,6,0,0,1-4.76-2,8.08,8.08,0,0,1-1.77-5.48,8.78,8.78,0,0,1,1.86-5.88,6.25,6.25,0,0,1,5-2.18A6.58,6.58,0,0,1,365.88,93.77Zm-1.53,9.74a9.32,9.32,0,0,0,.9-4.12,7.26,7.26,0,0,0-.65-3.33,3.63,3.63,0,0,0-3.54-2,3.46,3.46,0,0,0-3.24,1.72,8,8,0,0,0-1,4.15,7,7,0,0,0,1,3.89,3.54,3.54,0,0,0,3.21,1.56A3.35,3.35,0,0,0,364.35,103.51Z" />
|
||||
<path class="cls-8 app-tagline" d="M370.91,86.87h2.46V107h-2.46Z" />
|
||||
<path class="cls-8 app-tagline" d="M378.53,102.36a3.47,3.47,0,0,0,.63,1.89,4,4,0,0,0,3.29,1.19,4.86,4.86,0,0,0,2.45-.6A2,2,0,0,0,386,103a1.56,1.56,0,0,0-.84-1.43,10.63,10.63,0,0,0-2.14-.7l-2-.49a10,10,0,0,1-2.81-1,3.11,3.11,0,0,1-1.61-2.76,4.21,4.21,0,0,1,1.52-3.37,6.13,6.13,0,0,1,4.08-1.28q3.36,0,4.83,1.94a4.24,4.24,0,0,1,.91,2.65h-2.33A2.72,2.72,0,0,0,385,95a3.92,3.92,0,0,0-3-1,3.7,3.7,0,0,0-2.16.53,1.65,1.65,0,0,0-.74,1.4,1.73,1.73,0,0,0,1,1.53,5.69,5.69,0,0,0,1.64.6l1.66.4A12.73,12.73,0,0,1,387,99.75a3.3,3.3,0,0,1,1.44,3,4.48,4.48,0,0,1-1.5,3.37,6.45,6.45,0,0,1-4.58,1.43c-2.2,0-3.77-.5-4.68-1.49a5.59,5.59,0,0,1-1.48-3.67Z" />
|
||||
<path class="cls-8 app-tagline" d="M400,87.84c.58-.84,1.68-1.26,3.32-1.26l.48,0,.56,0v2.24l-.56,0h-.32c-.76,0-1.21.19-1.36.58a11.75,11.75,0,0,0-.22,3h2.46v1.94h-2.46V107h-2.43V94.32h-2V92.38h2v-2.3A4.43,4.43,0,0,1,400,87.84Z" />
|
||||
<path class="cls-8 app-tagline" d="M417.23,93.77a7.38,7.38,0,0,1,1.94,5.55,9.57,9.57,0,0,1-1.71,5.85,6.18,6.18,0,0,1-5.3,2.3,6,6,0,0,1-4.77-2,8.07,8.07,0,0,1-1.76-5.48,8.83,8.83,0,0,1,1.85-5.88,6.25,6.25,0,0,1,5-2.18A6.58,6.58,0,0,1,417.23,93.77Zm-1.53,9.74a9.32,9.32,0,0,0,.9-4.12,7.26,7.26,0,0,0-.65-3.33,3.63,3.63,0,0,0-3.54-2,3.47,3.47,0,0,0-3.24,1.72,8,8,0,0,0-1,4.15,7,7,0,0,0,1,3.89,3.55,3.55,0,0,0,3.22,1.56A3.35,3.35,0,0,0,415.7,103.51Z" />
|
||||
<path class="cls-8 app-tagline" d="M422.26,92.31h2.34v2.53A5.61,5.61,0,0,1,426,93,3.68,3.68,0,0,1,428.59,92l.24,0,.56,0v2.6a2.08,2.08,0,0,0-.41-.05l-.4,0a3.52,3.52,0,0,0-2.86,1.2,4.2,4.2,0,0,0-1,2.75V107h-2.46Z" />
|
||||
<path class="cls-8 app-tagline" d="M439.89,86.87h9a6.09,6.09,0,0,1,4.31,1.51,5.5,5.5,0,0,1,1.64,4.25,6.15,6.15,0,0,1-1.47,4.09,5.5,5.5,0,0,1-4.47,1.74h-6.27V107h-2.72Zm10.55,2.76a5.92,5.92,0,0,0-2.46-.42h-5.37v7H448a5.07,5.07,0,0,0,2.95-.78,3.1,3.1,0,0,0,1.14-2.75A3,3,0,0,0,450.44,89.63Z" />
|
||||
<path class="cls-8 app-tagline" d="M468.58,93.77a7.38,7.38,0,0,1,1.95,5.55,9.51,9.51,0,0,1-1.72,5.85,6.17,6.17,0,0,1-5.3,2.3,6,6,0,0,1-4.77-2A8.07,8.07,0,0,1,457,100a8.78,8.78,0,0,1,1.86-5.88,6.23,6.23,0,0,1,5-2.18A6.56,6.56,0,0,1,468.58,93.77Zm-1.52,9.74a9.32,9.32,0,0,0,.9-4.12,7.39,7.39,0,0,0-.65-3.33,3.65,3.65,0,0,0-3.55-2,3.48,3.48,0,0,0-3.24,1.72,8,8,0,0,0-1,4.15,7,7,0,0,0,1,3.89,3.56,3.56,0,0,0,3.22,1.56A3.36,3.36,0,0,0,467.06,103.51Z" />
|
||||
<path class="cls-8 app-tagline" d="M475,102.36a3.47,3.47,0,0,0,.63,1.89,4,4,0,0,0,3.29,1.19,4.93,4.93,0,0,0,2.46-.6,2,2,0,0,0,1.06-1.84,1.55,1.55,0,0,0-.85-1.43,10.4,10.4,0,0,0-2.14-.7l-2-.49a9.78,9.78,0,0,1-2.8-1,3.1,3.1,0,0,1-1.62-2.76,4.21,4.21,0,0,1,1.52-3.37,6.13,6.13,0,0,1,4.08-1.28q3.36,0,4.84,1.94a4.17,4.17,0,0,1,.9,2.65h-2.33a2.72,2.72,0,0,0-.6-1.51,3.92,3.92,0,0,0-3-1,3.7,3.7,0,0,0-2.16.53,1.64,1.64,0,0,0-.73,1.4,1.72,1.72,0,0,0,1,1.53,5.66,5.66,0,0,0,1.65.6l1.65.4a12.83,12.83,0,0,1,3.63,1.24,3.31,3.31,0,0,1,1.43,3,4.48,4.48,0,0,1-1.5,3.37,6.45,6.45,0,0,1-4.58,1.43q-3.3,0-4.68-1.49a5.59,5.59,0,0,1-1.48-3.67Z" />
|
||||
<path class="cls-8 app-tagline" d="M488,88.22h2.49v4.09h2.34v2h-2.34v9.56a1,1,0,0,0,.52,1,2.19,2.19,0,0,0,.95.15h.39l.48,0v2a4.39,4.39,0,0,1-.89.18,7.52,7.52,0,0,1-1,.06,2.69,2.69,0,0,1-2.34-.88A3.94,3.94,0,0,1,488,104v-9.7h-2v-2h2Z" />
|
||||
<path class="cls-8 app-tagline" d="M503.47,92.9a6.78,6.78,0,0,1,1.41,1.39V92.45h2.27v13.32a10,10,0,0,1-.81,4.4c-1,2-2.94,3-5.77,3a7.09,7.09,0,0,1-4-1.06,4.1,4.1,0,0,1-1.8-3.32h2.5a2.74,2.74,0,0,0,.71,1.52,3.57,3.57,0,0,0,2.61.82c1.87,0,3.1-.66,3.68-2a11.37,11.37,0,0,0,.48-4.2,4.84,4.84,0,0,1-1.77,1.67,6,6,0,0,1-2.74.54,5.83,5.83,0,0,1-4.15-1.69q-1.77-1.68-1.77-5.58a8.43,8.43,0,0,1,1.79-5.74,5.51,5.51,0,0,1,4.32-2.07A5.38,5.38,0,0,1,503.47,92.9Zm.3,2.64a3.56,3.56,0,0,0-2.85-1.31,3.5,3.5,0,0,0-3.53,2.43,9.48,9.48,0,0,0-.51,3.4,6.12,6.12,0,0,0,1,3.77,3.24,3.24,0,0,0,2.69,1.29,3.75,3.75,0,0,0,3.71-2.39,7.71,7.71,0,0,0,.6-3.16A6.14,6.14,0,0,0,503.77,95.54Z" />
|
||||
<path class="cls-8 app-tagline" d="M511,92.31h2.33v2.53a5.91,5.91,0,0,1,1.41-1.8A3.69,3.69,0,0,1,517.3,92l.23,0,.56,0v2.6a2.08,2.08,0,0,0-.4-.05l-.41,0a3.48,3.48,0,0,0-2.85,1.2,4.14,4.14,0,0,0-1,2.75V107H511Z" />
|
||||
<path class="cls-8 app-tagline" d="M529.27,92.72a5.44,5.44,0,0,1,2.21,1.89,6.52,6.52,0,0,1,1,2.58,16.6,16.6,0,0,1,.22,3.23H522a6.34,6.34,0,0,0,1,3.59,3.49,3.49,0,0,0,3,1.35,3.9,3.9,0,0,0,3-1.28,4.37,4.37,0,0,0,.9-1.72h2.42a5,5,0,0,1-.63,1.8,6.34,6.34,0,0,1-1.21,1.62,5.71,5.71,0,0,1-2.75,1.48,8.65,8.65,0,0,1-2,.21,6.11,6.11,0,0,1-4.6-2,7.79,7.79,0,0,1-1.89-5.58,8.41,8.41,0,0,1,1.9-5.72,6.26,6.26,0,0,1,5-2.21A6.59,6.59,0,0,1,529.27,92.72Zm.88,5.74a6.46,6.46,0,0,0-.69-2.55,3.54,3.54,0,0,0-3.35-1.78,3.72,3.72,0,0,0-2.82,1.22,4.69,4.69,0,0,0-1.21,3.11Z" />
|
||||
<path class="cls-8 app-tagline" d="M537.9,100.47a5.6,5.6,0,0,0,.78,2.78q1.3,2,4.59,2a7.9,7.9,0,0,0,2.69-.44,3.09,3.09,0,0,0,2.34-3,2.64,2.64,0,0,0-1-2.33,9.55,9.55,0,0,0-3.15-1.19l-2.64-.62a11.41,11.41,0,0,1-3.65-1.33A4.23,4.23,0,0,1,536,92.54a5.86,5.86,0,0,1,1.83-4.44A7.16,7.16,0,0,1,543,86.37a8.75,8.75,0,0,1,5.22,1.52,5.57,5.57,0,0,1,2.15,4.87h-2.56a5.12,5.12,0,0,0-.84-2.47c-.79-1.05-2.14-1.57-4.05-1.57a4.51,4.51,0,0,0-3.31,1,3.2,3.2,0,0,0-1,2.35,2.33,2.33,0,0,0,1.19,2.16,16.76,16.76,0,0,0,3.54,1.09l2.73.65a8.15,8.15,0,0,1,3,1.27,4.81,4.81,0,0,1,1.86,4.09,5.14,5.14,0,0,1-2.37,4.77,10.46,10.46,0,0,1-5.5,1.43,8.07,8.07,0,0,1-5.72-1.91,6.57,6.57,0,0,1-2-5.16Z" />
|
||||
<path class="cls-8 app-tagline" d="M573.17,106.9l-1.36,1.65-3.11-2.36a11.33,11.33,0,0,1-2.43,1,10.29,10.29,0,0,1-2.85.37,9.18,9.18,0,0,1-7.32-3.06A11.71,11.71,0,0,1,553.76,97a11.9,11.9,0,0,1,2-7,8.78,8.78,0,0,1,7.69-3.72c3.54,0,6.17,1.14,7.87,3.42a11.08,11.08,0,0,1,2,6.82,14.28,14.28,0,0,1-.48,3.73,9.67,9.67,0,0,1-2.44,4.46ZM565.35,105a3.36,3.36,0,0,0,1.29-.47l-2.22-1.72,1.37-1.68,2.62,2A7.5,7.5,0,0,0,570.1,100a13.76,13.76,0,0,0,.45-3.39,8.48,8.48,0,0,0-1.85-5.7,6.35,6.35,0,0,0-5.07-2.17,6.6,6.6,0,0,0-5.15,2.08q-1.9,2.07-1.9,6.38A8.64,8.64,0,0,0,558.4,103a6.63,6.63,0,0,0,5.36,2.15A11.24,11.24,0,0,0,565.35,105Z" />
|
||||
<path class="cls-8 app-tagline" d="M576.58,86.87h2.72v17.69h10.08V107h-12.8Z" />
|
||||
<line class="cls-9 app-name-underline" x1="219.17" y1="66.5" x2="384.17" y2="66.5" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<h4>{{ _('Feature rich') }} | {{ _('Maximises PostgreSQL') }} | {{ _('Open Source') }} </h4>
|
||||
<p>
|
||||
{{ _('pgAdmin is an Open Source administration and management tool for the PostgreSQL database. It includes a graphical administration interface, an SQL query tool, a procedural code debugger and much more. The tool is designed to answer the needs of developers, DBAs and system administrators alike.') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-12">
|
||||
<div class="card ">
|
||||
<div class="card-header">{{ _('Quick Links') }}</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="row">
|
||||
<div class="col-6 dashboard-link">
|
||||
<a href="#" onclick="pgAdmin.Dashboard.add_new_server()">
|
||||
<span class="fa fa-4x dashboard-icon fa-server" aria-hidden="true"></span><br/>
|
||||
{{ _('Add New Server') }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 dashboard-link">
|
||||
<a href="#" onclick="pgAdmin.Preferences.show()">
|
||||
<span id="mnu_preferences" class="fa fa-4x dashboard-icon fa-cogs"
|
||||
aria-hidden="true"></span><br/>
|
||||
{{ _('Configure pgAdmin') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-12">
|
||||
<div class="card ">
|
||||
<div class="card-header">{{ _('Getting Started') }}</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="row">
|
||||
<div class="col-3 dashboard-link">
|
||||
<a href="http://www.postgresql.org/docs" target="postgres_help">
|
||||
<span class="fa fa-4x dashboard-icon dashboard-pg-doc" aria-hidden="true"></span><br/>
|
||||
{{ _('PostgreSQL Documentation') }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-3 dashboard-link">
|
||||
<a href="https://www.pgadmin.org" target="pgadmin_website">
|
||||
<span class="fa fa-4x dashboard-icon fa-globe" aria-hidden="true"></span><br/>
|
||||
{{ _('pgAdmin Website') }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-3 dashboard-link">
|
||||
<a href="http://planet.postgresql.org" target="planet_website">
|
||||
<span class="fa fa-4x dashboard-icon fa-book" aria-hidden="true"></span><br/>
|
||||
{{ _('Planet PostgreSQL') }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-3 dashboard-link">
|
||||
<a href="http://www.postgresql.org/community" target="postgres_website">
|
||||
<span class="fa fa-4x dashboard-icon fa-users" aria-hidden="true"></span><br/>
|
||||
{{ _('Community Support') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -16,6 +16,7 @@ import Notify from '../../../../static/js/helpers/Notifier';
|
||||
import getApiInstance from 'sources/api_instance';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { getURL } from '../../../static/utils/utils';
|
||||
import Loader from 'sources/components/Loader';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
emptyPanel: {
|
||||
@ -71,7 +72,7 @@ function parseData(data, node) {
|
||||
export default function Dependencies({ nodeData, item, node, ...props }) {
|
||||
const classes = useStyles();
|
||||
const [tableData, setTableData] = React.useState([]);
|
||||
|
||||
const [loaderText, setLoaderText] = React.useState('');
|
||||
const [msg, setMsg] = React.useState('');
|
||||
var columns = [
|
||||
{
|
||||
@ -79,14 +80,14 @@ export default function Dependencies({ nodeData, item, node, ...props }) {
|
||||
accessor: 'type',
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: true,
|
||||
disableGlobalFilter: false,
|
||||
},
|
||||
{
|
||||
Header: 'Name',
|
||||
accessor: 'name',
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: true,
|
||||
disableGlobalFilter: false,
|
||||
},
|
||||
{
|
||||
Header: 'Restriction',
|
||||
@ -114,7 +115,7 @@ export default function Dependencies({ nodeData, item, node, ...props }) {
|
||||
);
|
||||
if (node.hasDepends) {
|
||||
const api = getApiInstance();
|
||||
|
||||
setLoaderText('Loading...');
|
||||
api({
|
||||
url: url,
|
||||
type: 'GET',
|
||||
@ -123,8 +124,10 @@ export default function Dependencies({ nodeData, item, node, ...props }) {
|
||||
if (res.data.length > 0) {
|
||||
let data = parseData(res.data, node);
|
||||
setTableData(data);
|
||||
setLoaderText('');
|
||||
} else {
|
||||
setMsg(message);
|
||||
setLoaderText('');
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
@ -157,10 +160,12 @@ export default function Dependencies({ nodeData, item, node, ...props }) {
|
||||
></PgTable>
|
||||
) : (
|
||||
<div className={classes.emptyPanel}>
|
||||
<div className={classes.panelIcon}>
|
||||
<i className="fa fa-exclamation-circle"></i>
|
||||
<span className={classes.panelMessage}>{gettext(msg)}</span>
|
||||
</div>
|
||||
{loaderText ? (<Loader message={loaderText} className={classes.loading} />) :
|
||||
<div className={classes.panelIcon}>
|
||||
<i className="fa fa-exclamation-circle"></i>
|
||||
<span className={classes.panelMessage}>{gettext(msg)}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
@ -16,6 +16,7 @@ import Notify from '../../../../static/js/helpers/Notifier';
|
||||
import getApiInstance from 'sources/api_instance';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { getURL } from '../../../static/utils/utils';
|
||||
import Loader from 'sources/components/Loader';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
emptyPanel: {
|
||||
@ -71,7 +72,7 @@ function parseData(data, node) {
|
||||
export default function Dependents({ nodeData, item, node, ...props }) {
|
||||
const classes = useStyles();
|
||||
const [tableData, setTableData] = React.useState([]);
|
||||
|
||||
const [loaderText, setLoaderText] = React.useState('');
|
||||
const [msg, setMsg] = React.useState('');
|
||||
|
||||
var columns = [
|
||||
@ -80,14 +81,14 @@ export default function Dependents({ nodeData, item, node, ...props }) {
|
||||
accessor: 'type',
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: true,
|
||||
disableGlobalFilter: false,
|
||||
},
|
||||
{
|
||||
Header: 'Name',
|
||||
accessor: 'name',
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: true,
|
||||
disableGlobalFilter: false,
|
||||
},
|
||||
{
|
||||
Header: 'Restriction',
|
||||
@ -115,6 +116,7 @@ export default function Dependents({ nodeData, item, node, ...props }) {
|
||||
);
|
||||
if (node.hasDepends && !nodeData.is_collection) {
|
||||
const api = getApiInstance();
|
||||
setLoaderText('Loading...');
|
||||
api({
|
||||
url: url,
|
||||
type: 'GET',
|
||||
@ -125,6 +127,7 @@ export default function Dependents({ nodeData, item, node, ...props }) {
|
||||
setTableData(data);
|
||||
} else {
|
||||
setMsg(message);
|
||||
setLoaderText('');
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
@ -158,11 +161,15 @@ export default function Dependents({ nodeData, item, node, ...props }) {
|
||||
></PgTable>
|
||||
) : (
|
||||
<div className={classes.emptyPanel}>
|
||||
<div className={classes.panelIcon}>
|
||||
<i className="fa fa-exclamation-circle"></i>
|
||||
<span className={classes.panelMessage}>{gettext(msg)}</span>
|
||||
</div>
|
||||
{loaderText ? (<Loader message={loaderText} className={classes.loading} />) :
|
||||
|
||||
<div className={classes.panelIcon}>
|
||||
<i className="fa fa-exclamation-circle"></i>
|
||||
<span className={classes.panelMessage}>{gettext(msg)}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
331
web/pgadmin/misc/properties/CollectionNodeProperties.jsx
Normal file
331
web/pgadmin/misc/properties/CollectionNodeProperties.jsx
Normal file
@ -0,0 +1,331 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
import React from 'react';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import getApiInstance from 'sources/api_instance';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { Box, Switch } from '@material-ui/core';
|
||||
import { generateCollectionURL } from '../../browser/static/js/node_ajax';
|
||||
import Notify from '../../static/js/helpers/Notifier';
|
||||
import gettext from 'sources/gettext';
|
||||
import 'wcdocker';
|
||||
import PgTable from 'sources/components/PgTable';
|
||||
import Theme from 'sources/Theme';
|
||||
import PropTypes from 'prop-types';
|
||||
import { PgIconButton } from '../../static/js/components/Buttons';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
emptyPanel: {
|
||||
minHeight: '100%',
|
||||
minWidth: '100%',
|
||||
background: theme.palette.grey[400],
|
||||
overflow: 'auto',
|
||||
padding: '7.5px',
|
||||
},
|
||||
panelIcon: {
|
||||
width: '80%',
|
||||
margin: '0 auto',
|
||||
marginTop: '25px !important',
|
||||
position: 'relative',
|
||||
textAlign: 'center',
|
||||
},
|
||||
panelMessage: {
|
||||
marginLeft: '0.5rem',
|
||||
fontSize: '0.875rem',
|
||||
},
|
||||
searchPadding: {
|
||||
flex: 2.5
|
||||
},
|
||||
searchInput: {
|
||||
flex: 1,
|
||||
margin: '4 0 4 0',
|
||||
borderLeft: 'none',
|
||||
paddingLeft: 5
|
||||
},
|
||||
propertiesPanel: {
|
||||
height: '100%'
|
||||
},
|
||||
autoResizer: {
|
||||
height: '100% !important',
|
||||
width: '100% !important',
|
||||
background: theme.palette.grey[400],
|
||||
padding: '8px',
|
||||
},
|
||||
dropButton: {
|
||||
marginRight: '5px !important'
|
||||
},
|
||||
readOnlySwitch: {
|
||||
opacity: 0.75,
|
||||
'& .MuiSwitch-track': {
|
||||
opacity: theme.palette.action.disabledOpacity,
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
export function CollectionNodeView({
|
||||
node,
|
||||
treeNodeInfo,
|
||||
itemNodeData,
|
||||
item,
|
||||
pgBrowser
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
|
||||
const [data, setData] = React.useState([]);
|
||||
const [infoMsg, setInfoMsg] = React.useState('Please select an object in the tree view.');
|
||||
const [selectedObject, setSelectedObject] = React.useState([]);
|
||||
const [reload, setReload] = React.useState();
|
||||
|
||||
const [pgTableColumns, setPgTableColumns] = React.useState([
|
||||
{
|
||||
Header: 'properties',
|
||||
accessor: 'Properties',
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
},
|
||||
{
|
||||
Header: 'value',
|
||||
accessor: 'value',
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
},
|
||||
]);
|
||||
|
||||
const getTableSelectedRows = (selRows) => {
|
||||
setSelectedObject(selRows);
|
||||
};
|
||||
|
||||
const onDrop = (type) => {
|
||||
let selRowModels = selectedObject,
|
||||
selRows = [],
|
||||
selItem = pgBrowser.tree.selected(),
|
||||
selectedItemData = selItem ? pgBrowser.tree.itemData(selItem) : null,
|
||||
selNode = selectedItemData && pgBrowser.Nodes[selectedItemData._type],
|
||||
url = undefined,
|
||||
msg = undefined,
|
||||
title = undefined;
|
||||
|
||||
if (selNode && selNode.type && selNode.type == 'coll-constraints') {
|
||||
// In order to identify the constraint type, the type should be passed to the server
|
||||
selRows = selRowModels.map((row) => ({
|
||||
id: row.get('oid'),
|
||||
_type: row.get('_type'),
|
||||
}));
|
||||
} else {
|
||||
selRows = selRowModels.map((row) => row.original.oid);
|
||||
}
|
||||
|
||||
if (selRows.length === 0) {
|
||||
Notify.alert(
|
||||
gettext('Drop Multiple'),
|
||||
gettext('Please select at least one object to delete.')
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selNode) return;
|
||||
|
||||
if (type === 'dropCascade') {
|
||||
url = selNode.generate_url(selItem, 'delete');
|
||||
msg = gettext(
|
||||
'Are you sure you want to drop all the selected objects and all the objects that depend on them?'
|
||||
);
|
||||
title = gettext('DROP CASCADE multiple objects?');
|
||||
} else {
|
||||
url = selNode.generate_url(selItem, 'drop');
|
||||
msg = gettext('Are you sure you want to drop all the selected objects?');
|
||||
title = gettext('DROP multiple objects?');
|
||||
}
|
||||
|
||||
const api = getApiInstance();
|
||||
let dropNodeProperties = function () {
|
||||
api
|
||||
.delete(url, {
|
||||
data: JSON.stringify({ ids: selRows }),
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
})
|
||||
.then(function (res) {
|
||||
if (res.success == 0) {
|
||||
pgBrowser.report_error(res.errormsg, res.info);
|
||||
} else {
|
||||
pgBrowser.Events.trigger(
|
||||
'pgadmin:browser:tree:refresh',
|
||||
selItem || pgBrowser.tree.selected(),
|
||||
{
|
||||
success: function () {
|
||||
pgBrowser.tree.select(selItem);
|
||||
selNode.callbacks.selected.apply(selNode, [selItem]);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
setReload(true);
|
||||
})
|
||||
.catch(function (error) {
|
||||
Notify.error(
|
||||
gettext('Error dropping %s', selectedItemData._label.toLowerCase()),
|
||||
error.message
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
if (confirm) {
|
||||
Notify.confirm(title, msg, dropNodeProperties, null);
|
||||
} else {
|
||||
dropNodeProperties();
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (node){
|
||||
|
||||
let nodeObj =
|
||||
pgAdmin.Browser.Nodes[itemNodeData?._type.replace('coll-', '')];
|
||||
|
||||
let schema = nodeObj.getSchema.call(nodeObj, treeNodeInfo, itemNodeData);
|
||||
let url = generateCollectionURL.call(nodeObj, item, 'properties');
|
||||
|
||||
const api = getApiInstance();
|
||||
|
||||
let tableColumns = [];
|
||||
var column = {};
|
||||
|
||||
if (itemNodeData._type.indexOf('coll-') > -1) {
|
||||
schema.fields.forEach((field) => {
|
||||
if (node.columns.indexOf(field.id) > -1) {
|
||||
if (field.label.indexOf('?') > -1) {
|
||||
column = {
|
||||
Header: field.label,
|
||||
accessor: field.id,
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
// eslint-disable-next-line react/display-name
|
||||
Cell: ({ value }) => {
|
||||
return (<Switch color="primary" checked={value} className={classes.readOnlySwitch} value={value} readOnly title={String(value)} />);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
column = {
|
||||
Header: field.label,
|
||||
accessor: field.id,
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: false,
|
||||
};
|
||||
}
|
||||
tableColumns.push(column);
|
||||
}
|
||||
});
|
||||
api({
|
||||
url: url,
|
||||
type: 'GET',
|
||||
})
|
||||
.then((res) => {
|
||||
res.data.forEach((element) => {
|
||||
element['icon'] = '';
|
||||
});
|
||||
setPgTableColumns(tableColumns);
|
||||
setData(res.data);
|
||||
setInfoMsg('No properties are available for the selected object.');
|
||||
})
|
||||
.catch((err) => {
|
||||
Notify.alert(
|
||||
gettext('Failed to retrieve data from the server.'),
|
||||
gettext(err.message)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [itemNodeData, node, item, reload]);
|
||||
|
||||
const customHeader = () => {
|
||||
return (
|
||||
<Box >
|
||||
<PgIconButton
|
||||
className={classes.dropButton}
|
||||
variant="outlined"
|
||||
icon={<i className='fa fa-trash-alt delete_multiple' aria-hidden="true" role="img"></i>}
|
||||
aria-label="Delete/Drop"
|
||||
title={gettext('Delete/Drop')}
|
||||
onClick={() => {
|
||||
onDrop('drop');
|
||||
}}
|
||||
disabled={
|
||||
(selectedObject.length > 0)
|
||||
? !node.canDrop
|
||||
: true
|
||||
}
|
||||
></PgIconButton>
|
||||
<PgIconButton
|
||||
className={classes.dropButton}
|
||||
variant="outlined"
|
||||
icon={<i className='pg-font-icon icon-drop_cascade delete_multiple_cascade' aria-hidden="true" role="img"></i>}
|
||||
aria-label="Drop Cascade"
|
||||
title={gettext('Drop Cascade')}
|
||||
onClick={() => {
|
||||
onDrop('dropCascade');
|
||||
}}
|
||||
disabled={
|
||||
(selectedObject.length > 0)
|
||||
? !node.canDropCascade
|
||||
: true
|
||||
}
|
||||
></PgIconButton>
|
||||
</Box>);
|
||||
};
|
||||
|
||||
return (
|
||||
<Theme>
|
||||
<Box className={classes.propertiesPanel}>
|
||||
{data.length > 0 ?
|
||||
(
|
||||
<PgTable
|
||||
isSelectRow={!('catalog' in treeNodeInfo) && (itemNodeData.label !== 'Catalogs')}
|
||||
customHeader={customHeader}
|
||||
className={classes.autoResizer}
|
||||
columns={pgTableColumns}
|
||||
data={data}
|
||||
type={'panel'}
|
||||
isSearch={false}
|
||||
getSelectedRows={getTableSelectedRows}
|
||||
/>
|
||||
)
|
||||
:
|
||||
(
|
||||
<div className={classes.emptyPanel}>
|
||||
<div className={classes.panelIcon}>
|
||||
<i className="fa fa-exclamation-circle"></i>
|
||||
<span className={classes.panelMessage}>{gettext(infoMsg)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
</Box>
|
||||
</Theme>
|
||||
);
|
||||
}
|
||||
|
||||
CollectionNodeView.propTypes = {
|
||||
node: PropTypes.func,
|
||||
itemData: PropTypes.object,
|
||||
itemNodeData: PropTypes.object,
|
||||
treeNodeInfo: PropTypes.object,
|
||||
item: PropTypes.object,
|
||||
pgBrowser: PropTypes.object,
|
||||
preferences: PropTypes.object,
|
||||
sid: PropTypes.number,
|
||||
did: PropTypes.number,
|
||||
row: PropTypes.object,
|
||||
serverConnected: PropTypes.bool,
|
||||
value: PropTypes.bool,
|
||||
};
|
114
web/pgadmin/misc/sql/static/js/SQL.jsx
Normal file
114
web/pgadmin/misc/sql/static/js/SQL.jsx
Normal file
@ -0,0 +1,114 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import { generateNodeUrl } from '../../../../browser/static/js/node_ajax';
|
||||
import gettext from 'sources/gettext';
|
||||
import PropTypes from 'prop-types';
|
||||
import Notify from '../../../../static/js/helpers/Notifier';
|
||||
import getApiInstance from 'sources/api_instance';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import CodeMirror from '../../../../static/js/components/CodeMirror';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
textArea: {
|
||||
height: '100% !important',
|
||||
width: '100% !important',
|
||||
background: theme.palette.grey[400],
|
||||
overflow: 'auto !important',
|
||||
minHeight: '100%',
|
||||
minWidth: '100%',
|
||||
},
|
||||
}));
|
||||
|
||||
export default function SQL({ nodeData, node, ...props }) {
|
||||
const classes = useStyles();
|
||||
const [nodeSQL, setNodeSQL] = React.useState('');
|
||||
|
||||
const [msg, setMsg] = React.useState('');
|
||||
|
||||
useEffect(() => {
|
||||
var sql = '-- ' + gettext('Please select an object in the tree view.');
|
||||
if (node) {
|
||||
let url = generateNodeUrl.call(
|
||||
node,
|
||||
props.treeNodeInfo,
|
||||
'sql',
|
||||
nodeData,
|
||||
true,
|
||||
node.url_jump_after_node
|
||||
);
|
||||
sql =
|
||||
'-- ' + gettext('No SQL could be generated for the selected object.');
|
||||
|
||||
if (node.hasSQL) {
|
||||
const api = getApiInstance();
|
||||
|
||||
api({
|
||||
url: url,
|
||||
type: 'GET',
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.data.length > 0) {
|
||||
setNodeSQL(res.data);
|
||||
} else {
|
||||
setMsg(sql);
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
Notify.alert(
|
||||
gettext('Failed to retrieve data from the server.'),
|
||||
gettext(e.message)
|
||||
);
|
||||
// show failed message.
|
||||
setMsg(gettext('Failed to retrieve data from the server.'));
|
||||
});
|
||||
}
|
||||
}
|
||||
if (sql != '') {
|
||||
setMsg(sql);
|
||||
}
|
||||
return () => {
|
||||
setNodeSQL([]);
|
||||
};
|
||||
}, [nodeData]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{nodeSQL.length > 0 ? (
|
||||
<CodeMirror
|
||||
className={classes.textArea}
|
||||
value={nodeSQL}
|
||||
options={{
|
||||
lineNumbers: true,
|
||||
mode: 'text/x-pgsql',
|
||||
readOnly: true,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<CodeMirror
|
||||
className={classes.textArea}
|
||||
value={msg}
|
||||
options={{
|
||||
lineNumbers: true,
|
||||
mode: 'text/x-pgsql',
|
||||
readOnly: true,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
SQL.propTypes = {
|
||||
res: PropTypes.array,
|
||||
nodeData: PropTypes.object,
|
||||
treeNodeInfo: PropTypes.object,
|
||||
node: PropTypes.func,
|
||||
};
|
@ -1,207 +0,0 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import Notify from '../../../../static/js/helpers/Notifier';
|
||||
|
||||
define('misc.sql', [
|
||||
'sources/gettext', 'underscore', 'jquery',
|
||||
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.alertifyjs',
|
||||
], function(gettext, _, $, pgAdmin, pgBrowser, Alertify) {
|
||||
|
||||
pgBrowser.ShowNodeSQL = pgBrowser.ShowNodeSQL || {};
|
||||
|
||||
if (pgBrowser.ShowNodeSQL.initialized) {
|
||||
return pgBrowser.ShowNodeSQL;
|
||||
}
|
||||
var wcDocker = window.wcDocker;
|
||||
|
||||
_.extend(pgBrowser.ShowNodeSQL, {
|
||||
init: function() {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
this.initialized = true;
|
||||
_.bindAll(this, 'showSQL', 'sqlPanelVisibilityChanged');
|
||||
|
||||
var sqlPanels;
|
||||
this.sqlPanels = sqlPanels = pgBrowser.docker.findPanels('sql');
|
||||
|
||||
// We will listend to the visibility change of the SQL panel
|
||||
pgBrowser.Events.on(
|
||||
'pgadmin-browser:panel-sql:' + wcDocker.EVENT.VISIBILITY_CHANGED,
|
||||
this.sqlPanelVisibilityChanged
|
||||
);
|
||||
|
||||
pgBrowser.Events.on(
|
||||
'pgadmin:browser:node:updated',
|
||||
function() {
|
||||
if (this.sqlPanels && this.sqlPanels.length) {
|
||||
$(this.sqlPanels[0]).data('node-prop', '');
|
||||
this.sqlPanelVisibilityChanged(this.sqlPanels[0]);
|
||||
}
|
||||
}, this
|
||||
);
|
||||
|
||||
// Hmm.. Did we find the SQL panel, and is it visible (opened)?
|
||||
// If that is the case - we need to listen the browser tree selection
|
||||
// events.
|
||||
if (sqlPanels.length == 0) {
|
||||
pgBrowser.Events.on(
|
||||
'pgadmin-browser:panel-sql:' + wcDocker.EVENT.INIT,
|
||||
function() {
|
||||
if ((sqlPanels[0].isVisible()) || sqlPanels.length != 1) {
|
||||
pgBrowser.Events.on(
|
||||
'pgadmin-browser:tree:selected', this.showSQL
|
||||
);
|
||||
pgBrowser.Events.on(
|
||||
'pgadmin-browser:tree:reactSelected', this.reactShowSQL
|
||||
);
|
||||
pgBrowser.Events.on(
|
||||
'pgadmin-browser:tree:refreshing', this.refreshSQL, this
|
||||
);
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
} else {
|
||||
if ((sqlPanels[0].isVisible()) || sqlPanels.length != 1) {
|
||||
pgBrowser.Events.on('pgadmin-browser:tree:selected', this.showSQL);
|
||||
pgBrowser.Events.on('pgadmin-browser:tree:reactSelected', this.reactShowSQL);
|
||||
pgBrowser.Events.on(
|
||||
'pgadmin-browser:tree:refreshing', this.refreshSQL, this
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
refreshSQL: function(item, data, node) {
|
||||
var that = this,
|
||||
cache_flag = {
|
||||
node_type: data._type,
|
||||
url: node.generate_url(item, 'sql', data, true),
|
||||
};
|
||||
|
||||
if (_.isEqual($(that.sqlPanels[0]).data('node-prop'), cache_flag)) {
|
||||
// Reset the current item selection
|
||||
$(that.sqlPanels[0]).data('node-prop', '');
|
||||
that.showSQL(item, data, node);
|
||||
}
|
||||
},
|
||||
showSQL: function(item, data, node) {
|
||||
/**
|
||||
* We can't start fetching the SQL immediately, it is possible - the user
|
||||
* is just using keyboards to select the node, and just traversing
|
||||
* through. We will wait for some time before fetching the Reversed
|
||||
* Engineering SQL.
|
||||
**/
|
||||
|
||||
this.timeout && clearTimeout(this.timeout);
|
||||
|
||||
var that = this;
|
||||
this.timeout = setTimeout(
|
||||
function() {
|
||||
var sql = '-- ' + gettext('Please select an object in the tree view.');
|
||||
if (node) {
|
||||
sql = '-- ' + gettext('No SQL could be generated for the selected object.');
|
||||
var n_type = data._type,
|
||||
url = node.generate_url(item, 'sql', data, true),
|
||||
treeHierarchy = pgBrowser.tree.getTreeNodeHierarchy(item),
|
||||
cache_flag = {
|
||||
node_type: n_type,
|
||||
url: url,
|
||||
};
|
||||
|
||||
// Avoid unnecessary reloads
|
||||
if (_.isEqual($(that.sqlPanels[0]).data('node-prop'), cache_flag)) {
|
||||
return;
|
||||
}
|
||||
// Cache the current IDs for next time
|
||||
$(that.sqlPanels[0]).data('node-prop', cache_flag);
|
||||
|
||||
if (node.hasSQL) {
|
||||
|
||||
sql = '';
|
||||
var timer;
|
||||
var ajaxHook = function() {
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'GET',
|
||||
beforeSend: function(xhr) {
|
||||
xhr.setRequestHeader(
|
||||
pgAdmin.csrf_token_header, pgAdmin.csrf_token
|
||||
);
|
||||
// Generate a timer for the request
|
||||
timer = setTimeout(function() {
|
||||
// Notify user if request is taking longer than 1 second
|
||||
pgAdmin.Browser.editor.setValue(
|
||||
gettext('Retrieving data from the server...')
|
||||
);
|
||||
}, 1000);
|
||||
},
|
||||
}).done(function(res) {
|
||||
if (pgAdmin.Browser.editor.getValue() != res) {
|
||||
pgAdmin.Browser.editor.setValue(res);
|
||||
}
|
||||
clearTimeout(timer);
|
||||
}).fail(function(xhr, error, message) {
|
||||
var _label = treeHierarchy[n_type].label;
|
||||
pgBrowser.Events.trigger(
|
||||
'pgadmin:node:retrieval:error', 'sql', xhr, error, message, item
|
||||
);
|
||||
if (!Alertify.pgHandleItemError(xhr, error, message, {
|
||||
item: item,
|
||||
info: treeHierarchy,
|
||||
})) {
|
||||
Notify.pgNotifier(
|
||||
error, xhr,
|
||||
gettext('Error retrieving the information - %s', message || _label),
|
||||
function(msg) {
|
||||
if(msg === 'CRYPTKEY_SET') {
|
||||
ajaxHook();
|
||||
} else {
|
||||
console.warn(arguments);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
ajaxHook();
|
||||
}
|
||||
}
|
||||
|
||||
if (sql != '') {
|
||||
pgAdmin.Browser.editor.setValue(sql);
|
||||
}
|
||||
}, 400);
|
||||
},
|
||||
sqlPanelVisibilityChanged: function(panel) {
|
||||
if (panel.isVisible()) {
|
||||
var t = pgBrowser.tree,
|
||||
i = t.selected(),
|
||||
d = i && t.itemData(i),
|
||||
n = i && d && pgBrowser.Nodes[d._type];
|
||||
|
||||
pgBrowser.ShowNodeSQL.showSQL.apply(pgBrowser.ShowNodeSQL, [i, d, n]);
|
||||
|
||||
// We will start listening the tree selection event.
|
||||
pgBrowser.Events.on('pgadmin-browser:tree:selected', pgBrowser.ShowNodeSQL.showSQL);
|
||||
pgBrowser.Events.on(
|
||||
'pgadmin-browser:tree:refreshing', pgBrowser.ShowNodeSQL.refreshSQL, this
|
||||
);
|
||||
} else {
|
||||
// We don't need to listen the tree item selection event.
|
||||
pgBrowser.Events.off('pgadmin-browser:tree:selected', pgBrowser.ShowNodeSQL.showSQL);
|
||||
pgBrowser.Events.off(
|
||||
'pgadmin-browser:tree:refreshing', pgBrowser.ShowNodeSQL.refreshSQL, this
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return pgBrowser.ShowNodeSQL;
|
||||
});
|
@ -17,7 +17,7 @@ import getApiInstance from 'sources/api_instance';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import sizePrettify from 'sources/size_prettify';
|
||||
import { getURL } from '../../../static/utils/utils';
|
||||
|
||||
import Loader from 'sources/components/Loader';
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
emptyPanel: {
|
||||
minHeight: '100%',
|
||||
@ -37,12 +37,18 @@ const useStyles = makeStyles((theme) => ({
|
||||
marginLeft: '0.5rem',
|
||||
fontSize: '0.875rem',
|
||||
},
|
||||
loading: {
|
||||
marginLeft: '0.5rem',
|
||||
fontSize: '0.875rem',
|
||||
colour: theme.palette.grey[400]
|
||||
},
|
||||
autoResizer: {
|
||||
height: '100% !important',
|
||||
width: '100% !important',
|
||||
background: theme.palette.grey[400],
|
||||
padding: '7.5px',
|
||||
overflow: 'auto !important',
|
||||
overflowX: 'auto !important',
|
||||
overflowY: 'hidden !important',
|
||||
minHeight: '100%',
|
||||
minWidth: '100%',
|
||||
},
|
||||
@ -58,7 +64,7 @@ function getColumn(data, singleLineStatistics) {
|
||||
accessor: row.name,
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: true,
|
||||
disableGlobalFilter: false,
|
||||
};
|
||||
columns.push(column);
|
||||
});
|
||||
@ -71,14 +77,14 @@ function getColumn(data, singleLineStatistics) {
|
||||
accessor: 'name',
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: true,
|
||||
disableGlobalFilter: false,
|
||||
},
|
||||
{
|
||||
Header: 'Value',
|
||||
accessor: 'value',
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: true,
|
||||
disableGlobalFilter: false,
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -142,20 +148,21 @@ export default function Statistics({ nodeData, item, node, ...props }) {
|
||||
const [tableData, setTableData] = React.useState([]);
|
||||
|
||||
const [msg, setMsg] = React.useState('');
|
||||
const [loaderText, setLoaderText] = React.useState('');
|
||||
const [columns, setColumns] = React.useState([
|
||||
{
|
||||
Header: 'Statictics',
|
||||
accessor: 'name',
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: true,
|
||||
disableGlobalFilter: false,
|
||||
},
|
||||
{
|
||||
Header: 'Value',
|
||||
accessor: 'value',
|
||||
sortble: true,
|
||||
resizable: false,
|
||||
disableGlobalFilter: true,
|
||||
disableGlobalFilter: false,
|
||||
},
|
||||
]);
|
||||
|
||||
@ -170,6 +177,7 @@ export default function Statistics({ nodeData, item, node, ...props }) {
|
||||
|
||||
const api = getApiInstance();
|
||||
if (node.hasStatistics) {
|
||||
setLoaderText('Loading...');
|
||||
api({
|
||||
url: url,
|
||||
type: 'GET',
|
||||
@ -180,6 +188,7 @@ export default function Statistics({ nodeData, item, node, ...props }) {
|
||||
if (!_.isUndefined(colData)) {
|
||||
setColumns(colData);
|
||||
}
|
||||
setLoaderText('');
|
||||
})
|
||||
.catch((e) => {
|
||||
Notify.alert(
|
||||
@ -187,10 +196,12 @@ export default function Statistics({ nodeData, item, node, ...props }) {
|
||||
gettext(e.message)
|
||||
);
|
||||
// show failed message.
|
||||
setLoaderText('');
|
||||
setMsg(gettext('Failed to retrieve data from the server.'));
|
||||
});
|
||||
} else {
|
||||
setMsg(message);
|
||||
setLoaderText('');
|
||||
setMsg('No statistics are available for the selected object.');
|
||||
}
|
||||
}
|
||||
if (message != '') {
|
||||
@ -213,10 +224,12 @@ export default function Statistics({ nodeData, item, node, ...props }) {
|
||||
></PgTable>
|
||||
) : (
|
||||
<div className={classes.emptyPanel}>
|
||||
<div className={classes.panelIcon}>
|
||||
<i className="fa fa-exclamation-circle"></i>
|
||||
<span className={classes.panelMessage}>{gettext(msg)}</span>
|
||||
</div>
|
||||
{loaderText ? (<Loader message={loaderText} className={classes.loading} />) :
|
||||
<div className={classes.panelIcon}>
|
||||
<i className="fa fa-exclamation-circle"></i>
|
||||
<span className={classes.panelMessage}>{gettext(msg)}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
@ -92,7 +92,8 @@ export default function(basicSettings) {
|
||||
activeStepFg: '#FFFFFF',
|
||||
stepBg: '#FFFFFF',
|
||||
stepFg: '#000',
|
||||
toggleBtnBg: '#000'
|
||||
toggleBtnBg: '#000',
|
||||
colorFg: '#FFFFFF',
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -90,7 +90,8 @@ export default function(basicSettings) {
|
||||
activeStepFg: '#010b15',
|
||||
stepBg: '#FFFFFF',
|
||||
stepFg: '#000',
|
||||
toggleBtnBg: '#6B6B6B'
|
||||
toggleBtnBg: '#6B6B6B',
|
||||
colorFg: '#FFFFFF',
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -85,6 +85,7 @@ export default function(basicSettings) {
|
||||
padding: '5px 8px',
|
||||
},
|
||||
borderColor: '#dde0e6',
|
||||
colorFg: '#222222',
|
||||
loader: {
|
||||
backgroundColor: fade('#000', 0.65),
|
||||
color: '#fff',
|
||||
@ -102,6 +103,7 @@ export default function(basicSettings) {
|
||||
toggleBtnBg: '#000',
|
||||
editorToolbarBg: '#ebeef3',
|
||||
datagridBg: '#fff',
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -8,13 +8,26 @@
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import React from 'react';
|
||||
import { useTable, useRowSelect, useSortBy, useResizeColumns, useFlexLayout, useGlobalFilter } from 'react-table';
|
||||
import { FixedSizeList } from 'react-window';
|
||||
import {
|
||||
useTable,
|
||||
useRowSelect,
|
||||
useSortBy,
|
||||
useResizeColumns,
|
||||
useFlexLayout,
|
||||
useGlobalFilter,
|
||||
useExpanded,
|
||||
} from 'react-table';
|
||||
import { VariableSizeList } from 'react-window';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import clsx from 'clsx';
|
||||
import PropTypes from 'prop-types';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { Checkbox } from '@material-ui/core';
|
||||
import { Checkbox, Box } from '@material-ui/core';
|
||||
import { InputText } from './FormComponents';
|
||||
import FormView from 'sources/SchemaView';
|
||||
import _ from 'lodash';
|
||||
import gettext from 'sources/gettext';
|
||||
|
||||
/* eslint-disable react/display-name */
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
@ -29,22 +42,56 @@ const useStyles = makeStyles((theme) => ({
|
||||
width: '100% !important',
|
||||
},
|
||||
fixedSizeList: {
|
||||
// position: 'relative',
|
||||
direction: 'ltr',
|
||||
overflowX: 'hidden !important',
|
||||
overflow: 'overlay !important'
|
||||
overflow: 'overlay !important',
|
||||
},
|
||||
customHeader:{
|
||||
marginTop: '12px',
|
||||
marginLeft: '4px'
|
||||
},
|
||||
searchBox: {
|
||||
marginBottom: '5px',
|
||||
display: 'flex',
|
||||
background: theme.palette.background.default
|
||||
},
|
||||
tableContentWidth: {
|
||||
width: 'calc(100% - 3px)',
|
||||
},
|
||||
searchPadding: {
|
||||
flex: 2.5
|
||||
},
|
||||
searchInput: {
|
||||
flex: 1,
|
||||
marginTop: 8,
|
||||
borderLeft: 'none',
|
||||
paddingLeft: 5,
|
||||
marginRight: 8,
|
||||
marginBottom: 8,
|
||||
|
||||
},
|
||||
table: {
|
||||
flexGrow:1,
|
||||
minHeight:0,
|
||||
flexGrow: 1,
|
||||
minHeight: 0,
|
||||
borderSpacing: 0,
|
||||
width: '100%',
|
||||
overflow: 'hidden',
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
border: '1px solid'+ theme.palette.grey[400]
|
||||
},
|
||||
extraTable:{
|
||||
backgroundColor: theme.palette.grey[400],
|
||||
flexGrow:1,
|
||||
pgTableHeadar: {
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden !important',
|
||||
height: '100% !important',
|
||||
flexDirection: 'column'
|
||||
},
|
||||
|
||||
expandedForm: {
|
||||
...theme.mixins.panelBorder,
|
||||
margin: '8px',
|
||||
paddingBottom: '12px',
|
||||
marginRight: '15px',
|
||||
},
|
||||
|
||||
tableCell: {
|
||||
@ -54,15 +101,14 @@ const useStyles = makeStyles((theme) => ({
|
||||
...theme.mixins.panelBorder.right,
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
height: '35px',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
backgroundColor: theme.otherVars.tableBg,
|
||||
// ...theme.mixins.panelBorder.top,
|
||||
...theme.mixins.panelBorder.left,
|
||||
|
||||
},
|
||||
selectCell: {
|
||||
textAlign: 'center'
|
||||
textAlign: 'center',
|
||||
minWidth: '25px'
|
||||
},
|
||||
tableCellHeader: {
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
@ -93,13 +139,57 @@ const useStyles = makeStyles((theme) => ({
|
||||
paddingTop: '0.35em',
|
||||
height: 35,
|
||||
backgroundPosition: '1%',
|
||||
}
|
||||
}),
|
||||
);
|
||||
},
|
||||
emptyPanel: {
|
||||
minHeight: '100%',
|
||||
minWidth: '100%',
|
||||
background: theme.palette.background.default,
|
||||
overflow: 'auto',
|
||||
padding: '7.5px',
|
||||
},
|
||||
panelIcon: {
|
||||
width: '80%',
|
||||
margin: '0 auto',
|
||||
marginTop: '25px !important',
|
||||
position: 'relative',
|
||||
textAlign: 'center',
|
||||
},
|
||||
panelMessage: {
|
||||
marginLeft: '0.5rem',
|
||||
fontSize: '0.875rem',
|
||||
},
|
||||
expandedIconCell: {
|
||||
backgroundColor: theme.palette.grey[400],
|
||||
...theme.mixins.panelBorder.top,
|
||||
borderBottom: 'none',
|
||||
},
|
||||
btnCell: {
|
||||
padding: theme.spacing(0.5, 0),
|
||||
textAlign: 'center',
|
||||
},
|
||||
}));
|
||||
|
||||
export default function PgTable({ columns, data, isSelectRow, ...props }) {
|
||||
export default function PgTable({ columns, data, isSelectRow, offset=105, ...props }) {
|
||||
// Use the state and functions returned from useTable to build your UI
|
||||
const classes = useStyles();
|
||||
const [searchVal, setSearchVal] = React.useState('');
|
||||
const tableRef = React.useRef();
|
||||
const rowHeights = React.useRef({});
|
||||
const rowRef = React.useRef({});
|
||||
|
||||
function getRowHeight(index, size) {
|
||||
return rowHeights.current[index] + size || 35;
|
||||
}
|
||||
|
||||
const setRowHeight = React.useCallback((index, size) => {
|
||||
if(tableRef.current) {
|
||||
tableRef.current.resetAfterIndex(index);
|
||||
if (!(rowHeights.current.hasOwnProperty(index))){
|
||||
rowHeights.current = { ...rowHeights.current, [index]: size };
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
const defaultColumn = React.useMemo(
|
||||
() => ({
|
||||
minWidth: 150,
|
||||
@ -119,7 +209,8 @@ export default function PgTable({ columns, data, isSelectRow, ...props }) {
|
||||
<>
|
||||
<Checkbox
|
||||
color="primary"
|
||||
ref={resolvedRef} {...rest} />
|
||||
ref={resolvedRef} {...rest}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
},
|
||||
@ -141,9 +232,9 @@ export default function PgTable({ columns, data, isSelectRow, ...props }) {
|
||||
rows,
|
||||
prepareRow,
|
||||
selectedFlatRows,
|
||||
state: { selectedRowIds },
|
||||
state: { selectedRowIds, expanded },
|
||||
setGlobalFilter,
|
||||
setHiddenColumns
|
||||
setHiddenColumns,
|
||||
} = useTable(
|
||||
{
|
||||
columns,
|
||||
@ -153,11 +244,12 @@ export default function PgTable({ columns, data, isSelectRow, ...props }) {
|
||||
},
|
||||
useGlobalFilter,
|
||||
useSortBy,
|
||||
useExpanded,
|
||||
useRowSelect,
|
||||
useResizeColumns,
|
||||
useFlexLayout,
|
||||
hooks => {
|
||||
hooks.visibleColumns.push(CLOUMNS => {
|
||||
(hooks) => {
|
||||
hooks.visibleColumns.push((CLOUMNS) => {
|
||||
if (isSelectRow) {
|
||||
return [
|
||||
// Let's make a column for selection
|
||||
@ -178,7 +270,7 @@ export default function PgTable({ columns, data, isSelectRow, ...props }) {
|
||||
</div>
|
||||
),
|
||||
sortble: false,
|
||||
width: 50,
|
||||
width: 30,
|
||||
minWidth: 0,
|
||||
},
|
||||
...CLOUMNS,
|
||||
@ -195,11 +287,16 @@ export default function PgTable({ columns, data, isSelectRow, ...props }) {
|
||||
}
|
||||
);
|
||||
|
||||
React.useEffect(()=>{
|
||||
tableRef.current?.resetAfterIndex(0);
|
||||
},[expanded]);
|
||||
|
||||
|
||||
React.useEffect(() => {
|
||||
setHiddenColumns(
|
||||
columns
|
||||
.filter((column) => {
|
||||
if (column.isVisible === undefined || columns.isVisible === true) {
|
||||
if (column.isVisible === undefined || column.isVisible === true) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -222,28 +319,44 @@ export default function PgTable({ columns, data, isSelectRow, ...props }) {
|
||||
}, [selectedRowIds]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setGlobalFilter(props.searchText || undefined);
|
||||
}, [props.searchText]);
|
||||
|
||||
setGlobalFilter(searchVal || undefined);
|
||||
}, [searchVal]);
|
||||
|
||||
const RenderRow = React.useCallback(
|
||||
({ index, style }) => {
|
||||
const row = rows[index];
|
||||
prepareRow(row);
|
||||
return (
|
||||
<div
|
||||
{...row.getRowProps({
|
||||
style,
|
||||
})}
|
||||
className={classes.tr}
|
||||
>
|
||||
{row.cells.map((cell) => {
|
||||
return (
|
||||
<div key={cell.column.id} {...cell.getCellProps()} className={clsx(classes.tableCell, row.original.icon && row.original.icon[cell.column.id], row.original.icon[cell.column.id] && classes.cellIcon)} title={cell.value}>
|
||||
{cell.render('Cell')}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div className={classes.tableContentWidth} style={style} key={row.id}>
|
||||
<div {...row.getRowProps()} className={classes.tr}>
|
||||
{row.cells.map((cell) => {
|
||||
let classNames = [classes.tableCell];
|
||||
if(typeof(cell.column.id) == 'string' && cell.column.id.startsWith('btn-')) {
|
||||
classNames.push(classes.btnCell);
|
||||
}
|
||||
if(cell.column.id == 'btn-edit' && row.isExpanded) {
|
||||
classNames.push(classes.expandedIconCell);
|
||||
}
|
||||
return (
|
||||
<div key={cell.column.id} {...cell.getCellProps()} className={clsx(classNames, row.original.icon && row.original.icon[cell.column.id], row.original.icon[cell.column.id] && classes.cellIcon)} title={String(cell.value)}>
|
||||
{cell.render('Cell')}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{!_.isUndefined(row) && row.isExpanded && (
|
||||
<Box key={row.id} className={classes.expandedForm} ref={rowRef} style={{height: rowHeights.current[index]}}>
|
||||
<FormView
|
||||
getInitData={() => {
|
||||
/*This is intentional (SonarQube)*/
|
||||
}}
|
||||
viewHelperProps={{ mode: 'properties' }}
|
||||
schema={props.schema[row.id]}
|
||||
showFooter={false}
|
||||
onDataChange={() => { }}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
@ -251,70 +364,108 @@ export default function PgTable({ columns, data, isSelectRow, ...props }) {
|
||||
);
|
||||
// Render the UI for your table
|
||||
return (
|
||||
<AutoSizer className={(props.type ==='panel' ? props.className: classes.autoResizer)}>
|
||||
{({ height}) => (
|
||||
<div {...getTableProps()} className={classes.table}>
|
||||
<div>
|
||||
{headerGroups.map((headerGroup) => (
|
||||
<div key={''} {...headerGroup.getHeaderGroupProps()}>
|
||||
{headerGroup.headers.map((column) => (
|
||||
<div
|
||||
key={column.id}
|
||||
{...column.getHeaderProps()}
|
||||
className={clsx(
|
||||
classes.tableCellHeader,
|
||||
column.className
|
||||
)}
|
||||
>
|
||||
<Box className={classes.pgTableHeadar}>
|
||||
<Box className={classes.searchBox}>
|
||||
{props.customHeader && (<Box className={classes.customHeader}> <props.customHeader /></Box>)}
|
||||
<Box className={classes.searchPadding}></Box>
|
||||
<InputText
|
||||
placeholder={'Search'}
|
||||
className={classes.searchInput}
|
||||
value={searchVal}
|
||||
onChange={(val) => {
|
||||
setSearchVal(val);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<AutoSizer
|
||||
className={props.type === 'panel' ? props.className : classes.autoResizer}
|
||||
>
|
||||
{({ height }) => (
|
||||
<div {...getTableProps()} className={classes.table}>
|
||||
<div>
|
||||
{headerGroups.map((headerGroup) => (
|
||||
<div key={''} {...headerGroup.getHeaderGroupProps()}>
|
||||
{headerGroup.headers.map((column) => (
|
||||
<div
|
||||
{...(column.sortble
|
||||
? column.getSortByToggleProps()
|
||||
: {})}
|
||||
key={column.id}
|
||||
{...column.getHeaderProps()}
|
||||
className={clsx(classes.tableCellHeader, column.className)}
|
||||
>
|
||||
{column.render('Header')}
|
||||
<span>
|
||||
{column.isSorted
|
||||
? column.isSortedDesc
|
||||
? ' 🔽'
|
||||
: ' 🔼'
|
||||
: ''}
|
||||
</span>
|
||||
{column.resizable && (
|
||||
<div
|
||||
{...column.getResizerProps()}
|
||||
className={classes.resizer}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
{...(column.sortble ? column.getSortByToggleProps() : {})}
|
||||
>
|
||||
{column.render('Header')}
|
||||
<span>
|
||||
{column.isSorted
|
||||
? column.isSortedDesc
|
||||
? ' 🔽'
|
||||
: ' 🔼'
|
||||
: ''}
|
||||
</span>
|
||||
{column.resizable && (
|
||||
<div
|
||||
{...column.getResizerProps()}
|
||||
className={classes.resizer}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{/* <span className={classes.extraTable}></span> */}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div {...getTableBodyProps()} className={classes}>
|
||||
<FixedSizeList
|
||||
className={classes.fixedSizeList}
|
||||
height={height - 75}
|
||||
itemCount={rows.length}
|
||||
itemSize={35}
|
||||
sorted={props?.sortOptions}
|
||||
>
|
||||
{RenderRow}
|
||||
</FixedSizeList>
|
||||
{
|
||||
data.length > 0 ? (
|
||||
<div {...getTableBodyProps()} >
|
||||
<VariableSizeList
|
||||
ref={tableRef}
|
||||
className={props.type === 'dashboard' ? props.fixedSizeList : classes.fixedSizeList}
|
||||
height={height - offset}
|
||||
itemCount={rows.length}
|
||||
itemSize={(i) => {
|
||||
if (_.isUndefined(rows[i].isExpanded)) {
|
||||
rows[i].isExpanded = false;
|
||||
}
|
||||
if (rowRef.current && rows[i].isExpanded) {
|
||||
setRowHeight(i, rowRef.current.offsetHeight + 35);
|
||||
}
|
||||
return rows[i].isExpanded ? getRowHeight(i, 35) : 35;
|
||||
}}
|
||||
sorted={props?.sortOptions}
|
||||
>
|
||||
{RenderRow}
|
||||
|
||||
</VariableSizeList>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
<div className={classes.emptyPanel}>
|
||||
<div className={classes.panelIcon}>
|
||||
<i className="fa fa-exclamation-circle"></i>
|
||||
<span className={classes.panelMessage}>{gettext('No record found')}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</AutoSizer>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
PgTable.propTypes = {
|
||||
stepId: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
offset: PropTypes.number,
|
||||
customHeader: PropTypes.func,
|
||||
className: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]),
|
||||
fixedSizeList: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
]),
|
||||
getToggleAllRowsSelectedProps: PropTypes.func,
|
||||
columns: PropTypes.array,
|
||||
data: PropTypes.array,
|
||||
@ -324,8 +475,6 @@ PgTable.propTypes = {
|
||||
getSelectedRows: PropTypes.func,
|
||||
searchText: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
sortOptions: PropTypes.array,
|
||||
|
||||
sortOptions: PropTypes.array,
|
||||
schema: PropTypes.object
|
||||
};
|
||||
|
||||
|
||||
|
@ -1,37 +0,0 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import {isString, isFunction} from 'sources/utils';
|
||||
import pgBrowser from 'pgadmin.browser';
|
||||
|
||||
|
||||
export function url(itemData, item, treeHierarchy) {
|
||||
let treeNode = pgBrowser.tree.findNodeByDomElement(item);
|
||||
let return_url = null;
|
||||
|
||||
if (treeNode) {
|
||||
treeNode.anyFamilyMember(
|
||||
(node) => {
|
||||
let nodeData = node.getData();
|
||||
let browserNode = pgBrowser.Nodes[nodeData._type];
|
||||
let dashboardURL = browserNode && browserNode.dashboard;
|
||||
|
||||
if (isFunction(dashboardURL)) {
|
||||
dashboardURL = dashboardURL.apply(
|
||||
browserNode, [node, nodeData, treeHierarchy]
|
||||
);
|
||||
}
|
||||
return_url = isString(dashboardURL) ? dashboardURL : null;
|
||||
|
||||
return (return_url !== null);
|
||||
});
|
||||
}
|
||||
|
||||
return return_url;
|
||||
}
|
@ -17,7 +17,7 @@ import Wizard from '../../../../static/js/helpers/wizard/Wizard';
|
||||
import WizardStep from '../../../../static/js/helpers/wizard/WizardStep';
|
||||
import PgTable from 'sources/components/PgTable';
|
||||
import { getNodePrivilegeRoleSchema } from '../../../../../pgadmin/browser/server_groups/servers/static/js/privilege.ui.js';
|
||||
import { InputSQL, InputText, FormFooterMessage, MESSAGE_TYPE } from '../../../../static/js/components/FormComponents';
|
||||
import { InputSQL, FormFooterMessage, MESSAGE_TYPE } from '../../../../static/js/components/FormComponents';
|
||||
import getApiInstance from '../../../../static/js/api_instance';
|
||||
import SchemaView from '../../../../static/js/SchemaView';
|
||||
import clsx from 'clsx';
|
||||
@ -117,7 +117,6 @@ export default function GrantWizard({ sid, did, nodeInfo, nodeData }) {
|
||||
const [selectedObject, setSelectedObject] = React.useState([]);
|
||||
const [selectedAcl, setSelectedAcl] = React.useState({});
|
||||
const [msqlData, setSQL] = React.useState('');
|
||||
const [searchVal, setSearchVal] = React.useState('');
|
||||
const [loaderText, setLoaderText] = React.useState('');
|
||||
const [tablebData, setTableData] = React.useState([]);
|
||||
const [privOptions, setPrivOptions] = React.useState({});
|
||||
@ -314,17 +313,6 @@ export default function GrantWizard({ sid, did, nodeInfo, nodeData }) {
|
||||
loaderText={loaderText}
|
||||
>
|
||||
<WizardStep stepId={0}>
|
||||
<Box className={classes.searchBox}>
|
||||
<Box className={classes.searchPadding}></Box>
|
||||
<InputText
|
||||
placeholder={'Search'}
|
||||
className={classes.searchInput}
|
||||
value={searchVal}
|
||||
onChange={(val) => {
|
||||
setSearchVal(val);}
|
||||
}>
|
||||
</InputText>
|
||||
</Box>
|
||||
<Box className={classes.panelContent}>
|
||||
<PgTable
|
||||
className={classes.table}
|
||||
@ -332,7 +320,6 @@ export default function GrantWizard({ sid, did, nodeInfo, nodeData }) {
|
||||
columns={columns}
|
||||
data={tablebData}
|
||||
isSelectRow={true}
|
||||
searchText={searchVal}
|
||||
getSelectedRows={getTableSelectedRows}>
|
||||
</PgTable>
|
||||
</Box>
|
||||
|
@ -469,10 +469,8 @@ module.exports = [{
|
||||
options: {
|
||||
type: 'commonjs',
|
||||
imports: [
|
||||
'pure|pgadmin.dashboard',
|
||||
'pure|pgadmin.browser.quick_search',
|
||||
'pure|pgadmin.tools.user_management',
|
||||
'pure|pgadmin.browser.object_sql',
|
||||
'pure|pgadmin.browser.bgprocess',
|
||||
'pure|pgadmin.node.server_group',
|
||||
'pure|pgadmin.node.server',
|
||||
|
@ -208,14 +208,13 @@ var webpackShimConfig = {
|
||||
'pgadmin.browser.dialog': path.join(__dirname, './pgadmin/browser/static/js/dialog'),
|
||||
'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.object_sql': path.join(__dirname, './pgadmin/misc/sql/static/js/sql'),
|
||||
'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.browser.wizard': path.join(__dirname, './pgadmin/browser/static/js/wizard'),
|
||||
'pgadmin.dashboard': path.join(__dirname, './pgadmin/dashboard/static/js/dashboard'),
|
||||
'pgadmin.dashboard': path.join(__dirname, './pgadmin/dashboard/static/js/Dashboard'),
|
||||
'pgadmin.datagrid': path.join(__dirname, './pgadmin/tools/datagrid/static/js/datagrid'),
|
||||
'pgadmin.file_manager': path.join(__dirname, './pgadmin/misc/file_manager/static/js/file_manager'),
|
||||
'pgadmin.file_utility': path.join(__dirname, './pgadmin/misc/file_manager/static/js/utility'),
|
||||
|
Loading…
Reference in New Issue
Block a user