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:
Pradip Parkale 2022-03-30 12:06:59 +05:30 committed by Akshay Joshi
parent 931a399890
commit cb052f1988
82 changed files with 2352 additions and 5605 deletions

View File

@ -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

View File

@ -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",

View File

@ -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),

View File

@ -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;
},
}),
});
}

View File

@ -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(

View File

@ -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',
},
],
}),
});
}

View File

@ -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',
}],
}),
});
}

View File

@ -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(

View File

@ -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(

View File

@ -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'],

View File

@ -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';

View File

@ -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();
}

View File

@ -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,
}],
}),
});
}

View File

@ -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,
},
],
}),
});

View File

@ -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(

View File

@ -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;
},
}),
});
}

View File

@ -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',
}],
}),
});
}

View File

@ -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;
},
}),
});
}

View File

@ -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',
}],
}),
});
}

View File

@ -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',
}],
}),
});
}

View File

@ -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(

View File

@ -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(

View File

@ -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'];
});

View File

@ -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;
},
}),
});
}

View File

@ -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);
},
}),
});
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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(

View File

@ -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'],
}],
}),
});
}

View File

@ -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(
{

View File

@ -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(

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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'],
}],
}),
});
}

View File

@ -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

View File

@ -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) {

View File

@ -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),

View File

@ -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) {

View File

@ -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) {

View File

@ -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(

View File

@ -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 || {},

View File

@ -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;
},
}),
});
}

View File

@ -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 = {

View File

@ -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'),

View File

@ -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(
{

View File

@ -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'],
},
{

View File

@ -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,

View File

@ -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;
},
}),
});
}

View File

@ -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 != "") { %>&nbsp;<%-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) {
/*

View File

@ -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;
}

View File

@ -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
);
}

View File

@ -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';

View File

@ -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]);

View File

@ -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 */

View 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'),
},
];
}
}

View 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,
};

View File

@ -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';

View 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>);
}

View 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

View File

@ -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,
};

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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>
)}
</>

View File

@ -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>
)}
</>
);

View 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,
};

View 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,
};

View File

@ -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;
});

View File

@ -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>
)}
</>

View File

@ -92,7 +92,8 @@ export default function(basicSettings) {
activeStepFg: '#FFFFFF',
stepBg: '#FFFFFF',
stepFg: '#000',
toggleBtnBg: '#000'
toggleBtnBg: '#000',
colorFg: '#FFFFFF',
}
});
}

View File

@ -90,7 +90,8 @@ export default function(basicSettings) {
activeStepFg: '#010b15',
stepBg: '#FFFFFF',
stepFg: '#000',
toggleBtnBg: '#6B6B6B'
toggleBtnBg: '#6B6B6B',
colorFg: '#FFFFFF',
}
});
}

View File

@ -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',
}
});
}

View File

@ -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
};

View File

@ -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;
}

View File

@ -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>

View File

@ -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',

View File

@ -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'),