pgadmin4/web/pgadmin/browser/static/js/browser.js

1969 lines
64 KiB
JavaScript
Raw Normal View History

2019-01-02 04:24:12 -06:00
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2019, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
define('pgadmin.browser', [
'sources/tree/tree',
'sources/gettext', 'sources/url_for', 'require', 'jquery', 'underscore', 'underscore.string',
'bootstrap', 'sources/pgadmin', 'pgadmin.alertifyjs', 'bundled_codemirror',
'sources/check_node_visibility', './toolbar', 'pgadmin.help', 'pgadmin.browser.utils',
'wcdocker', 'jquery.contextmenu', 'jquery.aciplugin', 'jquery.acitree',
'pgadmin.browser.preferences', 'pgadmin.browser.messages',
'pgadmin.browser.menu', 'pgadmin.browser.panel',
'pgadmin.browser.error', 'pgadmin.browser.frame',
'pgadmin.browser.node', 'pgadmin.browser.collection',
'sources/codemirror/addon/fold/pgadmin-sqlfoldcode',
'pgadmin.browser.keyboard', 'sources/tree/pgadmin_tree_save_state',
], function(
tree,
gettext, url_for, require, $, _, S,
Bootstrap, pgAdmin, Alertify, codemirror,
checkNodeVisibility, toolBar, help
) {
window.jQuery = window.$ = $;
// Some scripts do export their object in the window only.
// Generally the one, which do no have AMD support.
var wcDocker = window.wcDocker;
$ = $ || window.jQuery || window.$;
Bootstrap = Bootstrap || window.Bootstrap;
var CodeMirror = codemirror.default;
var pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
var select_object_msg = gettext('Please select an object in the tree view.');
var panelEvents = {};
2015-07-20 05:36:17 -05:00
panelEvents[wcDocker.EVENT.VISIBILITY_CHANGED] = function() {
if (this.isVisible()) {
var obj = pgAdmin.Browser,
i = obj.tree ? obj.tree.selected() : undefined,
d = i && i.length == 1 ? 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]);
}
}
};
var processTreeData = function(payload) {
var data = JSON.parse(payload).data;
if (data.length && data[0]._type !== 'column' &&
data[0]._type !== 'catalog_object_column') {
data = data.sort(function(a, b) {
return pgAdmin.natural_sort(a.label, b.label);
});
}
_.each(data, function(d){
d._label = d.label;
d.label = _.escape(d.label);
});
return data;
};
var initializeBrowserTree = pgAdmin.Browser.initializeBrowserTree =
function(b) {
$('#tree').aciTree({
ajax: {
url: url_for('browser.nodes'),
converters: {
'text json': processTreeData,
},
},
ajaxHook: function(item, settings) {
if (item != null) {
var d = this.itemData(item);
var n = b.Nodes[d._type];
if (n)
settings.url = n.generate_url(item, 'children', d, true);
}
},
loaderDelay: 100,
show: {
duration: 75,
},
hide: {
duration: 75,
},
view: {
duration: 75,
},
animateRoot: true,
unanimated: false,
});
b.tree = $('#tree').aciTree('api');
b.treeMenu.register($('#tree'));
};
// 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
editor:null,
// Left hand browser tree
tree:null,
treeMenu: new tree.Tree(),
// list of script to be loaded, when a certain type of node is loaded
// It will be used to register extensions, tools, child node scripts,
// etc.
scripts: {},
Fixed following: - Base font size changed from 0.815rem to 0.875rem, for navbar from 0.875rem to 0.925rem. - Dialog sizes made consistent throughout the application. Now there are 3 size options for width and height each - sm, md, lg. Combination of any of these to be used hereafter - Alignment fix for controls of Node properties dialogs which includes showing text and label in one line without dialog size change, checkbox alignment, switch control alignment at places and other minor improvements in other dialogs - Error message design change in dialogs validation - SQL Editor data grid editor popup design changes which were missed - Design change for dashboard server activity grid - Login page language dropdown color fix - Properties accordion collapse design fix - Help, Info icon fixed across all dialogs which were not working if clicked exactly on the text - Added missing icon with buttons at few places - Shadow behind the dialogs is increased to make it look clearly separated and depth. - Control Alignment fix in maintenance dialog - Min height of alertify dialogs set for better UX - File dialog design fix when no files found - Grant wizard fixes - Scroll bar visibility on first page, use full space for SQL generated on the last page - Browser toolbar buttons changed to sync with SQL editor toolbar buttons - Rounded corners for docker floating dialog (no properties) - Renaming file in file dialog should show original file name - SQL data grid text edit popup buttons behaviour was swapped. This is fixed. - Import/Export dialog changes as per new design.
2019-01-02 03:35:15 -06:00
// Standard Widths and Height for dialogs in px
stdW: {
sm: 500,
md: 700,
lg: 900,
default: 500,
},
stdH: {
sm: 200,
md: 400,
lg: 550,
default: 550,
},
// Default panels
panels: {
// Panel to keep the left hand browser tree
'browser': new pgAdmin.Browser.Panel({
name: 'browser',
title: gettext('Browser'),
showTitle: true,
isCloseable: false,
isPrivate: true,
icon: '',
content: '<div id="tree" class="aciTree"></div>',
onCreate: function(panel) {
toolBar.initializeToolbar(panel, wcDocker);
},
}),
// Properties of the object node
'properties': new pgAdmin.Browser.Panel({
name: 'properties',
title: gettext('Properties'),
icon: '',
width: 500,
isCloseable: false,
isPrivate: true,
2016-05-25 05:17:56 -05:00
elContainer: true,
content: '<div class="obj_properties container-fluid"><div class="alert alert-info pg-panel-message">' + select_object_msg + '</div></div>',
events: panelEvents,
onCreate: function(myPanel, $container) {
$container.addClass('pg-no-overflow');
},
}),
// Statistics of the object
'statistics': new pgAdmin.Browser.Panel({
name: 'statistics',
title: gettext('Statistics'),
icon: '',
width: 500,
isCloseable: false,
isPrivate: true,
content: '<div class="negative-space p-2"><div class="alert alert-info pg-panel-message pg-panel-statistics-message">' + select_object_msg + '</div><div class="pg-panel-statistics-container d-none"></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,
content: '<div class="sql_textarea"><textarea id="sql-textarea" name="sql-textarea"></textarea></div>',
}),
// Dependencies of the object
'dependencies': new pgAdmin.Browser.Panel({
name: 'dependencies',
title: gettext('Dependencies'),
icon: '',
width: 500,
isCloseable: false,
isPrivate: true,
content: '<div class="negative-space p-2"><div class="alert alert-info pg-panel-message pg-panel-depends-message">' + select_object_msg + '</div><div class="pg-panel-dependencies-container d-none"></div></div>',
events: panelEvents,
}),
// Dependents of the object
'dependents': new pgAdmin.Browser.Panel({
name: 'dependents',
title: gettext('Dependents'),
icon: '',
width: 500,
isCloseable: false,
isPrivate: true,
content: '<div class="negative-space p-2"><div class="alert alert-info pg-panel-message pg-panel-depends-message">' + select_object_msg + '</div><div class="pg-panel-dependents-container d-none"></div></div>',
events: panelEvents,
}),
},
// We also support showing dashboards, HTML file, external URL
frames: {},
/* Menus */
// pgAdmin.Browser.MenuItem.add_menus(...) will register all the menus
// in this container
menus: {
// All context menu goes here under certain menu types.
// i.e. context: {'server': [...], 'server-group': [...]}
context: {},
// File menus
file: {},
// Edit menus
edit: {},
// Object menus
object: {},
// Management menus
management: {},
// Tools menus
tools: {},
// Help menus
help: {},
},
add_panels: function() {
/* Add hooked-in panels by extensions */
var 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,
content: (panel.content) ? panel.content : '',
events: (panel.events) ? panel.events : '',
canHide: (panel.canHide) ? panel.canHide : '',
});
}
});
},
menu_categories: {
/* name, label (pair) */
'create': {
label: gettext('Create'),
priority: 1,
/* separator above this menu */
above: false,
below: true,
/* icon: 'fa fa-magic', */
single: true,
},
},
// A callback to load/fetch a script when a certain node is loaded
register_script: function(n, m, p) {
var scripts = this.scripts;
scripts[n] = _.isArray(scripts[n]) ? scripts[n] : [];
scripts[n].push({'name': m, 'path': p, loaded: false});
},
// Build the default layout
buildDefaultLayout: function() {
2015-07-20 05:36:17 -05:00
var browserPanel = this.docker.addPanel('browser', wcDocker.DOCK.LEFT);
var dashboardPanel = this.docker.addPanel(
'dashboard', wcDocker.DOCK.RIGHT, browserPanel);
2015-07-20 05:36:17 -05:00
this.docker.addPanel('properties', wcDocker.DOCK.STACKED, dashboardPanel, {
tabOrientation: wcDocker.TAB.TOP,
2015-07-20 05:36:17 -05:00
});
this.docker.addPanel('sql', wcDocker.DOCK.STACKED, dashboardPanel);
this.docker.addPanel(
'statistics', wcDocker.DOCK.STACKED, dashboardPanel);
2015-07-20 05:36:17 -05:00
this.docker.addPanel(
'dependencies', wcDocker.DOCK.STACKED, dashboardPanel);
2015-07-20 05:36:17 -05:00
this.docker.addPanel(
'dependents', wcDocker.DOCK.STACKED, dashboardPanel);
},
// Enable/disable menu options
enable_disable_menus: function(item) {
// Mechanism to enable/disable menus depending on the condition.
var obj = this,
// menu navigation bar
navbar = $('#navbar-menu > ul').first(),
// Drop down menu for objects
$obj_mnu = navbar.find('li#mnu_obj .dropdown-menu').first(),
// data for current selected object
d = obj.tree.itemData(item),
update_menuitem = function(m) {
if (m instanceof pgAdmin.Browser.MenuItem) {
m.update(d, item);
} else {
for (var key in m) {
update_menuitem(m[key]);
}
}
};
toolBar.enable(gettext('View Data'), false);
toolBar.enable(gettext('Filtered Rows'), false);
// All menus from the object menus (except the create drop-down
// menu) needs to be removed.
$obj_mnu.empty();
// All menus (except for the object menus) are already present.
// They will just require to check, wheather they are
// enabled/disabled.
_.each([
{m: 'file', id: '#mnu_file'},
{m: 'edit', id: '#mnu_edit'},
{m: 'management', id: '#mnu_management'},
{m: 'tools', id: '#mnu_tools'},
{m: 'help', id:'#mnu_help'}], function(o) {
_.each( obj.menus[o.m], function(m) { update_menuitem(m); });
});
// Create the object menu dynamically
if (item && obj.menus['object'] && obj.menus['object'][d._type]) {
pgAdmin.Browser.MenuCreator(
$obj_mnu, obj.menus['object'][d._type], obj.menu_categories, d, item
);
} else {
// Create a dummy 'no object seleted' menu
var create_submenu = pgAdmin.Browser.MenuGroup(
obj.menu_categories['create'], [{
$el: $('<li><a class="dropdown-item disabled" href="#">' + gettext('No object selected') + '</a></li>'),
priority: 1,
category: 'create',
update: function() {},
}], false);
$obj_mnu.append(create_submenu.$el);
}
},
save_current_layout: function(obj) {
if(obj.docker) {
var state = obj.docker.save();
var settings = { setting: 'Browser/Layout', value: state };
$.ajax({
type: 'POST',
url: url_for('settings.store_bulk'),
data: settings,
});
}
},
init: function() {
var obj=this;
Introduced a event manager for the browser, which will generate certain known events, when any activity happens on the browser layout. The following types of events will be triggered through the browser event manager. - pgadmin-browser:frame:* [1] - pgadmin-browser:frame-<name>:* [1] - pgadmin-browser:panel:* [1] - pgadmin-browser:panel-<name>:* [1] - pgadmin-browser:panel - pgadmin-browser:frame - pgadmin-browser:tree - pgadmin-browser:tree:* [2] [1] The '*' denotes some of the events generated by the wcDocker, which can be useful to do some operations. These events are: + 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 [2] The '*' denotes all the events generated by the Browser Tree (aciTree). The extension modules can utilize these events to do some operations on nodes, and panels. This patch includes showing 'Reversed Engineered Query' for the selected node (if allowed) using the above approch. The ShowNodeSQL module looks for two events. 1. SQL Panel Visibility change. - Based on the visibility of that panel, we start/stop listening the node selection event. 2. Node Selection in Browser tree - Based on the selected node type, it will look for 'hasSQL' parameter of the node, and fetch the Query from the server, and show it in the SQL editor.
2016-01-19 06:31:14 -06:00
if (obj.initialized) {
return;
}
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
var layout = pgBrowser.utils.layout;
// Try to restore the layout if there is one
if (layout != '') {
try {
obj.docker.restore(layout);
}
catch(err) {
2015-07-20 05:36:17 -05:00
obj.docker.clear();
obj.buildDefaultLayout();
}
} else {
obj.buildDefaultLayout();
}
// Listen to panel attach/detach event so that last layout will be remembered
_.each(obj.panels, function(panel) {
if (panel.panel) {
panel.panel.on(wcDocker.EVENT.ATTACHED, function() {
obj.save_current_layout(obj);
});
panel.panel.on(wcDocker.EVENT.DETACHED, function() {
obj.save_current_layout(obj);
});
panel.panel.on(wcDocker.EVENT.MOVE_ENDED, function() {
obj.save_current_layout(obj);
});
}
});
}
// Syntax highlight the SQL Pane
obj.editor = CodeMirror.fromTextArea(
document.getElementById('sql-textarea'), {
lineNumbers: true,
mode: 'text/x-pgsql',
readOnly: true,
extraKeys: pgAdmin.Browser.editor_shortcut_keys,
});
/* 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');
if(sqlEditPreferences) {
clearInterval(cacheIntervalId);
obj.reflectPreferences('sqleditor');
}
}, 500);
/* Check for sql editor preference changes */
obj.onPreferencesChange('sqleditor', function() {
obj.reflectPreferences('sqleditor');
});
setTimeout(function() {
obj.editor.setValue('-- ' + select_object_msg);
obj.editor.refresh();
}, 10);
// Initialise the treeview
initializeBrowserTree(obj);
// Build the treeview context menu
$('#tree').contextMenu({
selector: '.aciTreeLine',
autoHide: false,
build: function(element) {
var item = obj.tree.itemFrom(element),
d = obj.tree.itemData(item),
menus = obj.menus['context'][d._type],
$div = $('<div></div>'),
context_menu = {};
pgAdmin.Browser.MenuCreator(
$div, menus, obj.menu_categories, d, item, context_menu
);
return {
autoHide: false,
items: context_menu,
};
},
events: {
hide: function() {
// Return focus to the tree
obj.keyboardNavigation.bindLeftTree();
},
},
});
// Treeview event handler
$('#tree').on('acitree', function(event, api, item, eventName, options) {
var d = item ? obj.tree.itemData(item) : null;
Introduced a event manager for the browser, which will generate certain known events, when any activity happens on the browser layout. The following types of events will be triggered through the browser event manager. - pgadmin-browser:frame:* [1] - pgadmin-browser:frame-<name>:* [1] - pgadmin-browser:panel:* [1] - pgadmin-browser:panel-<name>:* [1] - pgadmin-browser:panel - pgadmin-browser:frame - pgadmin-browser:tree - pgadmin-browser:tree:* [2] [1] The '*' denotes some of the events generated by the wcDocker, which can be useful to do some operations. These events are: + 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 [2] The '*' denotes all the events generated by the Browser Tree (aciTree). The extension modules can utilize these events to do some operations on nodes, and panels. This patch includes showing 'Reversed Engineered Query' for the selected node (if allowed) using the above approch. The ShowNodeSQL module looks for two events. 1. SQL Panel Visibility change. - Based on the visibility of that panel, we start/stop listening the node selection event. 2. Node Selection in Browser tree - Based on the selected node type, it will look for 'hasSQL' parameter of the node, and fetch the Query from the server, and show it in the SQL editor.
2016-01-19 06:31:14 -06:00
var node;
if (d && obj.Nodes[d._type]) {
Introduced a event manager for the browser, which will generate certain known events, when any activity happens on the browser layout. The following types of events will be triggered through the browser event manager. - pgadmin-browser:frame:* [1] - pgadmin-browser:frame-<name>:* [1] - pgadmin-browser:panel:* [1] - pgadmin-browser:panel-<name>:* [1] - pgadmin-browser:panel - pgadmin-browser:frame - pgadmin-browser:tree - pgadmin-browser:tree:* [2] [1] The '*' denotes some of the events generated by the wcDocker, which can be useful to do some operations. These events are: + 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 [2] The '*' denotes all the events generated by the Browser Tree (aciTree). The extension modules can utilize these events to do some operations on nodes, and panels. This patch includes showing 'Reversed Engineered Query' for the selected node (if allowed) using the above approch. The ShowNodeSQL module looks for two events. 1. SQL Panel Visibility change. - Based on the visibility of that panel, we start/stop listening the node selection event. 2. Node Selection in Browser tree - Based on the selected node type, it will look for 'hasSQL' parameter of the node, and fetch the Query from the server, and show it in the SQL editor.
2016-01-19 06:31:14 -06:00
node = obj.Nodes[d._type];
/* If the node specific callback returns false, we will also return
* false for further processing.
*/
if (_.isObject(node.callbacks) &&
eventName in node.callbacks &&
typeof node.callbacks[eventName] == 'function' &&
!node.callbacks[eventName].apply(
node, [item, d, obj, options, eventName])) {
return false;
}
/* Raise tree events for the nodes */
try {
node.trigger(
'browser-node.' + eventName, node, item, d
);
} catch (e) {
console.warn(e.stack || e);
}
}
Introduced a event manager for the browser, which will generate certain known events, when any activity happens on the browser layout. The following types of events will be triggered through the browser event manager. - pgadmin-browser:frame:* [1] - pgadmin-browser:frame-<name>:* [1] - pgadmin-browser:panel:* [1] - pgadmin-browser:panel-<name>:* [1] - pgadmin-browser:panel - pgadmin-browser:frame - pgadmin-browser:tree - pgadmin-browser:tree:* [2] [1] The '*' denotes some of the events generated by the wcDocker, which can be useful to do some operations. These events are: + 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 [2] The '*' denotes all the events generated by the Browser Tree (aciTree). The extension modules can utilize these events to do some operations on nodes, and panels. This patch includes showing 'Reversed Engineered Query' for the selected node (if allowed) using the above approch. The ShowNodeSQL module looks for two events. 1. SQL Panel Visibility change. - Based on the visibility of that panel, we start/stop listening the node selection event. 2. Node Selection in Browser tree - Based on the selected node type, it will look for 'hasSQL' parameter of the node, and fetch the Query from the server, and show it in the SQL editor.
2016-01-19 06:31:14 -06:00
try {
obj.Events.trigger(
'pgadmin-browser:tree', eventName, item, d
);
Introduced a event manager for the browser, which will generate certain known events, when any activity happens on the browser layout. The following types of events will be triggered through the browser event manager. - pgadmin-browser:frame:* [1] - pgadmin-browser:frame-<name>:* [1] - pgadmin-browser:panel:* [1] - pgadmin-browser:panel-<name>:* [1] - pgadmin-browser:panel - pgadmin-browser:frame - pgadmin-browser:tree - pgadmin-browser:tree:* [2] [1] The '*' denotes some of the events generated by the wcDocker, which can be useful to do some operations. These events are: + 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 [2] The '*' denotes all the events generated by the Browser Tree (aciTree). The extension modules can utilize these events to do some operations on nodes, and panels. This patch includes showing 'Reversed Engineered Query' for the selected node (if allowed) using the above approch. The ShowNodeSQL module looks for two events. 1. SQL Panel Visibility change. - Based on the visibility of that panel, we start/stop listening the node selection event. 2. Node Selection in Browser tree - Based on the selected node type, it will look for 'hasSQL' parameter of the node, and fetch the Query from the server, and show it in the SQL editor.
2016-01-19 06:31:14 -06:00
obj.Events.trigger(
'pgadmin-browser:tree:' + eventName, item, d, node
);
Introduced a event manager for the browser, which will generate certain known events, when any activity happens on the browser layout. The following types of events will be triggered through the browser event manager. - pgadmin-browser:frame:* [1] - pgadmin-browser:frame-<name>:* [1] - pgadmin-browser:panel:* [1] - pgadmin-browser:panel-<name>:* [1] - pgadmin-browser:panel - pgadmin-browser:frame - pgadmin-browser:tree - pgadmin-browser:tree:* [2] [1] The '*' denotes some of the events generated by the wcDocker, which can be useful to do some operations. These events are: + 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 [2] The '*' denotes all the events generated by the Browser Tree (aciTree). The extension modules can utilize these events to do some operations on nodes, and panels. This patch includes showing 'Reversed Engineered Query' for the selected node (if allowed) using the above approch. The ShowNodeSQL module looks for two events. 1. SQL Panel Visibility change. - Based on the visibility of that panel, we start/stop listening the node selection event. 2. Node Selection in Browser tree - Based on the selected node type, it will look for 'hasSQL' parameter of the node, and fetch the Query from the server, and show it in the SQL editor.
2016-01-19 06:31:14 -06:00
} catch (e) {
console.warn(e.stack || e);
Introduced a event manager for the browser, which will generate certain known events, when any activity happens on the browser layout. The following types of events will be triggered through the browser event manager. - pgadmin-browser:frame:* [1] - pgadmin-browser:frame-<name>:* [1] - pgadmin-browser:panel:* [1] - pgadmin-browser:panel-<name>:* [1] - pgadmin-browser:panel - pgadmin-browser:frame - pgadmin-browser:tree - pgadmin-browser:tree:* [2] [1] The '*' denotes some of the events generated by the wcDocker, which can be useful to do some operations. These events are: + 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 [2] The '*' denotes all the events generated by the Browser Tree (aciTree). The extension modules can utilize these events to do some operations on nodes, and panels. This patch includes showing 'Reversed Engineered Query' for the selected node (if allowed) using the above approch. The ShowNodeSQL module looks for two events. 1. SQL Panel Visibility change. - Based on the visibility of that panel, we start/stop listening the node selection event. 2. Node Selection in Browser tree - Based on the selected node type, it will look for 'hasSQL' parameter of the node, and fetch the Query from the server, and show it in the SQL editor.
2016-01-19 06:31:14 -06:00
}
return true;
});
// Register scripts and add menus
pgBrowser.utils.registerScripts(this);
pgBrowser.utils.addMenus(obj);
// Ping the server every 5 minutes
setInterval(function() {
$.ajax({
url: url_for('misc.cleanup'),
type:'POST',
})
.done(function() {})
.fail(function() {});
}, 300000);
obj.Events.on('pgadmin:browser:tree:add', obj.onAddTreeNode, obj);
obj.Events.on('pgadmin:browser:tree:update', obj.onUpdateTreeNode, obj);
obj.Events.on('pgadmin:browser:tree:refresh', obj.onRefreshTreeNode, obj);
},
add_menu_category: function(
id, label, priority, icon, above_separator, below_separator, single
) {
this.menu_categories[id] = {
label: label,
priority: priority,
icon: icon,
above: (above_separator === true),
below: (below_separator === true),
single: single,
};
},
// Add menus of module/extension at appropriate menu
add_menus: function(menus) {
var self = this,
pgMenu = this.menus,
MenuItem = pgAdmin.Browser.MenuItem;
_.each(menus, function(m) {
_.each(m.applies, function(a) {
/* We do support menu type only from this list */
if ($.inArray(a, [
'context', 'file', 'edit', 'object',
'management', 'tools', 'help']) >= 0) {
var menus;
// If current node is not visible in browser tree
// then return from here
if(!checkNodeVisibility(self, 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)) {
return;
}
}
pgMenu[a] = pgMenu[a] || {};
if (_.isString(m.node)) {
menus = pgMenu[a][m.node] = pgMenu[a][m.node] || {};
} else if (_.isString(m.category)) {
menus = pgMenu[a][m.category] = pgMenu[a][m.category] || {};
}
else {
menus = pgMenu[a];
}
2015-02-25 11:06:00 -06:00
if (!_.has(menus, m.name)) {
menus[m.name] = new MenuItem({
name: m.name, label: m.label, module: m.module,
category: m.category, callback: m.callback,
priority: m.priority, data: m.data, url: m.url || '#',
target: m.target, icon: m.icon,
enable: (m.enable == '' ? true : (_.isString(m.enable) &&
m.enable.toLowerCase() == 'false') ?
false : m.enable),
node: m.node,
});
}
} else {
console.warn(
'Developer warning: Category \'' +
a +
'\' is not supported!\nSupported categories are: context, file, edit, object, tools, management, help'
);
}
});
});
},
// Create the menus
create_menus: function() {
/* Create menus */
var navbar = $('#navbar-menu > ul').first();
var obj = this;
_.each([
{menu: 'file', id: '#mnu_file'},
{menu: 'edit', id: '#mnu_edit'},
{menu: 'management', id: '#mnu_management'},
{menu: 'tools', id: '#mnu_tools'},
{menu: 'help', id:'#mnu_help'}],
function(o) {
var $mnu = navbar.children(o.id).first(),
$dropdown = $mnu.children('.dropdown-menu').first();
$dropdown.empty();
if (pgAdmin.Browser.MenuCreator(
$dropdown, obj.menus[o.menu], obj.menu_categories
)) {
$mnu.removeClass('d-none');
}
});
navbar.children('#mnu_obj').removeClass('d-none');
obj.enable_disable_menus();
},
2016-06-13 10:17:36 -05:00
// General function to handle callbacks for object or dialog help.
showHelp: function(type, url, node, item) {
if (type == 'object_help') {
2016-06-13 10:17:36 -05:00
// Construct the URL
var server = node.getTreeNodeHierarchy(item).server;
var baseUrl = pgBrowser.utils.pg_help_path;
2016-06-13 10:17:36 -05:00
if (server.server_type == 'ppas') {
baseUrl = pgBrowser.utils.edbas_help_path;
2016-06-13 10:17:36 -05:00
}
var fullUrl = help.getHelpUrl(baseUrl, url, server.version);
2016-06-13 10:17:36 -05:00
window.open(fullUrl, 'postgres_help');
} else if(type == 'dialog_help') {
window.open(url, 'pgadmin_help');
2016-06-13 10:17:36 -05:00
}
},
_findTreeChildNode: function(_i, _d, _o) {
var loaded = _o.t.wasLoad(_i),
onLoad = function() {
var items = _o.t.children(_i),
i, d, n, idx = 0, size = items.length;
for (; idx < size; idx++) {
i = items.eq(idx);
d = _o.t.itemData(i);
if (d._type === _d._type) {
if (!_o.hasId || d._id == _d._id) {
_o.i = i;
_o.d = _d;
_o.pathOfTreeItems.push({coll: false, item: i, d: _d});
_o.success();
return;
}
} else {
n = _o.b.Nodes[d._type];
// Are we looking at the collection node for the given node?
if (
n && n.collection_node && d.nodes &&
_.indexOf(d.nodes, _d._type) != -1
) {
_o.i = i;
_o.d = null;
_o.pathOfTreeItems.push({coll: true, item: i, d: d});
// Set load to false when the current collection node's inode is false
if (!_o.t.isInode(i)) {
_o.load = false;
}
_o.b._findTreeChildNode(i, _d, _o);
return;
}
}
}
_o.notFound && typeof(_o.notFound) == 'function' &&
_o.notFound(_d);
};
if (!loaded && _o.load) {
_o.t.open(_i, {
success: onLoad,
unanimated: true,
fail: function() {
var fail = _o && _o.o && _o.o.fail;
if (
fail && typeof(fail) == 'function'
) {
fail.apply(_o.t, []);
}
},
});
} else if (loaded) {
onLoad();
} else {
_o.notFound && typeof(_o.notFound) == 'function' &&
_o.notFound(_d);
}
return;
},
onAddTreeNode: function(_data, _hierarchy, _opts) {
var ctx = {
b: this, // Browser
d: null, // current parent
hasId: true,
i: null, // current item
p: _.toArray(_hierarchy || {}).sort(
function(a, b) {
return (a.priority === b.priority) ? 0 : (
a.priority < b.priority ? -1 : 1
);
}
), // path of the parent
pathOfTreeItems: [], // path Item
t: this.tree, // Tree Api
o: _opts,
},
traversePath = function() {
var ctx = this, data;
ctx.success = traversePath;
if (ctx.p.length) {
data = ctx.p.shift();
// This is the parent node.
// Replace the parent-id of the data, which could be different
// from the given hierarchy.
if (!ctx.p.length) {
data._id = _data._pid;
ctx.success = addItemNode;
}
ctx.b._findTreeChildNode(
ctx.i, data, ctx
);
// if parent node is null
if (!_data._pid) {
addItemNode.apply(ctx, arguments);
}
}
return true;
}.bind(ctx),
addItemNode = function() {
// Append the new data in the tree under the current item.
// We may need to pass it to proper collection node.
var ctx = this,
first = (ctx.i || this.t.wasLoad(ctx.i)) &&
this.t.first(ctx.i),
findChildNode = function(success, notFound) {
var ctx = this;
ctx.success = success;
ctx.notFound = notFound;
ctx.b._findTreeChildNode(ctx.i, _data, ctx);
}.bind(ctx),
selectNode = function() {
this.t.openPath(this.i);
this.t.select(this.i);
if (
ctx.o && ctx.o.success && typeof(ctx.o.success) == 'function'
) {
ctx.o.success.apply(ctx.t, [ctx.i, _data]);
}
}.bind(ctx),
addNode = function() {
var ctx = this,
items = ctx.t.children(ctx.i),
s = 0, e = items.length - 1, i,
linearSearch = function() {
while (e >= s) {
i = items.eq(s);
var d = ctx.t.itemData(i);
if (
pgAdmin.natural_sort(
d._label, _data._label
) == 1
)
return true;
s++;
}
if (e != items.length - 1) {
i = items.eq(e);
return true;
}
i = null;
return false;
},
binarySearch = function() {
var d, m, res;
// Binary search only outperforms Linear search for n > 44.
// Reference:
// https://en.wikipedia.org/wiki/Binary_search_algorithm#cite_note-30
//
// We will try until it's half.
while (e - s > 22) {
i = items.eq(s);
d = ctx.t.itemData(i);
if (
pgAdmin.natural_sort(
d._label, _data._label
) != -1
)
return true;
i = items.eq(e);
d = ctx.t.itemData(i);
if (
pgAdmin.natural_sort(
d._label, _data._label
) != 1
)
return true;
m = s + Math.round((e - s) / 2);
i = items.eq(m);
d = ctx.t.itemData(i);
res = pgAdmin.natural_sort(d._label, _data._label);
if (res == 0)
return true;
if (res == -1) {
s = m + 1;
e--;
} else {
s++;
e = m - 1;
}
}
return linearSearch();
};
if (binarySearch()) {
ctx.t.before(i, {
itemData: _data,
success: function() {
if (
ctx.o && ctx.o.success && typeof(ctx.o.success) == 'function'
) {
ctx.o.success.apply(ctx.t, [i, _data]);
}
},
fail: function() {
console.warn('Failed to add before...', arguments);
if (
ctx.o && ctx.o.fail && typeof(ctx.o.fail) == 'function'
) {
ctx.o.fail.apply(ctx.t, [i, _data]);
}
},
});
} else {
var _append = function() {
var ctx = this,
is_parent_loaded_before = ctx.t.wasLoad(ctx.i),
_parent_data = ctx.t.itemData(ctx.i);
ctx.t.append(ctx.i, {
itemData: _data,
success: function(item, options) {
var i = $(options.items[0]);
// Open the item path only if its parent is loaded
// or parent type is same as nodes
if(
is_parent_loaded_before &&
_parent_data && _parent_data._type.search(
_data._type
) > -1
) {
ctx.t.openPath(i);
ctx.t.select(i);
} else {
if (_parent_data) {
// Unload the parent node so that we'll get
// latest data when we try to expand it
ctx.t.unload(ctx.i, {
success: function (item) {
// Lets try to load it now
ctx.t.open(item);
},
});
}
}
if (
ctx.o && ctx.o.success &&
typeof(ctx.o.success) == 'function'
) {
ctx.o.success.apply(ctx.t, [i, _data]);
}
},
fail: function() {
console.warn('Failed to append...', arguments);
if (
ctx.o && ctx.o.fail &&
typeof(ctx.o.fail) == 'function'
) {
ctx.o.fail.apply(ctx.t, [ctx.i, _data]);
}
},
});
}.bind(ctx);
if (ctx.i && !ctx.t.isInode(ctx.i)) {
ctx.t.setInode(ctx.i, {success: _append});
} else {
// Handle case for node without parent i.e. server-group
// or if parent node's inode is true.
_append();
}
}
}.bind(ctx);
// Parent node do not have any children, let me unload it.
if (!first && ctx.t.wasLoad(ctx.i)) {
ctx.t.unload(ctx.i, {
success: function() {
findChildNode(
selectNode,
function() {
var o = this && this.o;
if (
o && o.fail && typeof(o.fail) == 'function'
) {
o.fail.apply(this.t, [this.i, _data]);
}
}.bind(this)
);
}.bind(this),
fail: function() {
var o = this && this.o;
if (
o && o.fail && typeof(o.fail) == 'function'
) {
o.fail.apply(this.t, [this.i, _data]);
}
}.bind(this),
});
return;
}
// We can find the collection node using _findTreeChildNode
// indirectly.
findChildNode(selectNode, addNode);
}.bind(ctx);
if (!ctx.t.wasInit() || !_data) {
return;
}
_data._label = _data.label;
_data.label = _.escape(_data.label);
traversePath();
},
onUpdateTreeNode: function(_old, _new, _hierarchy, _opts) {
var ctx = {
b: this, // Browser
d: null, // current parent
i: null, // current item
hasId: true,
p: _.toArray(_hierarchy || {}).sort(
function(a, b) {
return (a.priority === b.priority) ? 0 : (
a.priority < b.priority ? -1 : 1
);
}
), // path of the old object
pathOfTreeItems: [], // path items
t: this.tree, // Tree Api
o: _opts,
load: true,
old: _old,
new: _new,
op: null,
},
errorOut = function() {
var fail = this.o && this.o.fail;
if (fail && typeof(fail) == 'function') {
fail.apply(this.t, [this.i, _new, _old]);
}
}.bind(ctx),
deleteNode = function() {
var self = this,
pathOfTreeItems = this.pathOfTreeItems,
findParent = function() {
if (pathOfTreeItems.length) {
pathOfTreeItems.pop();
var length = pathOfTreeItems.length;
this.i = (length && pathOfTreeItems[length - 1].item) || null;
this.d = (length && pathOfTreeItems[length - 1].d) || null;
// It is a collection item, let's find the node item
if (length && pathOfTreeItems[length - 1].coll) {
pathOfTreeItems.pop();
length = pathOfTreeItems.length;
this.i = (length && pathOfTreeItems[length - 1].item) || null;
this.d = (length && pathOfTreeItems[length - 1].d) || null;
}
} else {
this.i = null;
this.d = null;
}
}.bind(this);
var _item_parent = (this.i
&& this.t.hasParent(this.i)
&& this.t.parent(this.i)) || null,
_item_grand_parent = _item_parent ?
(this.t.hasParent(_item_parent)
&& this.t.parent(_item_parent))
: null;
// Remove the current node first.
if (
this.i && this.d && this.old._id == this.d._id &&
this.old._type == this.d._type
) {
var _parent = this.t.parent(this.i) || null;
// If there is no parent then just update the node
if(_parent.length == 0 && ctx.op == 'UPDATE') {
updateNode();
} else {
var postRemove = function() {
// If item has parent but no grand parent
if (_item_parent && !_item_grand_parent) {
var parent = null;
// We need to search in all parent siblings (eg: server groups)
var parents = this.t.siblings(this.i) || [];
parents.push(this.i[0]);
_.each(parents, function (p) {
var d = self.t.itemData($(p));
// If new server group found then assign it parent
if(d._id == self.new._pid) {
parent = p;
self.pathOfTreeItems.push({coll: true, item: parent, d: d});
}
});
if (parent) {
this.load = true;
this.success = function() {
addItemNode();
}.bind(this);
// We can refresh the collection node, but - let's not bother about
// it right now.
this.notFound = errorOut;
// var _d = {_id: this.new._pid, _type: self.d._type};
parent = $(parent);
var loaded = this.t.wasLoad(parent),
onLoad = function() {
self.i = parent;
self.d = self.d;
self.pathOfTreeItems.push({coll: false, item: parent, d: self.d});
self.success();
return;
};
if (!loaded && self.load) {
self.t.open(parent, {
success: onLoad,
unanimated: true,
fail: function() {
var fail = self && self.o && self.o.fail;
if (
fail && typeof(fail) == 'function'
) {
fail.apply(self.t, []);
}
},
});
} else {
onLoad();
}
}
return;
} else {
// This is for rest of the nodes
var _parentData = this.d;
// Find the grand-parent, or the collection node of parent.
findParent();
if (this.i) {
this.load = true;
this.success = function() {
addItemNode();
}.bind(this);
// We can refresh the collection node, but - let's not bother about
// it right now.
this.notFound = errorOut;
// Find the new parent
this.b._findTreeChildNode(
this.i, {_id: this.new._pid, _type: _parentData._type}, this
);
} else {
addItemNode();
}
return;
}
}.bind(this);
// If there is a parent then we can remove the node
this.t.remove(this.i, {
success: function() {
// Find the parent
findParent();
// If server group have no children then close it and set inode
// and unload it so it can fetch new data on next expand
if (_item_parent && !_item_grand_parent && _parent
&& self.t.children(_parent).length == 0) {
self.t.setInode(_parent, {
success: function() {
self.t.unload(_parent, {success: function() {
setTimeout(postRemove);
}}
);
},
});
} else {
setTimeout(postRemove);
}
return true;
},
}
);
}
}
errorOut();
}.bind(ctx),
findNewParent = function(_d) {
var findParent = function() {
var pathOfTreeItems = this.pathOfTreeItems;
if (pathOfTreeItems.length) {
pathOfTreeItems.pop();
var length = pathOfTreeItems.length;
this.i = (length && pathOfTreeItems[length - 1].item) || null;
this.d = (length && pathOfTreeItems[length - 1].d) || null;
// It is a collection item, let's find the node item
if (length && pathOfTreeItems[length - 1].coll) {
pathOfTreeItems.pop();
length = pathOfTreeItems.length;
this.i = (length && pathOfTreeItems[length - 1].item) || null;
this.d = (length && pathOfTreeItems[length - 1].d) || null;
}
} else {
this.i = null;
this.d = null;
}
}.bind(this);
// old parent was not found, can we find the new parent?
if (this.i) {
this.load = true;
this.success = function() {
addItemNode();
}.bind(this);
if (_d._type == this.old._type) {
// We were already searching the old object under the parent.
findParent();
_d = this.d;
// Find the grand parent
findParent();
}
_d = this.new._pid;
// We can refresh the collection node, but - let's not bother about
// it right now.
this.notFound = errorOut;
// Find the new parent
this.b._findTreeChildNode(this.i, _d, this);
} else {
addItemNode();
}
}.bind(ctx),
updateNode = function() {
if (
this.i && this.d && this.new._type == this.d._type
) {
var self = this,
_id = this.d._id;
if (this.new._id != this.d._id) {
// Found the new oid, update its node_id
var node_data = this.t.itemData(ctx.i);
node_data._id = _id = this.new._id;
}
if (this.new._id == _id) {
// Found the current
_.extend(this.d, {
'_id': this.new._id,
'_label': this.new._label,
'label': this.new.label,
});
this.t.setLabel(ctx.i, {label: this.new.label});
this.t.addIcon(ctx.i, {icon: this.new.icon});
this.t.setId(ctx.i, {id: this.new.id});
this.t.openPath(this.i);
this.t.deselect(this.i);
// select tree item after few milliseconds
setTimeout(function() {
self.t.select(self.i);
}, 10);
}
}
var success = this.o && this.o.success;
if (success && typeof(success) == 'function') {
success.apply(this.t, [this.i, _old, _new]);
}
}.bind(ctx),
traversePath = function() {
var ctx = this, data;
ctx.success = traversePath;
if (ctx.p.length) {
data = ctx.p.shift();
// This is the node, we can now do the required operations.
// We should first delete the existing node, if the parent-id is
// different.
if (!ctx.p.length) {
if (ctx.op == 'RECREATE') {
ctx.load = false;
ctx.success = deleteNode;
ctx.notFound = findNewParent;
} else {
ctx.success = updateNode;
ctx.notFound = errorOut;
}
}
ctx.b._findTreeChildNode(
ctx.i, data, ctx
);
} else if (ctx.p.length == 1) {
ctx.notFound = findNewParent;
}
return true;
}.bind(ctx),
addItemNode = function() {
var ctx = this,
first = (ctx.i || this.t.wasLoad(ctx.i)) &&
this.t.first(ctx.i),
findChildNode = function(success, notFound) {
var ctx = this;
ctx.success = success;
ctx.notFound = notFound;
ctx.b._findTreeChildNode(ctx.i, _new, ctx);
}.bind(ctx),
selectNode = function() {
this.t.openPath(this.i);
this.t.select(this.i);
if (
ctx.o && ctx.o.success && typeof(ctx.o.success) == 'function'
) {
ctx.o.success.apply(ctx.t, [ctx.i, _new]);
}
}.bind(ctx),
addNode = function() {
var ctx = this,
items = ctx.t.children(ctx.i),
s = 0, e = items.length - 1, i,
linearSearch = function() {
while (e >= s) {
i = items.eq(s);
var d = ctx.t.itemData(i);
if (
pgAdmin.natural_sort(
d._label, _new._label
) == 1
)
return true;
s++;
}
if (e != items.length - 1) {
i = items.eq(e);
return true;
}
i = null;
return false;
},
binarySearch = function() {
while (e - s > 22) {
i = items.eq(s);
var d = ctx.t.itemData(i);
if (
pgAdmin.natural_sort(
d._label, _new._label
) != -1
)
return true;
i = items.eq(e);
d = ctx.t.itemData(i);
if (
pgAdmin.natural_sort(
d._label, _new._label
) != 1
)
return true;
var m = s + Math.round((e - s) / 2);
i = items.eq(m);
d = ctx.t.itemData(i);
var res = pgAdmin.natural_sort(d._label, _new._label);
if (res == 0)
return true;
if (res == -1) {
s = m + 1;
e--;
} else {
s++;
e = m - 1;
}
}
return linearSearch();
};
if (binarySearch()) {
ctx.t.before(i, {
itemData: _new,
success: function() {
var new_item = $(arguments[1].items[0]);
ctx.t.openPath(new_item);
ctx.t.select(new_item);
if (
ctx.o && ctx.o.success && typeof(ctx.o.success) == 'function'
) {
ctx.o.success.apply(ctx.t, [i, _old, _new]);
}
},
fail: function() {
console.warn('Failed to add before..', arguments);
if (
ctx.o && ctx.o.fail && typeof(ctx.o.fail) == 'function'
) {
ctx.o.fail.apply(ctx.t, [i, _old, _new]);
}
},
});
} else {
var _appendNode = function() {
ctx.t.append(ctx.i, {
itemData: _new,
success: function() {
var new_item = $(arguments[1].items[0]);
ctx.t.openPath(new_item);
ctx.t.select(new_item);
if (
ctx.o && ctx.o.success && typeof(ctx.o.success) == 'function'
) {
ctx.o.success.apply(ctx.t, [ctx.i, _old, _new]);
}
},
fail: function() {
console.warn('Failed to append...', arguments);
if (
ctx.o && ctx.o.fail && typeof(ctx.o.fail) == 'function'
) {
ctx.o.fail.apply(ctx.t, [ctx.i, _old, _new]);
}
},
});
};
// If the current node's inode is false
if (ctx.i && !ctx.t.isInode(ctx.i)) {
ctx.t.setInode(ctx.i, {success: _appendNode});
} else {
// Handle case for node without parent i.e. server-group
// or if parent node's inode is true.
_appendNode();
}
}
}.bind(ctx);
// Parent node do not have any children, let me unload it.
if (!first && ctx.t.wasLoad(ctx.i)) {
ctx.t.unload(ctx.i, {
success: function() {
findChildNode(
selectNode,
function() {
var o = this && this.o;
if (
o && o.fail && typeof(o.fail) == 'function'
) {
o.fail.apply(this.t, [this.i, _old, _new]);
}
}.bind(this)
);
}.bind(this),
fail: function() {
var o = this && this.o;
if (
o && o.fail && typeof(o.fail) == 'function'
) {
o.fail.apply(this.t, [this.i, _old, _new]);
}
}.bind(this),
});
return;
}
// We can find the collection node using _findTreeChildNode
// indirectly.
findChildNode(selectNode, addNode);
}.bind(ctx);
if (!ctx.t.wasInit() || !_new || !_old) {
return;
}
ctx.pathOfTreeItems.push(_old);
_new._label = _new.label;
_new.label = _.escape(_new.label);
// We need to check if this is collection node and have
// children count, if yes then add count with label too
if(ctx.b.Nodes[_new._type].is_collection) {
if ('collection_count' in _old && _old['collection_count'] > 0) {
_new.label = _.escape(_new._label) +
' <span>(' + _old['collection_count'] + ')</span>';
}
}
// If server icon/background changes then also we need to re-create it
if(_old._type == 'server' && _new._type == 'server' &&
( _old._pid != _new._pid ||
_old._label != _new._label ||
_old.icon != _new.icon )
) {
ctx.op = 'RECREATE';
traversePath();
} else if (_old._pid != _new._pid || _old._label != _new._label) {
ctx.op = 'RECREATE';
traversePath();
} else {
ctx.op = 'UPDATE';
traversePath();
}
},
onRefreshTreeNode: function(_i, _opts) {
var d = _i && this.tree.itemData(_i),
n = d && d._type && this.Nodes[d._type],
ctx = {
b: this, // Browser
d: d, // current parent
i: _i, // current item
p: null, // path of the old object
pathOfTreeItems: [], // path items
t: this.tree, // Tree Api
o: _opts,
},
isOpen,
idx = -1;
this.Events.trigger('pgadmin-browser:tree:refreshing', _i, d, n);
if (!n) {
_i = null;
ctx.i = null;
ctx.d = null;
} else {
isOpen = (this.tree.isInode(_i) && this.tree.isOpen(_i));
}
ctx.branch = ctx.t.serialize(
_i, {}, function(i, el, d) {
idx++;
if (!idx || (d.inode && d.open)) {
return {
_id: d._id, _type: d._type, branch: d.branch, open: d.open,
};
}
});
if (!n) {
ctx.t.destroy({
success: function() {
initializeBrowserTree(ctx.b);
ctx.t = ctx.b.tree;
ctx.i = null;
ctx.b._refreshNode(ctx, ctx.branch);
},
error: function() {
var fail = (_opts.o && _opts.o.fail) || _opts.fail;
if (typeof(fail) == 'function') {
fail();
}
},
});
return;
}
var fetchNodeInfo = function(_i, _d, _n) {
var info = _n.getTreeNodeHierarchy(_i),
url = _n.generate_url(_i, 'nodes', _d, true);
$.ajax({
url: url,
type: 'GET',
cache: false,
dataType: 'json',
})
.done(function(res) {
// Node information can come as result/data
var newData = res.result || res.data;
newData._label = newData.label;
newData.label = _.escape(newData.label);
ctx.t.setLabel(ctx.i, {label: newData.label});
ctx.t.addIcon(ctx.i, {icon: newData.icon});
ctx.t.setId(ctx.i, {id: newData.id});
if (newData.inode)
ctx.t.setInode(ctx.i, {inode: true});
// This will update the tree item data.
var itemData = ctx.t.itemData(ctx.i);
_.extend(itemData, newData);
if (
_n.can_expand && typeof(_n.can_expand) == 'function'
) {
if (!_n.can_expand(itemData)) {
ctx.t.unload(ctx.i);
return;
}
}
ctx.b._refreshNode(ctx, ctx.branch);
var success = (ctx.o && ctx.o.success) || ctx.success;
if (success && typeof(success) == 'function') {
success();
}
})
.fail(function(xhr, error, status) {
if (
!Alertify.pgHandleItemError(
xhr, error, status, {item: _i, info: info}
)
) {
var contentType = xhr.getResponseHeader('Content-Type'),
jsonResp = (
contentType &&
contentType.indexOf('application/json') == 0 &&
JSON.parse(xhr.responseText)
) || {};
if (xhr.status == 410 && jsonResp.success == 0) {
var p = ctx.t.parent(ctx.i);
ctx.t.remove(ctx.i, {
success: function() {
if (p) {
// Try to refresh the parent on error
try {
pgBrowser.Events.trigger(
'pgadmin:browser:tree:refresh', p
);
} catch (e) { console.warn(e.stack || e); }
}
},
});
}
Alertify.pgNotifier(
error, xhr, gettext('Error retrieving details for the node.'),
function() { console.warn(arguments); }
);
}
});
}.bind(this);
if (n && n.collection_node) {
var p = ctx.i = this.tree.parent(_i),
unloadNode = function() {
this.tree.unload(_i, {
success: function() {
_i = p;
d = ctx.d = ctx.t.itemData(ctx.i);
n = ctx.b.Nodes[d._type];
_i = p;
fetchNodeInfo(_i, d, n);
},
fail: function() { console.warn(arguments); },
});
}.bind(this);
if (!this.tree.isInode(_i)) {
this.tree.setInode(_i, { success: unloadNode });
} else {
unloadNode();
}
} else if (isOpen) {
this.tree.unload(_i, {
success: fetchNodeInfo.bind(this, _i, d, n),
fail: function() {
console.warn(arguments);
},
});
} else if (!this.tree.isInode(_i) && d.inode) {
this.tree.setInode(_i, {
success: fetchNodeInfo.bind(this, _i, d, n),
fail: function() {
console.warn(arguments);
},
});
} else {
fetchNodeInfo(_i, d, n);
}
},
removeChildTreeNodesById: function(_parentNode, _collType, _childIds) {
var tree = pgBrowser.tree;
if(_parentNode && _collType) {
var children = tree.children(_parentNode),
idx = 0, size = children.length,
childNode, childNodeData;
_parentNode = null;
for (; idx < size; idx++) {
childNode = children.eq(idx);
childNodeData = tree.itemData(childNode);
if (childNodeData._type == _collType) {
_parentNode = childNode;
break;
}
}
}
if (_parentNode) {
children = tree.children(_parentNode);
idx = 0;
size = children.length;
for (; idx < size; idx++) {
childNode = children.eq(idx);
childNodeData = tree.itemData(childNode);
if (_childIds.indexOf(childNodeData._id) != -1) {
pgBrowser.removeTreeNode(childNode, false, _parentNode);
}
}
return true;
}
return false;
},
removeTreeNode: function(_node, _selectNext, _parentNode) {
var tree = pgBrowser.tree,
nodeToSelect = null;
if (!_node)
return false;
if (_selectNext) {
nodeToSelect = tree.next(_node);
if (!nodeToSelect || !nodeToSelect.length) {
nodeToSelect = tree.prev(_node);
if (!nodeToSelect || !nodeToSelect.length) {
if (!_parentNode) {
nodeToSelect = tree.parent(_node);
} else {
nodeToSelect = _parentNode;
}
}
}
if (nodeToSelect)
tree.select(nodeToSelect);
}
tree.remove(_node);
return true;
},
findSiblingTreeNode: function(_node, _id) {
var tree = pgBrowser.tree,
parentNode = tree.parent(_node),
siblings = tree.children(parentNode),
idx = 0, nodeData, node;
for(; idx < siblings.length; idx++) {
node = siblings.eq(idx);
nodeData = tree.itemData(node);
if (nodeData && nodeData._id == _id)
return node;
}
return null;
},
findParentTreeNodeByType: function(_node, _parentType) {
var tree = pgBrowser.tree,
nodeData,
node = _node;
do {
nodeData = tree.itemData(node);
if (nodeData && nodeData._type == _parentType)
return node;
node = tree.hasParent(node) ? tree.parent(node) : null;
} while (node);
return null;
},
findChildCollectionTreeNode: function(_node, _collType) {
var tree = pgBrowser.tree,
nodeData, idx = 0,
node = _node,
children = _node && tree.children(_node);
if (!children || !children.length)
return null;
for(; idx < children.length; idx++) {
node = children.eq(idx);
nodeData = tree.itemData(node);
if (nodeData && nodeData._type == _collType)
return node;
}
return null;
},
addChildTreeNodes: function(_treeHierarchy, _node, _type, _arrayIds, _callback) {
var module = _type in pgBrowser.Nodes && pgBrowser.Nodes[_type],
childTreeInfo = _arrayIds.length && _.extend(
{}, _.mapObject(_treeHierarchy, function(_val) {
_val.priority -= 1; return _val;
})),
arrayChildNodeData = [],
fetchNodeInfo = function(_callback) {
if (!_arrayIds.length) {
if (_callback) {
_callback();
}
return;
}
var childDummyInfo = {
'_id': _arrayIds.pop(), '_type': _type, 'priority': 0,
},
childNodeUrl;
childTreeInfo[_type] = childDummyInfo;
childNodeUrl = module.generate_url(
null, 'nodes', childDummyInfo, true, childTreeInfo
);
$.ajax({
url: childNodeUrl,
dataType: 'json',
})
.done(function(res) {
if (res.success) {
arrayChildNodeData.push(res.data);
}
fetchNodeInfo(_callback);
})
.fail(function(xhr, status, error) {
Alertify.pgRespErrorNotify(xhr, error);
fetchNodeInfo(_callback);
});
};
if (!module) {
console.warn(
'Developer: Couldn\'t find the module for the given child: ',
_.clone(arguments)
);
return;
}
if (pgBrowser.tree.wasLoad(_node) || pgBrowser.tree.isLeaf(_node)) {
fetchNodeInfo(function() {
_.each(arrayChildNodeData, function(_nodData) {
pgBrowser.Events.trigger(
'pgadmin:browser:tree:add', _nodData, _treeHierarchy
);
});
if (_callback) {
_callback();
}
});
} else {
if (_callback) {
_callback();
}
}
},
_refreshNode: function(_ctx, _d) {
var traverseNodes = function(_d) {
var _ctx = this, idx = 0, ctx, d,
size = (_d.branch && _d.branch.length) || 0,
findNode = function(_i, __d, __ctx) {
setTimeout(
function() {
__ctx.b._findTreeChildNode(_i, __d, __ctx);
}, 0
);
};
for (; idx < size; idx++) {
d = _d.branch[idx];
var n = _ctx.b.Nodes[d._type];
ctx = {
b: _ctx.b,
t: _ctx.t,
pathOfTreeItems: [],
i: _ctx.i,
d: d,
select: _ctx.select,
hasId: n && !n.collection_node,
o: _ctx.o,
load: true,
};
ctx.success = function() {
this.b._refreshNode.call(this.b, this, this.d);
}.bind(ctx);
findNode(_ctx.i, d, ctx);
}
}.bind(_ctx, _d);
if (!_d || !_d.open)
return;
if (!_ctx.t.isOpen(_ctx.i)) {
_ctx.t.open(_ctx.i, {
unanimated: true,
success: traverseNodes,
fail: function() { /* Do nothing */ },
});
} else {
traverseNodes();
}
},
editor_shortcut_keys: {
// Autocomplete sql command
'Ctrl-Space': 'autocomplete',
'Cmd-Space': 'autocomplete',
// Select All text
'Ctrl-A': 'selectAll',
'Cmd-A': 'selectAll',
// Redo text
'Ctrl-Y': 'redo',
'Cmd-Y': 'redo',
// Undo text
'Ctrl-Z': 'undo',
'Cmd-Z': 'undo',
// Delete Line
'Ctrl-D': 'deleteLine',
'Cmd-D': 'deleteLine',
// Go to start/end of Line
'Alt-Left': 'goLineStart',
'Alt-Right': 'goLineEnd',
// Move word by word left/right
'Ctrl-Alt-Left': 'goGroupLeft',
'Cmd-Alt-Left': 'goGroupLeft',
'Ctrl-Alt-Right': 'goGroupRight',
'Cmd-Alt-Right': 'goGroupRight',
// Allow user to delete Tab(s)
'Shift-Tab': 'indentLess',
},
editor_options: {
tabSize: parseInt(pgBrowser.utils.tabSize),
wrapCode: pgBrowser.utils.wrapCode,
insert_pair_brackets: pgBrowser.utils.insertPairBrackets,
brace_matching: pgBrowser.utils.braceMatching,
indent_with_tabs: pgBrowser.utils.is_indent_with_tabs,
},
// This function will return the name and version of the browser.
get_browser: function() {
var ua=navigator.userAgent,tem,M=ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
if(/trident/i.test(M[1])) {
tem=/\brv[ :]+(\d+)/g.exec(ua) || [];
return {name:'IE', version:(tem[1]||'')};
}
if(M[1]==='Chrome') {
tem=ua.match(/\bOPR|Edge\/(\d+)/);
if(tem!=null) {return {name:tem[0], version:tem[1]};}
}
M=M[2]? [M[1], M[2]]: [navigator.appName, navigator.appVersion, '-?'];
if((tem=ua.match(/version\/(\d+)/i))!=null) {M.splice(1,1,tem[1]);}
return {
name: M[0],
version: M[1],
};
},
});
/* Remove paste event mapping from CodeMirror's emacsy KeyMap binding
* specific to Mac LineNumber:5797 - lib/Codemirror.js
* It is preventing default paste event(Cmd-V) from triggering
* in runtime.
*/
delete CodeMirror.keyMap.emacsy['Ctrl-V'];
// Use spaces instead of tab
if (pgBrowser.utils.useSpaces == 'True') {
pgAdmin.Browser.editor_shortcut_keys.Tab = 'insertSoftTab';
}
$(window).on('beforeunload', function(e) {
let tree_save_interval = pgBrowser.get_preference('browser', 'browser_tree_state_save_interval'),
confirm_on_refresh_close = pgBrowser.get_preference('browser', 'confirm_on_refresh_close');
if (!_.isUndefined(tree_save_interval) && tree_save_interval.value !== -1)
pgAdmin.Browser.browserTreeState.save_state();
if(confirm_on_refresh_close.value) {
/* This message will not be displayed in Chrome, Firefox, Safari as they have disabled it*/
let msg = S(gettext('Are you sure you want to close the %s browser?')).sprintf(pgBrowser.utils.app_name).value();
e.originalEvent.returnValue = msg;
return msg;
}
});
return pgAdmin.Browser;
});