Significant changes to use ReactJS extensively.

1. Replace the current layout library wcDocker with ReactJS based rc-dock. #6479
2. Have close buttons on individual panel tabs instead of common. #2821
3. Changes in the context menu on panel tabs - Add close, close all and close others menu items. #5394
4. Allow closing all the tabs, including SQL and Properties. #4733
5. Changes in docking behaviour of different tabs based on user requests and remove lock layout menu.
6. Fix an issue where the scroll position of panels was not remembered on Firefox. #2986
7. Reset layout now will not require page refresh and is done spontaneously.
8. Use the zustand store for storing preferences instead of plain JS objects. This will help reflecting preferences immediately.
9. The above fix incorrect format (no indent) of SQL stored functions/procedures. #6720
10. New version check is moved to an async request now instead of app start to improve startup performance.
11. Remove jQuery and Bootstrap completely.
12. Replace jasmine and karma test runner with jest. Migrate all the JS test cases to jest. This will save time in writing and debugging JS tests.
13. Other important code improvements and cleanup.
This commit is contained in:
Aditya Toshniwal
2023-10-23 17:43:17 +05:30
committed by GitHub
parent 6d555645e9
commit 862f101772
373 changed files with 11149 additions and 14836 deletions

View File

@@ -11,7 +11,6 @@ import pgAdmin from 'sources/pgadmin';
import Menu, { MenuItem } from '../../../static/js/helpers/Menu';
import getApiInstance from '../../../static/js/api_instance';
import url_for from 'sources/url_for';
import Notifier from '../../../static/js/helpers/Notifier';
import { getBrowser } from '../../../static/js/utils';
import { isMac } from '../../../static/js/keyboard_shortcuts';
@@ -98,7 +97,7 @@ export default class MainMenuFactory {
).then(()=>{
window.open(options.url);
}).catch(()=>{
Notifier.error(gettext('Error in opening window'));
pgAdmin.Browser.notifier.error(gettext('Error in opening window'));
});
}
}

View File

@@ -7,7 +7,6 @@
//
//////////////////////////////////////////////////////////////
import $ from 'jquery';
import _ from 'lodash';
import pgAdmin from 'sources/pgadmin';
@@ -106,7 +105,6 @@ _.extend(pgBrowser, {
if(self.is_inactivity_timeout()) {
clearInterval(timeout_daemon_id);
self.inactivity_timeout_daemon_running = false;
$(window).off('beforeunload');
self.logout_inactivity_user();
}
}, MIN_ACTIVITY_TIME_UNIT);

View File

@@ -7,45 +7,25 @@
//
//////////////////////////////////////////////////////////////
import React from 'react';
import { generateNodeUrl } from './node_ajax';
import MainMenuFactory from './MainMenuFactory';
import _ from 'lodash';
import Notify, {initializeModalProvider, initializeNotifier} from '../../../static/js/helpers/Notifier';
import { checkMasterPassword } from '../../../static/js/Dialogs/index';
import { checkMasterPassword, showQuickSearch } from '../../../static/js/Dialogs/index';
import { pgHandleItemError } from '../../../static/js/utils';
import { Search } from './quick_search/trigger_search';
import { send_heartbeat, stop_heartbeat } from './heartbeat';
import getApiInstance from '../../../static/js/api_instance';
import { copyToClipboard } from '../../../static/js/clipboard';
import { TAB_CHANGE } from './constants';
import usePreferences, { setupPreferenceBroadcast } from '../../../preferences/static/js/store';
import checkNodeVisibility from '../../../static/js/check_node_visibility';
define('pgadmin.browser', [
'sources/gettext', 'sources/url_for', 'jquery',
'sources/pgadmin', 'bundled_codemirror',
'sources/check_node_visibility', './toolbar', 'pgadmin.help',
'sources/csrf', 'sources/utils', 'sources/window', 'pgadmin.authenticate.kerberos',
'sources/tree/tree_init',
'pgadmin.browser.utils',
'pgadmin.browser.preferences', 'pgadmin.browser.messages',
'pgadmin.browser.panel', 'pgadmin.browser.layout',
'pgadmin.browser.frame',
'sources/gettext', 'sources/url_for', 'sources/pgadmin', 'bundled_codemirror',
'sources/csrf', 'pgadmin.authenticate.kerberos',
'pgadmin.browser.utils', 'pgadmin.browser.messages',
'pgadmin.browser.node', 'pgadmin.browser.collection', 'pgadmin.browser.activity',
'sources/codemirror/addon/fold/pgadmin-sqlfoldcode',
'pgadmin.browser.keyboard', 'sources/tree/pgadmin_tree_save_state',
/* wcDocker dependencies */
'bootstrap', 'jquery-contextmenu', 'wcdocker',
], function(
gettext, url_for, $,
pgAdmin, codemirror,
checkNodeVisibility, toolBar, help, csrfToken, pgadminUtils, pgWindow,
Kerberos, InitTree,
gettext, url_for, pgAdmin, codemirror, csrfToken, Kerberos,
) {
window.jQuery = window.$ = $;
// Some scripts do export their object in the window only.
// Generally the one, which do no have AMD support.
let wcDocker = window.wcDocker;
$ = $ || window.jQuery || window.$;
let CodeMirror = codemirror.default;
let pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
@@ -55,104 +35,10 @@ define('pgadmin.browser', [
Kerberos.validate_kerberos_ticket();
let panelEvents = {};
panelEvents[wcDocker.EVENT.VISIBILITY_CHANGED] = function() {
if (this.isVisible()) {
let obj = pgAdmin.Browser,
i = obj.tree ? obj.tree.selected() : undefined,
d = i ? obj.tree.itemData(i) : undefined;
if (d && obj.Nodes[d._type].callbacks['selected'] &&
_.isFunction(obj.Nodes[d._type].callbacks['selected'])) {
return obj.Nodes[d._type].callbacks['selected'].apply(
obj.Nodes[d._type], [i, d, obj, [], '', TAB_CHANGE]);
}
}
};
let initializeBrowserTree = pgAdmin.Browser.initializeBrowserTree =
function(b) {
const draggableTypes = [
'collation domain domain_constraints fts_configuration fts_dictionary fts_parser fts_template synonym table partition type sequence package view mview foreign_table edbvar',
'schema column database cast event_trigger extension language foreign_data_wrapper foreign_server user_mapping compound_trigger index index_constraint primary_key unique_constraint check_constraint exclusion_constraint foreign_key rule',
'trigger trigger_function',
'edbfunc function edbproc procedure'
];
InitTree.initBrowserTree(b).then(() => {
const getQualifiedName = (data, item)=>{
if(draggableTypes[0].includes(data._type)) {
return pgadminUtils.fully_qualify(b, data, item);
} else if(draggableTypes[1].includes(data._type)) {
return pgadminUtils.quote_ident(data._label);
} else if(draggableTypes[3].includes(data._type)) {
let newData = {...data};
let parsedFunc = pgadminUtils.parseFuncParams(newData._label);
newData._label = parsedFunc.func_name;
return pgadminUtils.fully_qualify(b, newData, item);
} else {
return data._label;
}
};
b.tree.registerDraggableType({
[draggableTypes[0]] : (data, item, treeNodeInfo)=>{
let text = getQualifiedName(data, item);
return {
text: text,
objUrl: generateNodeUrl.call(pgBrowser.Nodes[data._type], treeNodeInfo, 'properties', data, true),
nodeType: data._type,
cur: {
from: text.length,
to: text.length,
},
};
},
[draggableTypes[1]] : (data)=>{
return getQualifiedName(data);
},
[draggableTypes[2]] : (data)=>{
return getQualifiedName(data);
},
[draggableTypes[3]] : (data, item)=>{
let parsedFunc = pgadminUtils.parseFuncParams(data._label),
dropVal = getQualifiedName(data, item),
curPos = {from: 0, to: 0};
if(parsedFunc.params.length > 0) {
dropVal = dropVal + '(';
curPos.from = dropVal.length;
dropVal = dropVal + parsedFunc.params[0][0];
curPos.to = dropVal.length;
for(let i=1; i<parsedFunc.params.length; i++) {
dropVal = dropVal + ', ' + parsedFunc.params[i][0];
}
dropVal = dropVal + ')';
} else {
dropVal = dropVal + '()';
curPos.from = curPos.to = dropVal.length + 1;
}
return {
text: dropVal,
cur: curPos,
};
},
});
b.tree.onNodeCopy((data, item)=>{
copyToClipboard(getQualifiedName(data, item));
});
}, () => {console.warn('Tree Load Error');});
};
// Extend the browser class attributes
_.extend(pgAdmin.Browser, {
// The base url for browser
URL: url_for('browser.index'),
// We do have docker of type wcDocker to take care of different
// containers. (i.e. panels, tabs, frames, etc.)
docker:null,
// Reversed Engineer query for the selected database node object goes
// here
@@ -215,102 +101,7 @@ define('pgadmin.browser', [
},
},
// Default panels
panels: {
// Panel to keep the left hand browser tree
'browser': new pgAdmin.Browser.Panel({
name: 'browser',
title: gettext('Object Explorer'),
showTitle: true,
isCloseable: false,
isPrivate: true,
icon: '',
limit: 1,
content: '<div id="tree" class="browser-tree"></div>',
onCreate: function(panel, container) {
toolBar.initializeToolbar(panel, wcDocker);
container.classList.add('pg-no-overflow');
},
}),
// Properties of the object node
'properties': new pgAdmin.Browser.Panel({
name: 'properties',
title: gettext('Properties'),
icon: '',
width: 500,
isCloseable: false,
isPrivate: true,
elContainer: true,
limit: 1,
content: '<div class="obj_properties"><div role="status" class="pg-panel-message">' + select_object_msg + '</div></div>',
events: panelEvents,
onCreate: function(myPanel, container) {
container.classList.add('pg-no-overflow');
},
}),
// Statistics of the object
'statistics': new pgAdmin.Browser.Panel({
name: 'statistics',
title: gettext('Statistics'),
icon: '',
width: 500,
isCloseable: true,
isPrivate: false,
limit : 1,
canHide: true,
content: '<div></div>',
events: panelEvents,
}),
// Reversed engineered SQL for the object
'sql': new pgAdmin.Browser.Panel({
name: 'sql',
title: gettext('SQL'),
icon: '',
width: 500,
isCloseable: false,
isPrivate: true,
limit: 1,
content: '<div></div>',
}),
// Dependencies of the object
'dependencies': new pgAdmin.Browser.Panel({
name: 'dependencies',
title: gettext('Dependencies'),
icon: '',
width: 500,
isCloseable: true,
isPrivate: false,
canHide: true,
limit: 1,
content: '<div></div>',
events: panelEvents,
}),
// Dependents of the object
'dependents': new pgAdmin.Browser.Panel({
name: 'dependents',
title: gettext('Dependents'),
icon: '',
width: 500,
isCloseable: true,
isPrivate: false,
limit: 1,
canHide: true,
content: '<div></div>',
events: panelEvents,
}),
// Background processes
'processes': new pgAdmin.Browser.Panel({
name: 'processes',
title: gettext('Processes'),
icon: '',
width: 500,
isCloseable: true,
isPrivate: false,
limit: 1,
canHide: true,
content: '<div></div>',
events: panelEvents,
}),
},
panels: {},
// We also support showing dashboards, HTML file, external URL
frames: {},
/* Menus */
@@ -336,40 +127,6 @@ define('pgadmin.browser', [
MainMenus: [],
BrowserContextMenu: [],
add_panels: function() {
/* Add hooked-in panels by extensions */
let panels = JSON.parse(pgBrowser.panels_items);
_.each(panels, function(panel) {
if (panel.isIframe) {
pgBrowser.frames[panel.name] = new pgBrowser.Frame({
name: panel.name,
title: panel.title,
icon: panel.icon,
width: panel.width,
height: panel.height,
showTitle: panel.showTitle,
isCloseable: panel.isCloseable,
isPrivate: panel.isPrivate,
url: panel.content,
});
} else {
pgBrowser.panels[panel.name] = new pgBrowser.Panel({
name: panel.name,
title: panel.title,
icon: panel.icon,
width: panel.width,
height: panel.height,
showTitle: panel.showTitle,
isCloseable: panel.isCloseable,
isPrivate: panel.isPrivate,
limit: (panel.limit) ? panel.limit : null,
content: (panel.content) ? panel.content : '',
events: (panel.events) ? panel.events : '',
canHide: (panel.canHide) ? panel.canHide : '',
});
}
});
},
menu_categories: {
/* name, label (pair) */
'register': {
@@ -490,8 +247,6 @@ define('pgadmin.browser', [
enable_disable_menus: function(item) {
let obj = this;
let d = item ? obj.tree.itemData(item) : undefined;
toolBar.enable(gettext('View Data'), false);
toolBar.enable(gettext('Filtered Rows'), false);
// All menus (except for the object menus) are already present.
// They will just require to check, whether they are
@@ -529,64 +284,8 @@ define('pgadmin.browser', [
obj.initialized = true;
// Cache preferences
obj.cache_preferences();
obj.add_panels();
// Initialize the Docker
obj.docker = new wcDocker(
'#dockerContainer', {
allowContextMenu: true,
allowCollapse: false,
loadingClass: 'pg-sp-icon',
themePath: url_for('static', {
'filename': 'css',
}),
theme: 'webcabin.overrides.css',
});
if (obj.docker) {
// Initialize all the panels
_.each(obj.panels, function(panel, name) {
obj.panels[name].load(obj.docker);
});
// Initialize all the frames
_.each(obj.frames, function(frame, name) {
obj.frames[name].load(obj.docker);
});
// Stored layout in database from the previous session
let layout = pgBrowser.utils.layout;
obj.restore_layout(obj.docker, layout, obj.buildDefaultLayout.bind(obj), true);
obj.docker.on(wcDocker.EVENT.LAYOUT_CHANGED, function() {
obj.save_current_layout('Browser/Layout', obj.docker);
});
}
initializeBrowserTree(obj);
initializeModalProvider();
initializeNotifier();
/* Cache may take time to load for the first time
* Reflect the changes once cache is available
*/
let cacheIntervalId = setInterval(()=> {
let sqlEditPreferences = obj.get_preferences_for_module('sqleditor');
let browserPreferences = obj.get_preferences_for_module('browser');
if(sqlEditPreferences && browserPreferences) {
clearInterval(cacheIntervalId);
obj.reflectPreferences('sqleditor');
obj.reflectPreferences('browser');
}
}, 500);
/* Check for sql editor preference changes */
obj.onPreferencesChange('sqleditor', function() {
obj.reflectPreferences('sqleditor');
});
/* Check for browser preference changes */
obj.onPreferencesChange('browser', function() {
obj.reflectPreferences('browser');
});
usePreferences.getState().cache();
setupPreferenceBroadcast();
setTimeout(function() {
obj?.editor?.setValue('-- ' + select_object_msg);
@@ -621,7 +320,6 @@ define('pgadmin.browser', [
obj.Events.on('pgadmin:browser:tree:update', obj.onUpdateTreeNode.bind(obj));
obj.Events.on('pgadmin:browser:tree:refresh', obj.onRefreshTreeNodeReact.bind(obj));
obj.Events.on('pgadmin-browser:tree:loadfail', obj.onLoadFailNode.bind(obj));
obj.Events.on('pgadmin-browser:panel-browser:' + wcDocker.EVENT.RESIZE_ENDED, obj.onResizeEnded.bind(obj));
obj.bind_beforeunload();
/* User UI activity */
@@ -629,8 +327,8 @@ define('pgadmin.browser', [
obj.register_to_activity_listener(document);
obj.start_inactivity_timeout_daemon();
},
onResizeEnded: function() {
if (this.tree) this.tree.resizeTree();
uiloaded: function() {
this.check_version_update();
},
check_corrupted_db_file: function() {
getApiInstance().get(
@@ -638,7 +336,7 @@ define('pgadmin.browser', [
).then(({data: res})=> {
if(res.data.length > 0) {
Notify.alert(
pgAdmin.Browser.notifier.alert(
'Warning',
'pgAdmin detected unrecoverable corruption in it\'s SQLite configuration database. ' +
'The database has been backed up and recreated with default settings. '+
@@ -649,7 +347,7 @@ define('pgadmin.browser', [
);
}
}).catch(function(error) {
Notify.alert(error);
pgAdmin.Browser.notifier.alert(error);
});
},
check_master_password: function(on_resp_callback) {
@@ -664,7 +362,7 @@ define('pgadmin.browser', [
}
}
}).catch(function(error) {
Notify.pgRespErrorNotify(error);
pgAdmin.Browser.notifier.pgRespErrorNotify(error);
});
},
@@ -677,7 +375,7 @@ define('pgadmin.browser', [
self.set_master_password('');
}
}).catch(function(error) {
Notify.pgRespErrorNotify(error);
pgAdmin.Browser.notifier.pgRespErrorNotify(error);
});
},
@@ -695,13 +393,33 @@ define('pgadmin.browser', [
checkMasterPassword(data, self.masterpass_callback_queue, cancel_callback);
},
bind_beforeunload: function() {
$(window).on('beforeunload', function(e) {
/* Can open you in new tab */
let openerBrowser = pgWindow.default.pgAdmin.Browser;
check_version_update: function() {
getApiInstance().get(
url_for('misc.upgrade_check')
).then((res)=> {
const data = res.data.data;
if(data.outdated) {
pgAdmin.Browser.notifier.warning(
`
${gettext('You are currently running version %s of %s, <br/>however the current version is %s.', data.current_version, data.product_name, data.upgrade_version)}
<br/><br/>
${gettext('Please click <a href="%s" target="_new" style="color:inherit">here</a> for more information.', data.download_url)}
`,
null
);
}
let tree_save_interval = pgBrowser.get_preference('browser', 'browser_tree_state_save_interval'),
confirm_on_refresh_close = openerBrowser.get_preference('browser', 'confirm_on_refresh_close');
}).catch(function() {
// Suppress any errors
});
},
bind_beforeunload: function() {
window.addEventListener('beforeunload', function(e) {
/* Can open you in new tab */
const prefStore = usePreferences.getState();
let tree_save_interval = prefStore.getPreferences('browser', 'browser_tree_state_save_interval'),
confirm_on_refresh_close = prefStore.getPreferences('browser', 'confirm_on_refresh_close');
if (!_.isUndefined(tree_save_interval) && tree_save_interval.value !== -1)
pgAdmin.Browser.browserTreeState.save_state();
@@ -709,7 +427,9 @@ define('pgadmin.browser', [
if(!_.isUndefined(confirm_on_refresh_close) && confirm_on_refresh_close.value) {
/* This message will not be displayed in Chrome, Firefox, Safari as they have disabled it*/
let msg = gettext('Are you sure you want to close the %s browser?', pgBrowser.utils.app_name);
e.originalEvent.returnValue = msg;
if(e.originalEvent?.returnValue) {
e.originalEvent.returnValue = msg;
}
return msg;
}
});
@@ -730,8 +450,7 @@ define('pgadmin.browser', [
// Add menus of module/extension at appropriate menu
add_menus: function(menus) {
let self = this,
pgMenu = this.all_menus_cache;
let pgMenu = this.all_menus_cache;
_.each(menus, function(m) {
_.each(m.applies, function(a) {
@@ -741,12 +460,12 @@ define('pgadmin.browser', [
// If current node is not visible in browser tree
// then return from here
if(!checkNodeVisibility(self, m.node)) {
if(!checkNodeVisibility(m.node)) {
return;
} else if(_.has(m, 'module') && !_.isUndefined(m.module)) {
// If module to which this menu applies is not visible in
// browser tree then also we do not display menu
if(!checkNodeVisibility(self, m.module.type)) {
if(!checkNodeVisibility(m.module.type)) {
return;
}
}
@@ -770,14 +489,9 @@ define('pgadmin.browser', [
}
// This is to handel quick search callback
if(gettext(_m.label) == gettext('Quick Search')) {
if(_m.name == 'mnu_quick_search_help') {
_m.callback = () => {
// Render Search component
Notify.showModal(gettext('Quick Search'), (closeModal) => {
return <Search closeModal={closeModal}/>;
},
{ isFullScreen: false, isResizeable: false, showFullScreen: false, isFullWidth: false, showTitle: false}
);
showQuickSearch();
};
}
@@ -1683,7 +1397,6 @@ define('pgadmin.browser', [
if (!n) {
ctx.t.destroy({
success: function() {
initializeBrowserTree(ctx.b);
ctx.t = ctx.b.tree;
ctx.i = null;
ctx.b._refreshNode(ctx, ctx.branch);
@@ -1760,7 +1473,7 @@ define('pgadmin.browser', [
}
}
Notify.pgNotifier('error', error, gettext('Error retrieving details for the node.'), function (msg) {
pgAdmin.Browser.notifier.pgNotifier('error', error, gettext('Error retrieving details for the node.'), function (msg) {
if (msg == 'CRYPTKEY_SET') {
fetchNodeInfo(__i, __d, __n);
} else {
@@ -1974,7 +1687,7 @@ define('pgadmin.browser', [
}
fetchNodeInfo(_callback);
}).catch(function(error) {
Notify.pgRespErrorNotify(error);
pgAdmin.Browser.notifier.pgRespErrorNotify(error);
fetchNodeInfo(_callback);
});
};

View File

@@ -6,7 +6,6 @@
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import {getPanelView} from './panel_view';
import _ from 'lodash';
define([
@@ -84,15 +83,6 @@ define([
canDrop: true,
canDropCascade: true,
selectParentNodeOnDelete: false,
showProperties: function(item, data, panel) {
let container = panel.$container.find('.obj_properties').first();
getPanelView(
pgBrowser.tree,
container[0],
pgBrowser,
panel._type
);
},
generate_url: function(item, type) {
/*
* Using list, and collection functions of a node to get the nodes

View File

@@ -1,2 +1,34 @@
export const AUTH_METHODS = {
INTERNAL: 'internal',
LDAP: 'ldap',
KERBEROS: 'kerberos',
OAUTH2: 'oauth2',
WEBSERVER: 'webserver'
};
export const TAB_CHANGE = 'TAB_CHANGE';
export const TAB_CHANGE = 'TAB_CHANGE';
export const BROWSER_PANELS = {
MAIN: 'id-main',
OBJECT_EXPLORER: 'id-object-explorer',
DASHBOARD: 'id-dashboard',
PROPERTIES: 'id-properties',
SQL: 'id-sql',
STATISTICS: 'id-statistics',
DEPENDENCIES: 'id-dependencies',
DEPENDENTS: 'id-dependents',
PROCESSES: 'id-processes',
PROCESS_DETAILS: 'id-process-details',
EDIT_PROPERTIES: 'id-edit-properties',
UTILITY_DIALOG: 'id-utility',
QUERY_TOOL: 'id-query-tool',
PSQL_TOOL: 'id-psql-tool',
ERD_TOOL: 'id-erd-tool',
SCHEMA_DIFF_TOOL: 'id-schema-diff-tool',
DEBUGGER_TOOL: 'id-debugger-tool',
CLOUD_WIZARD: 'id-cloud-wizard',
GRANT_WIZARD: 'id-grant-wizard',
SEARCH_OBJECTS: 'id-search-objects',
USER_MANAGEMENT: 'id-user-management',
IMPORT_EXPORT_SERVERS: 'id-import-export-servers'
};

View File

@@ -1,135 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import _ from 'lodash';
define([
'sources/pgadmin', 'jquery', 'wcdocker',
], function(pgAdmin, $) {
let pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {},
wcDocker = window.wcDocker,
wcIFrame = window.wcIFrame;
pgAdmin.Browser.Frame = function(options) {
let defaults = [
'name', 'title', 'width', 'height', 'showTitle', 'isCloseable',
'isPrivate', 'url', 'icon', 'onCreate', 'isLayoutMember', 'isRenamable',
];
_.extend(this, _.pick(options, defaults));
};
_.extend(pgAdmin.Browser.Frame.prototype, {
name: '',
title: '',
width: 300,
height: 600,
showTitle: true,
isClosable: true,
isRenamable: false,
isPrivate: false,
isLayoutMember: false,
url: '',
icon: '',
panel: null,
frame: null,
onCreate: null,
load: function(docker) {
let that = this;
if (!that.panel) {
docker.registerPanelType(this.name, {
title: that.title,
isPrivate: that.isPrivate,
isLayoutMember: that.isLayoutMember,
onCreate: function(myPanel) {
myPanel.initSize(that.width, that.height);
if (!(myPanel.showTitle??true))
myPanel.title(false);
myPanel.icon(that.icon);
myPanel.closeable(!!that.isCloseable);
myPanel.renamable(that.isRenamable);
let $frameArea = $('<div style="position:absolute;top:0 !important;width:100%;height:100%;display:table;z-index:0;">');
myPanel.layout().addItem($frameArea);
that.panel = myPanel;
let frame = new wcIFrame($frameArea, myPanel);
myPanel.frameData = {
pgAdminName: that.name,
frameInitialized: false,
embeddedFrame: frame,
};
if (that.url != '' && that.url != 'about:blank') {
setTimeout(function() {
frame.openURL(that.url);
myPanel.frameData.frameInitialized = true;
pgBrowser.Events.trigger(
'pgadmin-browser:frame:urlloaded:' + that.name, frame,
that.url, self
);
}, 50);
} else {
frame.openURL('about:blank');
myPanel.frameData.frameInitialized = true;
pgBrowser.Events.trigger(
'pgadmin-browser:frame:urlloaded:' + that.name, frame,
that.url, self
);
}
if (that.events && _.isObject(that.events)) {
_.each(that.events, function(v, k) {
if (v && _.isFunction(v)) {
myPanel.on(k, v);
}
});
}
_.each([
wcDocker.EVENT.UPDATED, wcDocker.EVENT.VISIBILITY_CHANGED,
wcDocker.EVENT.BEGIN_DOCK, wcDocker.EVENT.END_DOCK,
wcDocker.EVENT.GAIN_FOCUS, wcDocker.EVENT.LOST_FOCUS,
wcDocker.EVENT.CLOSED, wcDocker.EVENT.BUTTON,
wcDocker.EVENT.ATTACHED, wcDocker.EVENT.DETACHED,
wcDocker.EVENT.MOVE_STARTED, wcDocker.EVENT.MOVE_ENDED,
wcDocker.EVENT.MOVED, wcDocker.EVENT.RESIZE_STARTED,
wcDocker.EVENT.RESIZE_ENDED, wcDocker.EVENT.RESIZED,
wcDocker.EVENT.SCROLLED,
], function(ev) {
myPanel.on(ev, that.eventFunc.bind(myPanel, ev));
});
if (that.onCreate && _.isFunction(that.onCreate)) {
that.onCreate.apply(that, [myPanel, frame]);
}
},
});
}
},
eventFunc: function(eventName) {
let name = this.frameData.pgAdminName;
try {
pgBrowser.Events.trigger('pgadmin-browser:frame', eventName, this, arguments);
pgBrowser.Events.trigger('pgadmin-browser:frame:' + eventName, this, arguments);
if (name) {
pgBrowser.Events.trigger('pgadmin-browser:frame-' + name, eventName, this, arguments);
pgBrowser.Events.trigger('pgadmin-browser:frame-' + name + ':' + eventName, this, arguments);
}
} catch (e) {
console.warn(e.stack || e);
}
},
});
return pgAdmin.Browser.Frame;
});

View File

@@ -10,7 +10,6 @@
import gettext from 'sources/gettext';
import url_for from 'sources/url_for';
import getApiInstance from '../../../static/js/api_instance';
import Notifier from '../../../static/js/helpers/Notifier';
import pgAdmin from 'sources/pgadmin';
const axiosApi = getApiInstance();
@@ -28,9 +27,9 @@ export function send_heartbeat(_server_id, _item) {
})
.catch((error) => {
if (error && error.message == 'Network Error') {
Notifier.error(gettext(`pgAdmin server not responding, try to login again: ${error.message || error.response.data.errormsg}`));
pgAdmin.Browser.notifier.error(gettext(`pgAdmin server not responding, try to login again: ${error.message || error.response.data.errormsg}`));
} else {
Notifier.error(gettext(`Server heartbeat logging error: ${error.message || error.response.data.errormsg}`));
pgAdmin.Browser.notifier.error(gettext(`Server heartbeat logging error: ${error.message || error.response.data.errormsg}`));
}
stop_heartbeat(_item);
});

View File

@@ -13,6 +13,7 @@ import Mousetrap from 'mousetrap';
import * as commonUtils from '../../../static/js/utils';
import gettext from 'sources/gettext';
import pgWindow from 'sources/window';
import usePreferences from '../../../preferences/static/js/store';
const pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
@@ -20,51 +21,53 @@ pgBrowser.keyboardNavigation = pgBrowser.keyboardNavigation || {};
_.extend(pgBrowser.keyboardNavigation, {
init: function() {
Mousetrap.reset();
if (pgBrowser.preferences_cache.length > 0) {
this.keyboardShortcut = {
...(pgBrowser.get_preference('browser', 'main_menu_file')?.value) && {'file_shortcut': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'main_menu_file')?.value)},
...(pgBrowser.get_preference('browser', 'main_menu_object')?.value) && {'object_shortcut': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'main_menu_object')?.value)},
...(pgBrowser.get_preference('browser', 'main_menu_tools')?.value) && {'tools_shortcut': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'main_menu_tools')?.value)},
...(pgBrowser.get_preference('browser', 'main_menu_help')?.value) && {'help_shortcut': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'main_menu_help')?.value)},
'left_tree_shortcut': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'browser_tree').value),
'tabbed_panel_backward': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'tabbed_panel_backward').value),
'tabbed_panel_forward': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'tabbed_panel_forward').value),
'sub_menu_query_tool': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_query_tool').value),
'sub_menu_view_data': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_view_data').value),
'sub_menu_search_objects': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_search_objects').value),
'sub_menu_properties': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_properties').value),
'sub_menu_create': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_create').value),
'sub_menu_delete': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_delete').value),
'sub_menu_refresh': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_refresh').value),
'context_menu': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'context_menu').value),
'direct_debugging': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'direct_debugging').value),
'add_grid_row': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'add_grid_row').value),
'open_quick_search': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'open_quick_search').value),
usePreferences.subscribe((prefStore)=>{
Mousetrap.reset();
if (prefStore.version > 0) {
this.keyboardShortcut = {
...(prefStore.getPreferences('browser', 'main_menu_file')?.value) && {'file_shortcut': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'main_menu_file')?.value)},
...(prefStore.getPreferences('browser', 'main_menu_object')?.value) && {'object_shortcut': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'main_menu_object')?.value)},
...(prefStore.getPreferences('browser', 'main_menu_tools')?.value) && {'tools_shortcut': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'main_menu_tools')?.value)},
...(prefStore.getPreferences('browser', 'main_menu_help')?.value) && {'help_shortcut': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'main_menu_help')?.value)},
'left_tree_shortcut': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'browser_tree').value),
'tabbed_panel_backward': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'tabbed_panel_backward').value),
'tabbed_panel_forward': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'tabbed_panel_forward').value),
'sub_menu_query_tool': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_query_tool').value),
'sub_menu_view_data': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_view_data').value),
'sub_menu_search_objects': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_search_objects').value),
'sub_menu_properties': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_properties').value),
'sub_menu_create': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_create').value),
'sub_menu_delete': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_delete').value),
'sub_menu_refresh': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_refresh').value),
'context_menu': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'context_menu').value),
'direct_debugging': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'direct_debugging').value),
'add_grid_row': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'add_grid_row').value),
'open_quick_search': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'open_quick_search').value),
};
this.shortcutMethods = {
...(pgBrowser.get_preference('browser', 'main_menu_file')?.value) && {'bindMainMenu': {
'shortcuts': [this.keyboardShortcut.file_shortcut,
this.keyboardShortcut.object_shortcut, this.keyboardShortcut.tools_shortcut,
this.keyboardShortcut.help_shortcut],
}}, // Main menu
'bindRightPanel': {'shortcuts': [this.keyboardShortcut.tabbed_panel_backward, this.keyboardShortcut.tabbed_panel_forward]}, // Main window panels
'bindLeftTree': {'shortcuts': this.keyboardShortcut.left_tree_shortcut}, // Main menu,
'bindSubMenuQueryTool': {'shortcuts': this.keyboardShortcut.sub_menu_query_tool}, // Sub menu - Open Query Tool,
'bindSubMenuViewData': {'shortcuts': this.keyboardShortcut.sub_menu_view_data}, // Sub menu - Open View Data,
'bindSubMenuSearchObjects': {'shortcuts': this.keyboardShortcut.sub_menu_search_objects}, // Sub menu - Open search objects,
'bindSubMenuProperties': {'shortcuts': this.keyboardShortcut.sub_menu_properties}, // Sub menu - Edit Properties,
'bindSubMenuCreate': {'shortcuts': this.keyboardShortcut.sub_menu_create}, // Sub menu - Create Object,
'bindSubMenuDelete': {'shortcuts': this.keyboardShortcut.sub_menu_delete}, // Sub menu - Delete object,
'bindSubMenuRefresh': {'shortcuts': this.keyboardShortcut.sub_menu_refresh, 'bindElem': '#tree'}, // Sub menu - Refresh object,
'bindContextMenu': {'shortcuts': this.keyboardShortcut.context_menu}, // Sub menu - Open context menu,
'bindDirectDebugging': {'shortcuts': this.keyboardShortcut.direct_debugging}, // Sub menu - Direct Debugging
'bindAddGridRow': {'shortcuts': this.keyboardShortcut.add_grid_row}, // Subnode Grid Add Row
'bindOpenQuickSearch': {'shortcuts': this.keyboardShortcut.open_quick_search}, // Subnode Grid Refresh Row
};
this.bindShortcuts();
}
};
this.shortcutMethods = {
...(prefStore.getPreferences('browser', 'main_menu_file')?.value) && {'bindMainMenu': {
'shortcuts': [this.keyboardShortcut.file_shortcut,
this.keyboardShortcut.object_shortcut, this.keyboardShortcut.tools_shortcut,
this.keyboardShortcut.help_shortcut],
}}, // Main menu
'bindRightPanel': {'shortcuts': [this.keyboardShortcut.tabbed_panel_backward, this.keyboardShortcut.tabbed_panel_forward]}, // Main window panels
'bindLeftTree': {'shortcuts': this.keyboardShortcut.left_tree_shortcut}, // Main menu,
'bindSubMenuQueryTool': {'shortcuts': this.keyboardShortcut.sub_menu_query_tool}, // Sub menu - Open Query Tool,
'bindSubMenuViewData': {'shortcuts': this.keyboardShortcut.sub_menu_view_data}, // Sub menu - Open View Data,
'bindSubMenuSearchObjects': {'shortcuts': this.keyboardShortcut.sub_menu_search_objects}, // Sub menu - Open search objects,
'bindSubMenuProperties': {'shortcuts': this.keyboardShortcut.sub_menu_properties}, // Sub menu - Edit Properties,
'bindSubMenuCreate': {'shortcuts': this.keyboardShortcut.sub_menu_create}, // Sub menu - Create Object,
'bindSubMenuDelete': {'shortcuts': this.keyboardShortcut.sub_menu_delete}, // Sub menu - Delete object,
'bindSubMenuRefresh': {'shortcuts': this.keyboardShortcut.sub_menu_refresh, 'bindElem': '#tree'}, // Sub menu - Refresh object,
'bindContextMenu': {'shortcuts': this.keyboardShortcut.context_menu}, // Sub menu - Open context menu,
'bindDirectDebugging': {'shortcuts': this.keyboardShortcut.direct_debugging}, // Sub menu - Direct Debugging
'bindAddGridRow': {'shortcuts': this.keyboardShortcut.add_grid_row}, // Subnode Grid Add Row
'bindOpenQuickSearch': {'shortcuts': this.keyboardShortcut.open_quick_search}, // Subnode Grid Refresh Row
};
this.bindShortcuts();
}
});
},
bindShortcuts: function() {
const self = this;
@@ -116,7 +119,7 @@ _.extend(pgBrowser.keyboardNavigation, {
}
if(menuLabel) {
document.querySelector(`#main-menu-container button[data-label="${menuLabel}"]`)?.click();
document.querySelector(`div[data-test="app-menu-bar"] button[data-label="${menuLabel}"]`)?.click();
}
},
bindRightPanel: function(event, combo) {

View File

@@ -1,170 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import pgAdmin from 'sources/pgadmin';
import url_for from 'sources/url_for';
import gettext from 'sources/gettext';
import 'wcdocker';
import pgWindow from 'sources/window';
import Notify from '../../../static/js/helpers/Notifier';
import getApiInstance from '../../../static/js/api_instance';
const pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
let wcDocker = window.wcDocker;
/* Add cache related methods and properties */
_.extend(pgBrowser, {
lock_layout_levels : {
PREVENT_DOCKING: 'docking',
FULL: 'full',
NONE: 'none',
},
// Build the default layout
buildDefaultLayout: function(docker) {
let browserPanel = docker.addPanel('browser', wcDocker.DOCK.LEFT);
let dashboardPanel = docker.addPanel(
'dashboard', wcDocker.DOCK.RIGHT, browserPanel);
docker.addPanel('properties', wcDocker.DOCK.STACKED, dashboardPanel, {
tabOrientation: wcDocker.TAB.TOP,
});
docker.addPanel('sql', wcDocker.DOCK.STACKED, dashboardPanel);
docker.addPanel(
'statistics', wcDocker.DOCK.STACKED, dashboardPanel);
docker.addPanel(
'dependencies', wcDocker.DOCK.STACKED, dashboardPanel);
docker.addPanel(
'dependents', wcDocker.DOCK.STACKED, dashboardPanel);
docker.addPanel(
'processes', wcDocker.DOCK.STACKED, dashboardPanel);
},
save_current_layout: function(layout_id, docker) {
if(docker) {
let layout = docker.save(),
settings = { setting: layout_id, value: layout };
getApiInstance().post(url_for('settings.store_bulk'), settings);
}
},
restore_layout: function(docker, layout, defaultLayoutCallback, checkLayout= false) {
// Try to restore the layout if there is one
if (layout != '') {
try {
docker.restore(layout);
if(checkLayout) {
// Check restore layout is restored pgAdmin 4 layout successfully if not then reset layout to default pgAdmin 4 layout.
if((docker.findPanels('properties').length == 0 || docker.findPanels('browser').length == 0) && defaultLayoutCallback){
// clear the wcDocker before reset layout.
docker.clear();
Notify.info(gettext('pgAdmin has reset the layout because the previously saved layout is invalid.'), null);
defaultLayoutCallback(docker);
}
}
}
catch(err) {
docker.clear();
if(defaultLayoutCallback) {
defaultLayoutCallback(docker);
}
}
} else {
if(defaultLayoutCallback) {
defaultLayoutCallback(docker);
}
}
/* preference available only with top/opener browser. */
let browser = pgWindow.pgAdmin.Browser;
/* interval required initially as preference load may take time */
let cacheIntervalId = setInterval(()=> {
let browserPref = browser.get_preferences_for_module('browser');
if(browserPref) {
clearInterval(cacheIntervalId);
browser.reflectLocklayoutChange(docker);
browser.onPreferencesChange('browser', function() {
browser.reflectLocklayoutChange(docker);
});
}
}, 5000);
},
reflectLocklayoutChange: function(docker) {
let browser = pgWindow.pgAdmin.Browser;
let browserPref = browser.get_preferences_for_module('browser');
browser.lock_layout(docker, browserPref.lock_layout);
},
lock_layout: function(docker, op) {
let menu_items = [];
if('mnu_locklayout' in this.all_menus_cache['file']) {
menu_items = this.all_menus_cache['file']['mnu_locklayout']['menu_items'];
}
switch(op) {
case this.lock_layout_levels.PREVENT_DOCKING:
docker.lockLayout(wcDocker.LOCK_LAYOUT_LEVEL.PREVENT_DOCKING);
break;
case this.lock_layout_levels.FULL:
docker.lockLayout(wcDocker.LOCK_LAYOUT_LEVEL.FULL);
break;
case this.lock_layout_levels.NONE:
docker.lockLayout(wcDocker.LOCK_LAYOUT_LEVEL.NONE);
break;
}
if(menu_items) {
_.each(menu_items, function(menu_item) {
if(menu_item.name != ('mnu_lock_'+op)) {
menu_item.change_checked(false);
} else {
menu_item.change_checked(true);
}
});
}
},
save_lock_layout: function(op) {
let browser = pgWindow.pgAdmin.Browser;
getApiInstance().put(
url_for('browser.lock_layout'),
JSON.stringify({
'value': op,
})
).then(()=> {
browser.cache_preferences('browser');
}).catch(function(error) {
Notify.pgNotifier('error', error, gettext('Failed to save the lock layout setting.'));
});
},
mnu_lock_docking: function() {
this.lock_layout(this.docker, this.lock_layout_levels.PREVENT_DOCKING);
this.save_lock_layout(this.lock_layout_levels.PREVENT_DOCKING);
},
mnu_lock_full: function() {
this.lock_layout(this.docker, this.lock_layout_levels.FULL);
this.save_lock_layout(this.lock_layout_levels.FULL);
},
mnu_lock_none: function() {
this.lock_layout(this.docker, this.lock_layout_levels.NONE);
this.save_lock_layout(this.lock_layout_levels.NONE);
},
});
export {pgBrowser};

View File

@@ -7,12 +7,14 @@
//
//////////////////////////////////////////////////////////////
import {getNodeView, removeNodeView} from './node_view';
import Notify from '../../../static/js/helpers/Notifier';
import _ from 'lodash';
import getApiInstance from '../../../static/js/api_instance';
import { removePanelView } from './panel_view';
import { TAB_CHANGE } from './constants';
import { BROWSER_PANELS } from './constants';
import React from 'react';
import ObjectNodeProperties from '../../../misc/properties/ObjectNodeProperties';
import ErrorBoundary from '../../../static/js/helpers/ErrorBoundary';
import toPx from '../../../static/js/to_px';
import usePreferences from '../../../preferences/static/js/store';
define('pgadmin.browser.node', [
'sources/gettext', 'sources/pgadmin',
@@ -21,9 +23,6 @@ define('pgadmin.browser.node', [
], function(
gettext, pgAdmin, generateUrl, commonUtils
) {
let wcDocker = window.wcDocker;
const pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
// It has already been defined.
@@ -89,8 +88,11 @@ define('pgadmin.browser.node', [
dialogHelp: '',
epasHelp: false,
title: function(o, d) {
return o.label + (d ? (' - ' + d.label) : '');
title: function(d, action) {
if(action == 'create') {
return gettext('Create - %s', this.label);
}
return d.label??'';
},
hasId: true,
///////
@@ -282,114 +284,6 @@ define('pgadmin.browser.node', [
return true;
}
},
addUtilityPanel: function(width, height, docker) {
let body = window.document.body,
el = document.createElement('div'),
dockerObject = docker || pgBrowser.docker;
body.insertBefore(el, body.firstChild);
let new_width = screen.width < 700 ? screen.width * 0.95 : screen.width * 0.5,
new_height = screen.height < 500 ? screen.height * 0.95 : screen.height * 0.4;
if (!_.isUndefined(width) && !_.isNull(width)) {
new_width = width;
}
if (!_.isUndefined(height) && !_.isNull(height)) {
new_height = height;
}
let x = (body.offsetWidth - new_width) / 2;
let y = (body.offsetHeight - new_height) / 4;
let new_panel = dockerObject.addPanel(
'utility_props', window.wcDocker.DOCK.FLOAT, undefined, {
w: new_width,
h: new_height,
x: (x),
y: (y),
}
);
/*set movable false to prevent dialog from docking,
by setting this we can able to move the dialog but can't dock it
in to the frame. e.g: can't dock it in to properties and other tabs. */
setTimeout(function() {
new_panel.moveable(false);
}, 0);
body.removeChild(el);
return new_panel;
},
registerDockerPanel: function(docker, name, params) {
let w = docker || pgBrowser.docker,
p = w.findPanels(name);
if (p && p.length == 1)
return;
p = new pgBrowser.Panel({
name: name,
showTitle: true,
isCloseable: true,
isPrivate: true,
isLayoutMember: false,
canMaximise: true,
content: '<div class="obj_properties container-fluid h-100"></div>',
...params,
});
p.load(w);
},
registerUtilityPanel: function(docker) {
let w = docker || pgBrowser.docker,
p = w.findPanels('utility_props');
if (p && p.length == 1)
return;
let events = {};
p = new pgBrowser.Panel({
name: 'utility_props',
showTitle: true,
isCloseable: true,
isPrivate: true,
isLayoutMember: false,
canMaximise: true,
elContainer: true,
content: '<div class="obj_properties"></div>',
onCreate: function(myPanel, container) {
container.classList.add('pg-no-overflow');
},
events: events,
});
p.load(w);
},
register_node_panel: function() {
let w = pgBrowser.docker,
p = w.findPanels('node_props');
if (p && p.length == 1)
return;
let events = {};
p = new pgBrowser.Panel({
name: 'node_props',
showTitle: true,
isCloseable: true,
isPrivate: true,
isLayoutMember: false,
canMaximise: true,
elContainer: true,
content: '<div class="obj_properties"></div>',
onCreate: function(myPanel, container) {
container.classList.add('pg-no-overflow');
},
events: events,
});
p.load(pgBrowser.docker);
},
/*
* Default script type menu for node.
*
@@ -447,194 +341,132 @@ define('pgadmin.browser.node', [
**/
show_obj_properties: function(args, item) {
let t = pgBrowser.tree,
i = (args && args.item) || item || t.selected(),
d = i ? t.itemData(i) : undefined,
o = this,
l = o.title.apply(this, [d]),
p;
nodeItem = (args && args.item) || item || t.selected(),
nodeData = nodeItem ? t.itemData(nodeItem) : undefined,
panelTitle = this.title(nodeData, args.action),
treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(nodeItem);
// Make sure - the properties dialog type registered
pgBrowser.Node.register_node_panel();
// No node selected.
if (!d)
if (!nodeData)
return;
let self = this,
isParent = (_.isArray(this.parent_type) ?
function(_d) {
return (_.indexOf(self.parent_type, _d._type) != -1);
} : function(_d) {
return (self.parent_type == _d._type);
}),
addPanel = function() {
let body = window.document.body,
el = document.createElement('div');
body.insertBefore(el, body.firstChild);
let w, h, x, y;
if(screen.width < 800) {
w = pgAdmin.toPx(el, '95%', 'width', true);
} else {
w = pgAdmin.toPx(
el, self.width || (pgBrowser.stdW.default + 'px'),
'width', true
);
/* Fit to standard sizes */
if(w <= pgBrowser.stdW.sm) {
w = pgBrowser.stdW.sm;
} else {
if(w <= pgBrowser.stdW.md) {
w = pgBrowser.stdW.md;
} else {
w = pgBrowser.stdW.lg;
}
}
}
if(screen.height < 600) {
h = pgAdmin.toPx(el, '95%', 'height', true);
} else {
h = pgAdmin.toPx(
el, self.height || (pgBrowser.stdH.default + 'px'),
'height', true
);
/* Fit to standard sizes */
if(h <= pgBrowser.stdH.sm) {
h = pgBrowser.stdH.sm;
} else {
if(h <= pgBrowser.stdH.md) {
h = pgBrowser.stdH.md;
} else {
h = pgBrowser.stdH.lg;
}
}
}
x = (body.offsetWidth - w) / 2;
y = (body.offsetHeight - h) / 4;
// If the screen resolution is higher, but - it is zoomed, dialog
// may be go out of window, and will not be accessible through the
// keyboard.
if (w > window.innerWidth) {
x = 0;
w = window.innerWidth;
}
if (h > window.innerHeight) {
y = 0;
h = window.innerHeight;
}
let new_panel = pgBrowser.docker.addPanel(
'node_props', wcDocker.DOCK.FLOAT, undefined, {
w: w + 'px',
h: h + 'px',
x: x + 'px',
y: y + 'px',
}
);
body.removeChild(el);
return new_panel;
};
const isParent = (_.isArray(this.parent_type) ?
(_d)=>{
return (_.indexOf(this.parent_type, _d._type) != -1);
} : (_d)=>{
return (this.parent_type == _d._type);
});
if (args.action == 'create') {
// If we've parent, we will get the information of it for
// proper object manipulation.
//
// You know - we're working with RDBMS, relation is everything
// for us.
if (self.parent_type && !isParent(d)) {
// In browser tree, I can be under any node, But - that
// does not mean, it is my parent.
//
// We have some group nodes too.
//
// i.e.
// Tables, Views, etc. nodes under Schema node
//
// And, actual parent of a table is schema, not Tables.
while (i && t.hasParent(i)) {
i = t.parent(i);
let pd = t.itemData(i);
if (this.parent_type && !isParent(nodeData)) {
// actual parent of a table is schema, not Tables.
while (nodeItem && t.hasParent(nodeItem)) {
nodeItem = t.parent(nodeItem);
let pd = t.itemData(nodeItem);
if (isParent(pd)) {
// Assign the data, this is my actual parent.
d = pd;
nodeData = pd;
break;
}
}
}
// Seriously - I really don't have parent data present?
//
// The only node - which I know - who does not have parent
// node, is the Server Group (and, comes directly under root
// node - which has no parent.)
if (!d || (this.parent_type != null && !isParent(d))) {
// The only node who does not have parent is the Server Group
if (!nodeData || (this.parent_type != null && !isParent(nodeData))) {
// It should never come here.
// If it is here, that means - we do have some bug in code.
return;
}
l = gettext('Create - %s', this.label);
if (this.type == 'server') {
l = gettext('Register - %s', this.label);
}
p = addPanel();
setTimeout(function() {
o.showProperties(i, d, p, args.action);
}, 10);
} else {
if (pgBrowser.Node.panels && pgBrowser.Node.panels[d.id] &&
pgBrowser.Node.panels[d.id].$container) {
p = pgBrowser.Node.panels[d.id];
/** TODO ::
* Run in edit mode (if asked) only when it is
* not already been running edit mode
**/
let mode = p.$container.attr('action-mode');
if (mode) {
let msg = gettext('Are you sure want to stop editing the properties of %s "%s"?');
if (args.action == 'edit') {
msg = gettext('Are you sure want to reset the current changes and re-open the panel for %s "%s"?');
}
Notify.confirm(
gettext('Edit in progress?'),
commonUtils.sprintf(msg, o.label.toLowerCase(), d.label),
function() {
setTimeout(function() {
o.showProperties(i, d, p, args.action);
}, 10);
},
null).show();
} else {
setTimeout(function() {
o.showProperties(i, d, p, args.action);
}, 10);
const panelId = _.uniqueId(BROWSER_PANELS.EDIT_PROPERTIES);
const onClose = (force=false)=>pgBrowser.docker.close(panelId, force);
const onSave = (newNodeData)=>{
// Clear the cache for this node now.
setTimeout(()=>{
this.clear_cache.apply(this, item);
}, 0);
try {
pgBrowser.Events.trigger(
'pgadmin:browser:tree:add', _.clone(newNodeData.node),
_.clone(treeNodeInfo)
);
} catch (e) {
console.warn(e.stack || e);
}
} else {
pgBrowser.Node.panels = pgBrowser.Node.panels || {};
p = pgBrowser.Node.panels[d.id] = addPanel();
onClose();
};
this.showPropertiesDialog(panelId, panelTitle, {
treeNodeInfo: treeNodeInfo,
item: nodeItem,
nodeData: nodeData,
actionType: 'create',
onSave: onSave,
onClose: onClose,
});
} else {
const panelId = BROWSER_PANELS.EDIT_PROPERTIES+nodeData.id;
const onClose = (force=false)=>pgBrowser.docker.close(panelId, force);
const onSave = (newNodeData)=>{
let _old = nodeData,
_new = newNodeData.node,
info = treeNodeInfo;
setTimeout(function() {
o.showProperties(i, d, p, args.action);
}, 10);
// Clear the cache for this node now.
setTimeout(()=>{
this.clear_cache.apply(this, item);
}, 0);
pgBrowser.Events.trigger(
'pgadmin:browser:tree:update',
_old, _new, info, {
success: function(_item, _newNodeData, _oldNodeData) {
pgBrowser.Events.trigger(
'pgadmin:browser:node:updated', _item, _newNodeData,
_oldNodeData
);
pgBrowser.Events.trigger(
'pgadmin:browser:node:' + _newNodeData._type + ':updated',
_item, _newNodeData, _oldNodeData
);
},
}
);
onClose();
};
if(pgBrowser.docker.find(panelId)) {
let msg = gettext('Are you sure want to stop editing the properties of %s "%s"?');
if (args.action == 'edit') {
msg = gettext('Are you sure want to reset the current changes and re-open the panel for %s "%s"?');
}
pgAdmin.Browser.notifier.confirm(
gettext('Edit in progress?'),
commonUtils.sprintf(msg, this.label.toLowerCase(), nodeData.label),
()=>{
this.showPropertiesDialog(panelId, panelTitle, {
treeNodeInfo: treeNodeInfo,
item: nodeItem,
nodeData: nodeData,
actionType: 'edit',
onSave: onSave,
onClose: onClose,
}, true);
},
null
);
} else {
this.showPropertiesDialog(panelId, panelTitle, {
treeNodeInfo: treeNodeInfo,
item: nodeItem,
nodeData: nodeData,
actionType: 'edit',
onSave: onSave,
onClose: onClose,
});
}
}
p.title(l);
p.icon('icon-' + this.type);
// Make sure the properties dialog is visible
p.focus();
},
// Delete the selected object
delete_obj: function(args, item) {
@@ -668,7 +500,7 @@ define('pgadmin.browser.node', [
if (!(_.isFunction(obj.canDropCascade) ?
obj.canDropCascade.apply(obj, [d, i]) : obj.canDropCascade)) {
Notify.error(
pgAdmin.Browser.notifier.error(
gettext('The %s "%s" cannot be dropped.', obj.label, d.label),
10000
);
@@ -685,24 +517,24 @@ define('pgadmin.browser.node', [
if (!(_.isFunction(obj.canDrop) ?
obj.canDrop.apply(obj, [d, i]) : obj.canDrop)) {
Notify.error(
pgAdmin.Browser.notifier.error(
gettext('The %s "%s" cannot be dropped/removed.', obj.label, d.label),
10000
);
return;
}
}
Notify.confirm(title, msg,
pgAdmin.Browser.notifier.confirm(title, msg,
function() {
getApiInstance().delete(
obj.generate_url(i, input.url, d, true),
).then(({data: res})=> {
if(res.success == 2){
Notify.error(res.info, null);
pgAdmin.Browser.notifier.error(res.info, null);
return;
}
if (res.success == 0) {
Notify.alert(res.errormsg, res.info);
pgAdmin.Browser.notifier.alert(res.errormsg, res.info);
} else {
// Remove the node from tree and set collection node as selected.
let selectNextNode = true;
@@ -727,7 +559,7 @@ define('pgadmin.browser.node', [
console.warn(e.stack || e);
}
}
Notify.alert(gettext('Error dropping/removing %s: "%s"', obj.label, objName), errmsg);
pgAdmin.Browser.notifier.alert(gettext('Error dropping/removing %s: "%s"', obj.label, objName), errmsg);
});
}
);
@@ -773,7 +605,7 @@ define('pgadmin.browser.node', [
// Callback to render query editor
show_query_tool: function(args, item) {
let preference = pgBrowser.get_preference('sqleditor', 'copy_sql_to_query_tool');
let preference = usePreferences.getState().getPreferences('sqleditor', 'copy_sql_to_query_tool');
let t = pgBrowser.tree,
i = item || t.selected(),
d = i ? t.itemData(i) : undefined;
@@ -862,7 +694,7 @@ define('pgadmin.browser.node', [
pgBrowser.Node.callbacks.change_server_background(item, data);
},
// Callback called - when a node is selected in browser tree.
selected: function(item, data, browser, _argsList, _event, actionSource) {
selected: function(item, data, browser) {
// Show the information about the selected node in the below panels,
// which are visible at this time:
// + Properties
@@ -870,34 +702,11 @@ define('pgadmin.browser.node', [
// + Dependents
// + Dependencies
// + Statistics
let b = browser || pgBrowser,
t = b.tree,
d = data || t.itemData(item);
let b = browser || pgBrowser;
// Update the menu items
pgAdmin.Browser.enable_disable_menus.apply(b, [item]);
if (d && b) {
if ('properties' in b.panels &&
b.panels['properties'] &&
b.panels['properties'].panel) {
if (actionSource != TAB_CHANGE) {
const propertiesPanel = b.panels['properties'].panel.$container.find('.obj_properties').first();
if (propertiesPanel) {
removePanelView(propertiesPanel[0]);
}
}
if (b.panels['properties'].panel.isVisible()) {
this.showProperties(item, d, b.panels['properties'].panel);
}
}
}
pgBrowser.Events.trigger('pgadmin:browser:tree:update-tree-state',
item);
return true;
@@ -922,7 +731,7 @@ define('pgadmin.browser.node', [
},
opened: function(item) {
let tree = pgBrowser.tree,
auto_expand = pgBrowser.get_preference('browser', 'auto_expand_sole_children');
auto_expand = usePreferences.getState().getPreferences('browser', 'auto_expand_sole_children');
if (auto_expand && auto_expand.value && tree.children(item).length == 1) {
// Automatically expand the child node, if a treeview node has only a single child.
@@ -957,127 +766,55 @@ define('pgadmin.browser.node', [
item);
},
},
/**********************************************************************
* A hook (not a callback) to show object properties in given HTML
* element.
*
* This has been used for the showing, editing properties of the node.
* This has also been used for creating a node.
**/
showProperties: function(item, data, panel, action) {
let that = this,
j = panel.$container.find('.obj_properties').first();
showPropertiesDialog: function(panelId, panelTitle, dialogProps, update=false) {
const panelData = {
id: panelId,
title: panelTitle,
manualClose: true,
icon: `dialog-node-icon ${this.node_image?.(dialogProps.itemNodeData) ?? ('icon-' + this.type)}`,
content: (
<ErrorBoundary>
<ObjectNodeProperties
panelId={panelId}
node={this}
formType="dialog"
{...dialogProps}
/>
</ErrorBoundary>
)
};
// Callback to show object properties
let properties = function() {
let treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item);
getNodeView(
that.type, treeNodeInfo, 'properties', data, 'tab', j[0], this, onEdit
);
return;
}.bind(panel),
let w = toPx(this.width || (pgBrowser.stdW.default + 'px'), 'width', true);
let h = toPx(this.height || (pgBrowser.stdH.default + 'px'), 'height', true);
editFunc = function() {
let self = this;
if (action && action == 'properties') {
action = 'edit';
}
self.$container.attr('action-mode', action);
self.icon(
_.isFunction(that['node_image']) ?
(that['node_image']).apply(that, [data]) :
(that['node_image'] || ('icon-' + that.type))
);
/* Remove any dom rendered by getNodeView */
removeNodeView(j[0]);
/* getSchema is a schema for React. Get the react node view */
let treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item);
getNodeView(
that.type, treeNodeInfo, action, data, 'dialog', j[0], this, onEdit,
(nodeData)=>{
if(nodeData.node) {
onSaveFunc(nodeData.node, treeNodeInfo);
if(nodeData.success === 0) {
Notify.alert(gettext('Error'),
gettext(nodeData.errormsg)
);
}
}
}
);
return;
}.bind(panel),
updateTreeItem = function(obj, tnode, node_info) {
let _old = data,
_new = tnode,
info = node_info;
// Clear the cache for this node now.
setTimeout(function() {
obj.clear_cache.apply(obj, item);
}, 0);
pgBrowser.Events.trigger(
'pgadmin:browser:tree:update',
_old, _new, info, {
success: function(_item, _newNodeData, _oldNodeData) {
pgBrowser.Events.trigger(
'pgadmin:browser:node:updated', _item, _newNodeData,
_oldNodeData
);
pgBrowser.Events.trigger(
'pgadmin:browser:node:' + _newNodeData._type + ':updated',
_item, _newNodeData, _oldNodeData
);
},
}
);
this.close();
},
saveNewNode = function(obj, tnode, node_info) {
// Clear the cache for this node now.
setTimeout(function() {
obj.clear_cache.apply(obj, item);
}, 0);
try {
pgBrowser.Events.trigger(
'pgadmin:browser:tree:add', _.clone(tnode),
_.clone(node_info)
);
} catch (e) {
console.warn(e.stack || e);
}
this.close();
}.bind(panel, that),
editInNewPanel = function() {
// Open edit in separate panel
setTimeout(function() {
that.callbacks.show_obj_properties.apply(that, [{
'action': 'edit',
'item': item,
}]);
}, 0);
},
onSaveFunc = updateTreeItem.bind(panel, that),
onEdit = editFunc.bind(panel);
if (action) {
if (action == 'create') {
onSaveFunc = saveNewNode;
}
if (action != 'properties') {
// We need to keep track edit/create mode for this panel.
editFunc();
} else {
properties();
}
/* Fit to standard sizes */
if(w <= pgBrowser.stdW.sm) {
w = pgBrowser.stdW.sm;
} else {
/* Show properties */
onEdit = editInNewPanel.bind(panel);
properties();
if(w <= pgBrowser.stdW.md) {
w = pgBrowser.stdW.md;
} else {
w = pgBrowser.stdW.lg;
}
}
if(h <= pgBrowser.stdH.sm) {
h = pgBrowser.stdH.sm;
} else {
if(h <= pgBrowser.stdH.md) {
h = pgBrowser.stdH.md;
} else {
h = pgBrowser.stdH.lg;
}
}
if(update) {
dialogProps.onClose(true);
setTimeout(()=>{
pgBrowser.docker.openDialog(panelData, w, h);
}, 10);
} else {
pgBrowser.docker.openDialog(panelData, w, h);
}
},
_find_parent_node: function(t, i, d) {

View File

@@ -1,225 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import React from 'react';
import ReactDOM from 'react-dom';
import pgAdmin from 'sources/pgadmin';
import getApiInstance from 'sources/api_instance';
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';
import Theme from '../../../static/js/Theme';
/* The entry point for rendering React based view in properties, called in node.js */
export function getNodeView(nodeType, treeNodeInfo, actionType, itemNodeData, formType, container, containerPanel, onEdit, onSave) {
let nodeObj = pgAdmin.Browser.Nodes[nodeType];
let serverInfo = treeNodeInfo && ('server' in treeNodeInfo) &&
pgAdmin.Browser.serverInfo && pgAdmin.Browser.serverInfo[treeNodeInfo.server._id];
let inCatalog = treeNodeInfo && ('catalog' in treeNodeInfo);
let urlBase = generateNodeUrl.call(nodeObj, treeNodeInfo, actionType, itemNodeData, false, nodeObj.url_jump_after_node);
const api = getApiInstance();
const url = (isNew)=>{
return urlBase + (isNew ? '' : itemNodeData._id);
};
let isDirty = false; // usefull for warnings
let warnOnCloseFlag = true;
const confirmOnCloseReset = pgAdmin.Browser.get_preferences_for_module('browser').confirm_on_properties_close;
let updatedData = ['table', 'partition'].includes(nodeType) && !_.isEmpty(itemNodeData.rows_cnt) ? {rows_cnt: itemNodeData.rows_cnt} : undefined;
let onError = (err)=> {
if(err.response){
console.error('error resp', err.response);
} else if(err.request){
console.error('error req', err.request);
} else if(err.message){
console.error('error msg', err.message);
}
};
/* Called when dialog is opened in edit mode, promise required */
let initData = ()=>new Promise((resolve, reject)=>{
if(actionType === 'create') {
resolve({});
} else {
api.get(url(false))
.then((res)=>{
resolve(res.data);
})
.catch((err)=>{
Notify.pgNotifier('error', err, '', function(msg) {
if (msg == 'CRYPTKEY_SET') {
return Promise.resolve(initData());
} else if (msg == 'CRYPTKEY_NOT_SET') {
reject(gettext('The master password is not set.'));
}
reject(err);
});
});
}
});
/* on save button callback, promise required */
const onSaveClick = (isNew, data)=>new Promise((resolve, reject)=>{
return api({
url: url(isNew),
method: isNew ? 'POST' : 'PUT',
data: data,
}).then((res)=>{
/* Don't warn the user before closing dialog */
warnOnCloseFlag = false;
resolve(res.data);
onSave && onSave(res.data);
}).catch((err)=>{
Notify.pgNotifier('error-noalert', err, '', function(msg) {
if (msg == 'CRYPTKEY_SET') {
return Promise.resolve(onSaveClick(isNew, data));
} else if (msg == 'CRYPTKEY_NOT_SET') {
reject(gettext('The master password is not set.'));
}
reject(err);
});
});
});
/* Called when switched to SQL tab, promise required */
const getSQLValue = (isNew, changedData)=>{
const msqlUrl = generateNodeUrl.call(nodeObj, treeNodeInfo, 'msql', itemNodeData, !isNew, nodeObj.url_jump_after_node);
return new Promise((resolve, reject)=>{
api({
url: msqlUrl,
method: 'GET',
params: changedData,
}).then((res)=>{
resolve(res.data.data);
}).catch((err)=>{
onError(err);
reject(err);
});
});
};
/* Callback for help button */
const onHelp = (isSqlHelp=false, isNew=false)=>{
if(isSqlHelp) {
let server = treeNodeInfo.server;
let helpUrl = pgAdmin.Browser.utils.pg_help_path;
let fullUrl = '';
if (server.server_type == 'ppas' && nodeObj.epasHelp) {
fullUrl = getEPASHelpUrl(server.version);
} else {
if (nodeObj.sqlCreateHelp == '' && nodeObj.sqlAlterHelp != '') {
fullUrl = getHelpUrl(helpUrl, nodeObj.sqlAlterHelp, server.version);
} else if (nodeObj.sqlCreateHelp != '' && nodeObj.sqlAlterHelp == '') {
fullUrl = getHelpUrl(helpUrl, nodeObj.sqlCreateHelp, server.version);
} else {
if (isNew) {
fullUrl = getHelpUrl(helpUrl, nodeObj.sqlCreateHelp, server.version);
} else {
fullUrl = getHelpUrl(helpUrl, nodeObj.sqlAlterHelp, server.version);
}
}
}
window.open(fullUrl, 'postgres_help');
} else {
window.open(nodeObj.dialogHelp, 'pgadmin_help');
}
};
/* A warning before closing the dialog with unsaved changes, based on preference */
let warnBeforeChangesLost = (yesCallback)=>{
let confirmOnClose = pgAdmin.Browser.get_preferences_for_module('browser').confirm_on_properties_close;
if (warnOnCloseFlag && confirmOnClose) {
if(isDirty){
Notify.confirm(
gettext('Warning'),
gettext('Changes will be lost. Are you sure you want to close the dialog?'),
function() {
yesCallback();
return true;
},
function() {
return true;
}
);
} else {
return true;
}
return false;
} else {
yesCallback();
return true;
}
};
/* Bind the wcDocker dialog close event and check if user should be warned */
if (containerPanel.closeable()) {
containerPanel.on(window.wcDocker.EVENT.CLOSING, warnBeforeChangesLost.bind(
containerPanel,
function() {
containerPanel.off(window.wcDocker.EVENT.CLOSING);
/* Always clean up the react mounted dom before closing */
removeNodeView(container);
containerPanel.close();
}
));
}
/* All other useful details can go with this object */
const viewHelperProps = {
mode: actionType,
serverInfo: serverInfo ? {
type: serverInfo.server_type,
version: serverInfo.version,
}: undefined,
inCatalog: inCatalog,
};
let schema = nodeObj.getSchema.call(nodeObj, treeNodeInfo, itemNodeData);
// Show/Hide security group for nodes under the catalog
if('catalog' in treeNodeInfo
&& formType !== 'tab') {
schema.filterGroups = [gettext('Security')];
}
/* Fire at will, mount the DOM */
ReactDOM.render(
<Theme>
<SchemaView
key={itemNodeData?._id}
formType={formType}
getInitData={initData}
updatedData={updatedData}
schema={schema}
viewHelperProps={viewHelperProps}
onSave={onSaveClick}
onClose={()=>containerPanel.close()}
onHelp={onHelp}
onEdit={onEdit}
onDataChange={(dataChanged)=>{
isDirty = dataChanged;
}}
confirmOnCloseReset={confirmOnCloseReset}
hasSQL={nodeObj.hasSQL && (actionType === 'create' || actionType === 'edit')}
getSQLValue={getSQLValue}
disableSqlHelp={nodeObj.sqlAlterHelp == '' && nodeObj.sqlCreateHelp == '' && !nodeObj.epasHelp}
disableDialogHelp={nodeObj.dialogHelp == undefined || nodeObj.dialogHelp == ''}
/>
</Theme>, container);
}
/* When switching from normal node to collection node, clean up the React mounted DOM */
export function removeNodeView(container) {
ReactDOM.unmountComponentAtNode(container);
}

View File

@@ -1,290 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import { getPanelView } from './panel_view';
import _ from 'lodash';
define(
['sources/pgadmin', 'wcdocker'],
function(pgAdmin) {
let pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {},
wcDocker = window.wcDocker;
pgAdmin.Browser.Panel = function(options) {
let defaults = [
'name', 'title', 'width', 'height', 'showTitle', 'isCloseable',
'isPrivate', 'isLayoutMember', 'content', 'icon', 'events', 'onCreate', 'elContainer',
'canHide', 'limit', 'extraClasses', 'canMaximise',
];
_.extend(this, _.pick(options, defaults));
};
_.extend(pgAdmin.Browser.Panel.prototype, {
name: '',
title: '',
width: 300,
height: 600,
showTitle: true,
isCloseable: true,
isPrivate: false,
isLayoutMember: true,
content: '',
icon: '',
panel: null,
onCreate: null,
elContainer: false,
canMaximise: false,
limit: null,
extraClasses: null,
load: function(docker, title) {
let that = this;
if (!that.panel) {
docker.registerPanelType(that.name, {
title: that.title,
isPrivate: that.isPrivate,
limit: that.limit,
isLayoutMember: that.isLayoutMember,
onCreate: function(myPanel) {
myPanel.panelData = {
pgAdminName: that.name,
};
myPanel.initSize(that.width, that.height);
if (!that.showTitle)
myPanel.title(false);
else {
let title_elem = '<a href="#" tabindex="-1" class="panel-link-heading">' + (title || that.title) + '</a>';
myPanel.title(title_elem);
if (that.icon != '')
myPanel.icon(that.icon);
}
let container = document.createElement('div');
container.setAttribute('class', 'pg-panel-content');
container.innerHTML = that.content;
// Add extra classes
if (!_.isNull('extraClasses')) {
container.classList.add(that.extraClasses);
}
myPanel.maximisable(!!that.canMaximise);
myPanel.closeable(!!that.isCloseable);
myPanel.layout().addItem(container);
that.panel = myPanel;
if (that.events && _.isObject(that.events)) {
_.each(that.events, function(v, k) {
if (v && _.isFunction(v)) {
myPanel.on(k, v);
}
});
}
_.each([
wcDocker.EVENT.UPDATED, wcDocker.EVENT.VISIBILITY_CHANGED,
wcDocker.EVENT.BEGIN_DOCK, wcDocker.EVENT.END_DOCK,
wcDocker.EVENT.GAIN_FOCUS, wcDocker.EVENT.LOST_FOCUS,
wcDocker.EVENT.CLOSED, wcDocker.EVENT.BUTTON,
wcDocker.EVENT.ATTACHED, wcDocker.EVENT.DETACHED,
wcDocker.EVENT.MOVE_STARTED, wcDocker.EVENT.MOVE_ENDED,
wcDocker.EVENT.MOVED, wcDocker.EVENT.RESIZE_STARTED,
wcDocker.EVENT.RESIZE_ENDED, wcDocker.EVENT.RESIZED,
wcDocker.EVENT.SCROLLED,
], function(ev) {
myPanel.on(ev, that.eventFunc.bind(myPanel, ev));
});
if (that.onCreate && _.isFunction(that.onCreate)) {
that.onCreate.apply(that, [myPanel, container]);
}
// Prevent browser from opening the drag file.
// Using addEventListener to avoid conflict with jquery.drag
['dragover', 'drop'].forEach((eventName)=>{
container.addEventListener(eventName, function(event) {
event.stopPropagation();
event.preventDefault();
});
});
if (that.elContainer) {
myPanel.pgElContainer = container;
_.each([
wcDocker.EVENT.RESIZED, wcDocker.EVENT.ATTACHED,
wcDocker.EVENT.DETACHED, wcDocker.EVENT.VISIBILITY_CHANGED,
], function(ev) {
myPanel.on(ev, that.resizedContainer.bind(myPanel));
});
that.resizedContainer.apply(myPanel);
}
if (myPanel._type == 'dashboard' || myPanel._type == 'processes') {
getPanelView(
pgBrowser.tree,
container,
pgBrowser,
myPanel._type
);
}
// Re-render the dashboard panel when preference value 'show graph' gets changed.
pgBrowser.onPreferencesChange('dashboards', function() {
getPanelView(
pgBrowser.tree,
container,
pgBrowser,
myPanel._type
);
});
// Re-render the dashboard panel when preference value gets changed.
pgBrowser.onPreferencesChange('graphs', function() {
getPanelView(
pgBrowser.tree,
container,
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() && myPanel._type !== 'properties') {
getPanelView(
pgBrowser.tree,
container,
pgBrowser,
myPanel._type
);
}
});
pgBrowser.Events.on('pgadmin:database:connected', () => {
if(myPanel.isVisible() && myPanel._type !== 'properties') {
getPanelView(
pgBrowser.tree,
container,
pgBrowser,
myPanel._type
);
}
});
pgBrowser.Events.on('pgadmin-browser:tree:refreshing', () => {
if(myPanel.isVisible() && myPanel._type !== 'properties') {
getPanelView(
pgBrowser.tree,
container,
pgBrowser,
myPanel._type
);
}
});
},
});
}
},
eventFunc: function(eventName) {
let name = this.panelData.pgAdminName;
try {
pgBrowser.Events.trigger(
'pgadmin-browser:panel', eventName, this, arguments
);
pgBrowser.Events.trigger(
'pgadmin-browser:panel:' + eventName, this, arguments
);
if (name) {
pgBrowser.Events.trigger(
'pgadmin-browser:panel-' + name, eventName, this, arguments
);
pgBrowser.Events.trigger(
'pgadmin-browser:panel-' + name + ':' + eventName, this, arguments
);
}
} catch (e) {
console.warn(e.stack || e);
}
},
resizedContainer: function() {
let p = this;
if (p.pgElContainer && !p.pgResizeTimeout) {
if (!p.isVisible()) {
clearTimeout(p.pgResizeTimeout);
p.pgResizeTimeout = null;
return;
}
p.pgResizeTimeout = setTimeout(
function() {
let w = p.width(),
elAttr = 'xs';
p.pgResizeTimeout = null;
/** Calculations based on https://getbootstrap.com/docs/4.1/layout/grid/#grid-options **/
if (w >= 480) {
elAttr = 'sm';
}
if (w >= 768) {
elAttr = 'md';
}
if (w >= 992) {
elAttr = 'lg';
}
if (w >=1200) {
elAttr = 'xl';
}
p.pgElContainer.setAttribute('el', elAttr);
},
100
);
}
},
handleVisibility: function(eventName) {
let selectedPanel = pgBrowser.docker.findPanels(this._type)[0];
let isPanelVisible = selectedPanel.isVisible();
let container = selectedPanel
.layout()
.scene()
.find('.pg-panel-content');
if (isPanelVisible && ['dashboard', 'statistics', 'dependencies', 'dependents', 'sql', 'processes'].includes(selectedPanel._type) ) {
if (eventName == 'panelVisibilityChanged') {
getPanelView(
pgBrowser.tree,
container[0],
pgBrowser,
this._type
);
}
}
if (eventName == 'panelClosed' && selectedPanel._type == 'dashboard') {
getPanelView(
pgBrowser.tree,
container[0],
pgBrowser,
this._type,
false
);
}
}
});
return pgAdmin.Browser.Panel;
});

View File

@@ -1,148 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import React from 'react';
import ReactDOM from 'react-dom';
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';
import Processes from '../../../misc/bgprocess/static/js/Processes';
/* The entry point for rendering React based view in properties, called in node.js */
export function getPanelView(
tree,
container,
pgBrowser,
panelType,
panelVisible = true
) {
let item = !_.isNull(tree)? tree.selected(): null,
nodeData, node, treeNodeInfo, preferences, graphPref, dashPref;
if (item){
nodeData = tree.itemData(item);
node = nodeData && pgBrowser.Nodes[nodeData._type];
treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item);
dashPref = pgBrowser.get_preferences_for_module('dashboards');
graphPref = pgBrowser.get_preferences_for_module('graphs');
preferences = _.merge(dashPref, graphPref);
}
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}
dbConnected={!_.isUndefined(treeNodeInfo) && !_.isUndefined(treeNodeInfo['database']) ? treeNodeInfo.database.connected: false}
panelVisible={panelVisible}
/>
</Theme>,
container
);
}
if (panelType == 'statistics') {
ReactDOM.render(
<Theme>
<Statistics
treeNodeInfo={treeNodeInfo}
pgBrowser={pgBrowser}
nodeData={nodeData}
node={node}
item={item}
/>
</Theme>,
container
);
}
if (panelType == 'properties' && nodeData?.is_collection) {
ReactDOM.render(
<Theme>
<CollectionNodeView
treeNodeInfo={treeNodeInfo}
item={item}
itemNodeData={nodeData}
node={node}
pgBrowser={pgBrowser}
/>
</Theme>,
container
);
}
if (panelType == 'dependencies') {
ReactDOM.render(
<Theme>
<Dependencies
treeNodeInfo={treeNodeInfo}
pgBrowser={pgBrowser}
nodeData={nodeData}
node={node}
item={item}
/>
</Theme>,
container
);
}
if (panelType == 'dependents') {
ReactDOM.render(
<Theme>
<Dependents
treeNodeInfo={treeNodeInfo}
pgBrowser={pgBrowser}
nodeData={nodeData}
node={node}
item={item}
/>
</Theme>,
container
);
}
if (panelType == 'sql') {
ReactDOM.render(
<Theme>
<SQL
treeNodeInfo={treeNodeInfo}
pgBrowser={pgBrowser}
nodeData={nodeData}
node={node}
item={item}
did={((!_.isUndefined(treeNodeInfo)) && (!_.isUndefined(treeNodeInfo['database']))) ? treeNodeInfo['database']._id: 0}
dbConnected={!_.isUndefined(treeNodeInfo) && !_.isUndefined(treeNodeInfo['database']) ? treeNodeInfo.database.connected: false}
/>
</Theme>,
container
);
}
if (panelType == 'processes') {
ReactDOM.render(
<Theme>
<Processes />
</Theme>,
container
);
}
}
/* When switching from normal node to collection node, clean up the React mounted DOM */
export function removePanelView(container) {
ReactDOM.unmountComponentAtNode(container);
}

View File

@@ -1,148 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import pgAdmin from 'sources/pgadmin';
import url_for from 'sources/url_for';
import Notify from '../../../static/js/helpers/Notifier';
import { shortcutToString } from '../../../static/js/components/ShortcutTitle';
import gettext from 'sources/gettext';
import getApiInstance from '../../../static/js/api_instance';
const pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
/* Add cache related methods and properties */
_.extend(pgBrowser, {
/* This will hold preference data (Works as a cache object)
* Here node will be a key and it's preference data will be value
*/
preferences_cache: [],
/* This will be used by poller of new tabs/windows to check
* if preference cache is updated in parent/window.opener.
*/
prefcache_version: 0,
/* Generate a unique version number */
generate_preference_version: function() {
return (new Date()).getTime();
},
preference_version: function(version) {
if(version) {
this.prefcache_version = version;
}
else {
return this.prefcache_version;
}
},
/* Get cached preference */
get_preference: function(module, preference){
const self = this;
return _.find(
self.preferences_cache, {'module': module, 'name': preference}
);
},
/* Get all the preferences of a module */
get_preferences_for_module: function(module) {
let self = this;
let preferences = {};
_.forEach(
_.filter(self.preferences_cache, {'module': module}),
(preference) => {
preferences[preference.name] = preference.value;
}
);
if(Object.keys(preferences).length > 0) {
return preferences;
}
},
/* Get preference of an id, id is numeric */
get_preference_for_id : function(id) {
let self = this;
/* find returns undefined if not found */
return _.find(self.preferences_cache, {'id': id});
},
// Get and cache the preferences
cache_preferences: function (modulesChanged) {
let self = this;
setTimeout(function() {
getApiInstance().get(url_for('preferences.get_all'))
.then(({data: res})=>{
self.preferences_cache = res;
self.preference_version(self.generate_preference_version());
pgBrowser.keyboardNavigation.init();
// Initialize Tree saving/reloading
pgBrowser.browserTreeState.init();
/* Once the cache is loaded after changing the preferences,
* notify the modules of the change
*/
if(modulesChanged) {
if(typeof modulesChanged === 'string'){
pgBrowser.Events.trigger('prefchange:'+modulesChanged);
} else {
_.each(modulesChanged, (val, key)=> {
pgBrowser.Events.trigger('prefchange:'+key);
});
}
}
})
.catch(function(error) {
Notify.pgRespErrorNotify(error);
});
}, 500);
},
triggerPreferencesChange: function(moduleChanged) {
pgBrowser.Events.trigger('prefchange:'+moduleChanged);
},
reflectPreferences: function(module) {
let obj = this;
//browser preference
if(module === 'browser') {
let browserPreferences = obj.get_preferences_for_module('browser');
let buttonList = obj?.panels?.browser?.panel?._buttonList;
buttonList.forEach(btn => {
let key = null;
switch(btn.name) {
case gettext('Query Tool'):
key = shortcutToString(browserPreferences.sub_menu_query_tool,null,true);
obj?.panels?.browser?.panel?.updateButton(gettext('Query Tool'), {key});
break;
case gettext('View Data'):
key = shortcutToString(browserPreferences.sub_menu_view_data,null,true);
obj?.panels?.browser?.panel?.updateButton(gettext('View Data'), {key});
break;
case gettext('Search objects'):
key = shortcutToString(browserPreferences.sub_menu_search_objects,null,true);
obj?.panels?.browser?.panel?.updateButton(gettext('Search objects'), {key});
}
});
}
},
onPreferencesChange: function(module, eventHandler) {
pgBrowser.Events?.on('prefchange:'+module, function(event) {
eventHandler(event);
});
},
});
export {pgBrowser};

View File

@@ -1,44 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import React, {Component} from 'react';
import PropTypes from 'prop-types';
// Allow us to render IFrame using React
// Here we will add the event listener on Iframe load event
export class Iframe extends Component {
static get propTypes() {
return {
id: PropTypes.string.isRequired,
srcURL: PropTypes.string.isRequired,
onLoad: PropTypes.func.isRequired,
};
}
render () {
const iframeStyle = {
border: '0',
display: 'block',
position:'absolute',
opacity:'0',
};
const {id, srcURL, onLoad} = this.props;
return (
<iframe
id={id}
src={srcURL}
onLoad={onLoad}
width={'20'}
height={'20'}
style={iframeStyle}
/>
);
}
}

View File

@@ -1,73 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import {MenuItem as NewMenuItem} from '../../../../static/js/helpers/Menu';
import pgAdmin from 'sources/pgadmin';
// Allow us to
const getMenuName = (item) => {
return item.label;
};
export function menuSearch(param, props) {
param = param.trim();
const setState = props.setState;
let result = [];
const iterItem = (subMenus, path, parentPath) => {
subMenus.forEach((subMenu) =>{
if(subMenu instanceof NewMenuItem) {
if(subMenu.type != 'separator' && subMenu?.label?.toLowerCase().indexOf(param.toLowerCase()) != -1){
let localPath = path;
if(parentPath) {
localPath = `${parentPath} > ${path} `;
}
subMenu.path = localPath;
let selectedNode = pgAdmin.Browser.tree.selected();
if(subMenu.path == 'Object') {
if(selectedNode && selectedNode._metadata.data._type == subMenu.module.parent_type) {
result.push(subMenu);
}
} else {
result.push(subMenu);
}
}
if(subMenu.getMenuItems()) {
iterItem(subMenu.getMenuItems(), getMenuName(subMenu), path);
}
} else {
if(typeof(subMenu) == 'object' && !(subMenu instanceof NewMenuItem)) {
iterItem(Object.values(subMenu), path, parentPath);
} else {
iterItem(subMenu, path, parentPath);
}
}
});
};
const mainMenus = pgAdmin.Browser.MainMenus;
mainMenus.forEach((menu) => {
let subMenus = [];
if(menu.name == 'object') {
let selectedNode = pgAdmin.Browser.tree.selected();
if(selectedNode) {
subMenus = menu.getMenuItems();
}
} else {
subMenus = pgAdmin.Browser.all_menus_cache[menu.name];
}
iterItem(Object.values(subMenus), getMenuName(menu));
});
setState(state => ({
...state,
fetched: true,
data: result,
}));
}

View File

@@ -1,105 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import React from 'react';
import ReactDOM from 'react-dom';
import {Iframe} from './iframe_component';
import url_for from 'sources/url_for';
const extractSearchResult = (list) => {
let result = {};
for (let list_val of list) {
let link = list_val.getElementsByTagName('A');
// we are not going to display more than first 10 result as per design
if (link.length == 0) {
break;
}
let topicName = link[0].text;
let topicLink = url_for('help.static', {
'filename': link[0].getAttribute('href'),
});
result[topicName] = topicLink;
}
return result;
};
export function onlineHelpSearch(param, props) {
param = param.split(' ').join('+');
const setState = props.setState;
const helpURL = url_for('help.static', {
'filename': 'search.html',
});
const srcURL = `${helpURL}?q=${param}`;
let isIFrameLoaded = false;
if(document.getElementById('hidden-quick-search-iframe')){
document.getElementById('hidden-quick-search-iframe').contentDocument.location.reload(true);
}
// Below function will be called when the page will be loaded in Iframe
const _iframeLoaded = () => {
if (isIFrameLoaded) {
return false;
}
isIFrameLoaded = true;
let iframe = document.getElementById('hidden-quick-search-iframe');
let content = (iframe.contentWindow || iframe.contentDocument);
let iframeHTML = content.document;
window.pooling = setInterval(() => {
let resultEl = iframeHTML.getElementById('search-results');
let pooling = window.pooling;
if(resultEl) {
let searchResultsH2Tags = resultEl.getElementsByTagName('h2');
let list = resultEl && resultEl.getElementsByTagName('LI');
if ((list && list.length > 0 )) {
let res = extractSearchResult(list);
// After getting the data, we need to call the Parent component function
// which will render the data on the screen
if (searchResultsH2Tags[0]['childNodes'][0]['textContent'] != 'Searching') {
window.clearInterval(pooling);
setState(state => ({
...state,
fetched: true,
clearedPooling: true,
url: srcURL,
data: res,
}));
isIFrameLoaded = false;
ReactDOM.unmountComponentAtNode(document.getElementById('quick-search-iframe-container'));
} else {
setState(state => ({
...state,
fetched: true,
clearedPooling: false,
url: srcURL,
data: res,
}));
}
} else if(searchResultsH2Tags[0]['childNodes'][0]['textContent'] == 'Search Results') {
setState(state => ({
...state,
fetched: true,
clearedPooling: true,
url: srcURL,
data: {},
}));
ReactDOM.unmountComponentAtNode(document.getElementById('quick-search-iframe-container'));
isIFrameLoaded = false;
window.clearInterval(pooling);
}
} else {
window.clearInterval(pooling);
}
}, 500);
};
// Render IFrame
ReactDOM.render(
<Iframe id='hidden-quick-search-iframe' srcURL={srcURL} onLoad={_iframeLoaded}/>,
document.getElementById('quick-search-iframe-container'),
);
}

View File

@@ -1,279 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import React, {useRef,useState, useEffect} from 'react';
import { makeStyles } from '@material-ui/core';
import clsx from 'clsx';
import {useDelayDebounce} from 'sources/custom_hooks';
import {onlineHelpSearch} from './online_help';
import {menuSearch} from './menuitems_help';
import $ from 'jquery';
import gettext from 'sources/gettext';
import PropTypes from 'prop-types';
function HelpArticleContents({isHelpLoading, isMenuLoading, helpSearchResult}) {
return (isHelpLoading && !(isMenuLoading??true)) ? (
<div>
<div className='help-groups'>
<span className='fa fa-question-circle'></span>
&nbsp;HELP ARTICLES
{Object.keys(helpSearchResult.data).length > 10
? '(10 of ' + Object.keys(helpSearchResult.data).length + ')'
: '(' + Object.keys(helpSearchResult.data).length + ')'
}
{ Object.keys(helpSearchResult.data).length > 10
? <a href={helpSearchResult.url} className='pull-right no-padding' target='_blank' rel='noreferrer'>
Show all &nbsp;<span className='fas fa-external-link-alt' ></span></a> : ''
}
</div>
<div className='pad-12'><div className="search-icon">{gettext('Searching...')}</div></div>
</div>) : '';
}
HelpArticleContents.propTypes = {
helpSearchResult: PropTypes.object,
isHelpLoading: PropTypes.bool,
isMenuLoading: PropTypes.bool
};
const useModalStyles = makeStyles(() => ({
setTop: {
marginTop: '-20px',
}
}));
export function Search({closeModal}) {
const classes = useModalStyles();
const wrapperRef = useRef(null);
const firstEleRef = useRef();
const [searchTerm, setSearchTerm] = useState('');
const [isShowMinLengthMsg, setIsShowMinLengthMsg] = useState(false);
const [isMenuLoading, setIsMenuLoading] = useState(false);
const [isHelpLoading, setIsHelpLoading] = useState(false);
const [menuSearchResult, setMenuSearchResult] = useState({
fetched: false,
data: [],
});
const [helpSearchResult, setHelpSearchResult] = useState({
fetched: false,
clearedPooling: true,
url: '',
data: [],
});
const [showResults, setShowResults] = useState(false);
const resetSearchState = () => {
setMenuSearchResult(state => ({
...state,
fetched: false,
data: [],
}));
setHelpSearchResult(state => ({
...state,
fetched: false,
clearedPooling: true,
url: '',
data: {},
}));
};
// Below will be called when any changes has been made to state
useEffect(() => {
if(menuSearchResult.fetched){
setIsMenuLoading(false);
}
if(helpSearchResult.fetched){
setIsHelpLoading(false);
}
}, [menuSearchResult, helpSearchResult]);
const initSearch = (param) => {
setIsMenuLoading(true);
setIsHelpLoading(true);
onlineHelpSearch(param, {
state: helpSearchResult,
setState: setHelpSearchResult,
});
menuSearch(param, {
state: menuSearchResult,
setState: setMenuSearchResult,
});
};
// Debounse logic to avoid multiple re-render with each keypress
useDelayDebounce(initSearch, searchTerm, 1000);
const toggleDropdownMenu = () => {
let pooling = window.pooling;
if(pooling){
window.clearInterval(pooling);
}
document.getElementsByClassName('live-search-field')[0].value = '';
setTimeout(function(){
document.getElementById('live-search-field').focus();
},100);
resetSearchState();
setShowResults(!showResults);
setIsMenuLoading(false);
setIsHelpLoading(false);
setIsShowMinLengthMsg(false);
};
const refactorMenuItems = (items) => {
if(items.length > 0){
let menuItemsHtmlElement = [];
items.forEach((i) => {
menuItemsHtmlElement.push(
<li key={ 'li-menu-' + i.label }><a tabIndex='0' id={ 'li-menu-' + i.label } href={'#'} className={ (i.isDisabled ? 'dropdown-item menu-groups-a disabled':'dropdown-item menu-groups-a')} onClick={
() => {
closeModal();
i.callback();
}
}>
{i.label}
<span key={ 'menu-span-' + i.label }>{i.path}</span>
</a>
</li>);
});
$('[data-toggle="tooltip"]').tooltip();
return menuItemsHtmlElement;
}
};
const onInputValueChange = (value) => {
let pooling = window.pooling;
if(pooling){
window.clearInterval(pooling);
}
resetSearchState();
setSearchTerm('');
if(value.length >= 3){
setSearchTerm(value);
setIsMenuLoading(true);
setIsHelpLoading(true);
setIsShowMinLengthMsg(false);
}else{
setIsMenuLoading(false);
setIsHelpLoading(false);
}
if(value.length < 3 && value.length > 0){
setIsShowMinLengthMsg(true);
}
if(value.length == 0){
setIsShowMinLengthMsg(false);
}
};
const useOutsideAlerter = (ref) => {
useEffect(() => {
/**
* Alert if clicked on outside of element
*/
function handleClickOutside(event) {
if (ref.current && !ref.current.contains(event.target)) {
let input_element = document.getElementById('live-search-field');
if(input_element == null){
return;
}
let input_value = input_element.value;
if(input_value && input_value.length > 0){
toggleDropdownMenu();
}
}
}
// Bind the event listener
document.addEventListener('mousedown', handleClickOutside);
return () => {
// Unbind the event listener on clean up
document.removeEventListener('mousedown', handleClickOutside);
};
}, [ref]);
};
useOutsideAlerter(wrapperRef);
const showLoader = (loading) => {
return loading ? <div className='pad-12'><div className="search-icon">{gettext('Searching...')}</div></div> : '';
};
useEffect(() => {
setTimeout(() => {
firstEleRef.current && firstEleRef.current.focus();
}, 350);
}, [firstEleRef.current]);
return (
<div id='quick-search-container' onClick={setSearchTerm}></div>,
<ul id='quick-search-container' ref={wrapperRef} className={clsx('test', classes.setTop)} role="menu">
<li>
<ul id='myDropdown'>
<li className='dropdown-item-input'>
<input ref={firstEleRef} tabIndex='0' autoFocus type='text' autoComplete='off' className='form-control live-search-field'
aria-label='live-search-field' id='live-search-field' placeholder={gettext('Quick Search')} onChange={(e) => {onInputValueChange(e.target.value);} } />
</li>
<div style={{marginBottom:0}}>
<div>
{ isShowMinLengthMsg
? (<div className='pad-12 no-results'>
<span className='fa fa-info-circle'></span>
&nbsp;Please enter minimum 3 characters to search
</div>)
:''}
<div >
{ (menuSearchResult.fetched && !(isMenuLoading??true) ) ?
<div>
<div className='menu-groups'>
<span className='fa fa-window-maximize'></span> &nbsp;{gettext('MENU ITEMS')} ({menuSearchResult.data.length})
</div>
{refactorMenuItems(menuSearchResult.data)}
</div> : showLoader(isMenuLoading)}
{(menuSearchResult.data.length == 0 && menuSearchResult.fetched && !(isMenuLoading??true)) ? (<div className='pad-12 no-results'><span className='fa fa-info-circle'></span> {gettext('No search results')}</div>):''}
{ (helpSearchResult.fetched && !(isHelpLoading??true)) ?
<div>
<div className='help-groups'>
<span className='fa fa-question-circle'></span> &nbsp;{gettext('HELP ARTICLES')} {Object.keys(helpSearchResult.data).length > 10 ?
<span>(10 of {Object.keys(helpSearchResult.data).length} )
</span>:
'(' + Object.keys(helpSearchResult.data).length + ')'}&nbsp;
{ !helpSearchResult.clearedPooling ? <img src='/static/img/loading.gif' alt={gettext('Loading...')} className='help_loading_icon'/> :''}
{ Object.keys(helpSearchResult.data).length > 10 ? <a href={helpSearchResult.url} className='pull-right no-padding' target='_blank' rel='noreferrer'>{gettext('Show all')} &nbsp;<span className='fas fa-external-link-alt' ></span></a> : ''}
</div>
{Object.keys(helpSearchResult.data).map( (value, index) => {
if(index <= 9) { return <li key={ 'li-help-' + value }><a tabIndex='0' href={helpSearchResult.data[value]} className='dropdown-item' target='_blank' rel='noreferrer'>{value}</a></li>; }
})}
{(Object.keys(helpSearchResult.data).length == 0) ? (<div className='pad-12 no-results'><span className='fa fa-info-circle'></span> {gettext('No search results')}</div>):''}
</div> : <HelpArticleContents isHelpLoading={isHelpLoading} isMenuLoading={isMenuLoading} helpSearchResult={helpSearchResult} /> }
</div>
</div>
</div>
</ul>
</li>
<div id='quick-search-iframe-container' />
</ul>
);
}
Search.propTypes = {
closeModal: PropTypes.func
};

View File

@@ -1,140 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import gettext from 'sources/gettext';
import _ from 'lodash';
import pgAdmin from 'sources/pgadmin';
let _toolbarButtons = {};
let _browserPanel = null;
// Default Tool Bar Buttons.
let _defaultToolBarButtons = [
{
label: gettext('Search objects'),
ariaLabel: gettext('Search objects'),
btnClass: 'fa fa-search',
text: '',
toggled: false,
toggleClass: '',
parentClass: 'pg-toolbar-btn btn-primary-icon',
enabled: false,
},
{
label: gettext('Filtered Rows'),
ariaLabel: gettext('Filtered Rows'),
btnClass: 'pg-font-icon icon-row_filter',
text: '',
toggled: false,
toggleClass: '',
parentClass: 'pg-toolbar-btn btn-primary-icon',
enabled: false,
},
{
label: gettext('View Data'),
ariaLabel: gettext('View Data'),
btnClass: 'pg-font-icon sql-icon-lg icon-view_data',
text: '',
toggled: false,
toggleClass: '',
parentClass: 'pg-toolbar-btn btn-primary-icon',
enabled: false,
},
{
label: gettext('Query Tool'),
ariaLabel: gettext('Query Tool'),
btnClass: 'pg-font-icon icon-query_tool',
text: '',
toggled: false,
toggleClass: '',
parentClass: 'pg-toolbar-btn btn-primary-icon',
enabled: false,
}
];
if(pgAdmin['enable_psql']) {
_defaultToolBarButtons.unshift({
label: gettext('PSQL Tool'),
ariaLabel: gettext('PSQL Tool'),
btnClass: 'fas fa-terminal',
text: '',
toggled: false,
toggleClass: '',
parentClass: 'pg-toolbar-btn btn-primary-icon pg-toolbar-psql',
enabled: false,
});
}
// Place holder for non default tool bar buttons.
let _otherToolbarButtons = [];
// This function is used to add button into the browser panel.
function registerToolBarButton(btn) {
/* Sometimes the panel onCreate is called two times.
* Add buttons if not present in the panel also.
*/
if (!(btn.label in _toolbarButtons)
|| (_.findIndex(_browserPanel._buttonList,{name:btn.label}) < 0)) {
_browserPanel.addButton(
btn.label, btn.btnClass, btn.text, btn.label, btn.toggled,
btn.toggleClass, btn.parentClass, btn.enabled, btn.ariaLabel
);
_toolbarButtons[btn.label] = btn;
}
}
// This function is used to add tool bar button and
// listen on the button event.
export function initializeToolbar(panel, wcDocker) {
_browserPanel = panel;
// Iterate through default tool bar buttons and add them into the
// browser panel.
_.each(_defaultToolBarButtons, (btn) => {
registerToolBarButton(btn);
});
// Iterate through other tool bar buttons and add them into the
// browser panel.
_.each(_otherToolbarButtons, (btn) => {
registerToolBarButton(btn);
});
// Listen on button click event.
panel.on(wcDocker.EVENT.BUTTON, function(data) {
if ('name' in data && data.name === gettext('Query Tool'))
pgAdmin.Tools.SQLEditor.showQueryTool('', pgAdmin.Browser.tree.selected());
else if ('name' in data && data.name === gettext('View Data'))
pgAdmin.Tools.SQLEditor.showViewData({mnuid: 3}, pgAdmin.Browser.tree.selected());
else if ('name' in data && data.name === gettext('Filtered Rows'))
pgAdmin.Tools.SQLEditor.showFilteredRow({mnuid: 4}, pgAdmin.Browser.tree.selected());
else if ('name' in data && data.name === gettext('Search objects'))
pgAdmin.Tools.SearchObjects.show_search_objects('', pgAdmin.Browser.tree.selected());
else if ('name' in data && data.name === gettext('PSQL Tool')){
let input = {},
t = pgAdmin.Browser.tree,
i = input.item || t.selected(),
d = i ? t.itemData(i) : undefined;
pgAdmin.Browser.psql.psql_tool(d, i, true);
}
});
}
// This function is used to enable/disable the specific button
// based on their label.
export function enable(label, enable_btn) {
if (label in _toolbarButtons) {
_browserPanel.buttonEnable(label, enable_btn);
} else {
console.warn('Developer warning: No tool button found with label: ' + label);
}
}

View File

@@ -1,163 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import React from 'react';
import ReactDOM from 'react-dom';
import pgAdmin from 'sources/pgadmin';
import getApiInstance from 'sources/api_instance';
import {getHelpUrl, getEPASHelpUrl} from 'pgadmin.help';
import SchemaView from 'sources/SchemaView';
import 'wcdocker';
import Theme from '../../../static/js/Theme';
import url_for from 'sources/url_for';
import { generateNodeUrl } from './node_ajax';
/* The entry point for rendering React based view in properties, called in node.js */
export function getUtilityView(schema, treeNodeInfo, actionType, formType, container, containerPanel,
onSave, extraData, saveBtnName, urlBase, sqlHelpUrl, helpUrl, isTabView=true) {
let serverInfo = treeNodeInfo && ('server' in treeNodeInfo) &&
pgAdmin.Browser.serverInfo && pgAdmin.Browser.serverInfo[treeNodeInfo.server._id];
let inCatalog = treeNodeInfo && ('catalog' in treeNodeInfo);
const api = getApiInstance();
const url = ()=>{
return urlBase;
};
const confirmOnReset = pgAdmin.Browser.get_preferences_for_module('browser').confirm_on_properties_close;
/* button icons */
const saveBtnIcon = extraData.save_btn_icon;
/* Node type & Noen obj*/
let nodeObj = extraData.nodeType? pgAdmin.Browser.Nodes[extraData.nodeType]: undefined;
let itemNodeData = extraData?.itemNodeData ? itemNodeData: undefined;
/* on save button callback, promise required */
const onSaveClick = (isNew, data)=>new Promise((resolve, reject)=>{
return api({
url: url(),
method: isNew ? 'POST' : 'PUT',
data: Object.assign({}, data, extraData),
}).then((res)=>{
/* Don't warn the user before closing dialog */
resolve(res.data);
onSave && onSave(res.data, containerPanel);
containerPanel.close();
}).catch((err)=>{
reject(err);
});
});
/* Called when switched to SQL tab, promise required */
const getSQLValue = (isNew, changedData)=>{
const msqlUrl = extraData?.msqlurl ? extraData.msqlurl: generateNodeUrl.call(nodeObj, treeNodeInfo, 'msql', itemNodeData, !isNew, nodeObj.url_jump_after_node);
return new Promise((resolve, reject)=>{
api({
url: msqlUrl,
method: 'GET',
params: changedData,
}).then((res)=>{
resolve(res.data.data);
}).catch((err)=>{
onError(err);
reject(err);
});
});
};
/* Callback for help button */
const onHelp = (isSqlHelp=false)=>{
if(isSqlHelp) {
let server = treeNodeInfo.server;
let help_url = pgAdmin.Browser.utils.pg_help_path;
let fullUrl = '';
if (server.server_type == 'ppas') {
fullUrl = getEPASHelpUrl(server.version);
} else {
fullUrl = getHelpUrl(help_url, sqlHelpUrl, server.version);
}
window.open(fullUrl, 'postgres_help');
} else {
window.open(helpUrl, 'pgadmin_help');
}
};
/* All other useful details can go with this object */
const viewHelperProps = {
mode: actionType,
serverInfo: serverInfo ? {
type: serverInfo.server_type,
version: serverInfo.version,
}: undefined,
inCatalog: inCatalog,
};
let initData = ()=>new Promise((resolve, reject)=>{
if(actionType === 'create') {
resolve({});
}else{
api.get(url_for('import_export.get_settings'))
.then((res)=>{
resolve(res.data.data);
})
.catch((err)=>{
if(err.response){
console.error('error resp', err.response);
} else if(err.request){
console.error('error req', err.request);
} else if(err.message){
console.error('error msg', err.message);
}
reject(err);
});
}
});
let onError = (err)=> {
if(err.response){
console.error('error resp', err.response);
} else if(err.request){
console.error('error req', err.request);
} else if(err.message){
console.error('error msg', err.message);
}
};
let _schema = schema;
/* Fire at will, mount the DOM */
ReactDOM.render(
<Theme>
<SchemaView
formType={formType}
getInitData={initData}
schema={_schema}
viewHelperProps={viewHelperProps}
customSaveBtnName={saveBtnName}
customSaveBtnIconType={saveBtnIcon}
onSave={onSaveClick}
onClose={()=>containerPanel.close()}
onHelp={onHelp}
onDataChange={()=>{/*This is intentional (SonarQube)*/}}
confirmOnCloseReset={confirmOnReset}
hasSQL={nodeObj?nodeObj.hasSQL:false && (actionType === 'create' || actionType === 'edit')}
getSQLValue={getSQLValue}
isTabView={isTabView}
disableSqlHelp={sqlHelpUrl == undefined || sqlHelpUrl == ''}
disableDialogHelp={helpUrl == undefined || helpUrl == ''}
/>
</Theme>, container);
}
/* When switching from normal node to collection node, clean up the React mounted DOM */
export function removeNodeView(container) {
ReactDOM.unmountComponentAtNode(container);
}

View File

@@ -1,150 +0,0 @@
#myInput {
box-sizing: border-box;
background-position: 14px 12px;
background-repeat: no-repeat;
font-size: 16px;
padding: 14px 20px 12px 45px;
border: none;
border-bottom: 1px solid #ddd;
}
#myInput:focus {outline: 3px solid #ddd;}
.custom-dropdown {
position: relative;
display: inline-block;
}
.custom-dropdown-content {
background-color: $color-bg;
min-width: 376px;
overflow: auto;
z-index: 1;
}
.custom-dropdown-content a {
color: $dropdown-link-color;
padding: 6px 12px 6px 16px;
text-decoration: none;
display: block;
cursor:pointer;
}
.custom-dropdown-content a:hover {
color: $black;
}
#myDropdown a:hover {background-color: $dropdown-link-hover-bg; color:$color-danger-fg !important;}
.search_icon{
color: $white;
cursor: pointer;
padding-right: 8px;
}
.hidden { display:none; }
.visible { display:block; }
.menu-groups, .help-groups{
background-color: $color-gray-light;
padding: 6px;
font-size: 12px;
font-weight: 600;
}
.menu-groups .fa, .fas{
font-weight:normal !important;
}
.help-groups .fa, .fas{
font-weight:600 !important;
}
.pad-12{
padding:12px;
}
.no-results{
font-size: 14px;
color: #697986;
text-align: center;
}
.no-padding{
padding:0 !important;
}
.menu-groups-a{
display:flex !important;
flex-direction:column;
padding: 6px 16px;
color: $dropdown-link-color;
}
.menu-groups-a span{
font-size: 0.9em;
font-weight: 100;
color: $quick-search-span-text;
}
#myDropdown a:hover span{
color: $color-danger-fg !important;
}
.search-icon{
background: $loader-icon-small center center no-repeat;
margin: auto !important;
height: 22px !important;
width: 130px !important;
background-position: left !important;
font-size: 14px;
color: $dropdown-link-color;
padding-left: 30px;
}
.help_loading_icon{
height: 16px;
}
#myDropdown ul {
list-style: none;
}
.border-right-search-icon{
border-right: 2px solid #fff;
}
.help_submenu{
left: 100%;
width: 20rem;
top:-0.4rem;
}
.help_menu{
min-width:300px;
}
.dropdown-item-input{
padding:4px;
}
.menu-groups-a span:focus{
color:$color-primary-fg;
}
.dropdown-item:hover, .dropdown-item:focus span{
color: $color-danger-fg !important;
}
.quick-search-tooltip{
position: absolute;
right: 10px;
margin-top: -2.1em;
font-size: 16px;
cursor:pointer;
color: $quick-search-info-icon;
}
#myDropdown .dropdown-divider{
height: auto;
border-top: 0;
}