mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2026-10-28 07:30:05 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
969a2961fe | ||
|
|
6b80523872 | ||
|
|
c16c593322 | ||
|
|
6a7339bbe4 | ||
|
|
41a20e0252 | ||
|
|
2dd075161d | ||
|
|
91075cc3f5 | ||
|
|
26c7d76d4f | ||
|
|
2185ea302d | ||
|
|
32defc91cc |
No files matched your search
Binary file not shown.
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 53 KiB |
Executable → Regular
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 168 KiB |
@@ -25,6 +25,9 @@ Use the *File* menu to access the following options:
|
||||
+-------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| *Reset Layout* | If you have modified the workspace, click to restore the default layout. |
|
||||
+-------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| *Lock Layout* | Click to open a submenu to select the level for locking the UI layout |
|
||||
| | This can also be changed from browser display :ref:`preferences <preferences>` |
|
||||
+-------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
|
||||
The Object Menu
|
||||
***************
|
||||
|
||||
@@ -38,10 +38,6 @@ Use the fields on the *Display* panel to specify general display preferences:
|
||||
attempt to catch browser close or refresh events and prompt before allowing
|
||||
them to continue.
|
||||
|
||||
* When the *Show system objects?* switch is set to *True*, the client will
|
||||
display system objects such as system schemas (for example, *pg_temp*) or
|
||||
system columns (for example, *xmin* or *ctid*) in the tree control.
|
||||
|
||||
* When the *Enable browser tree animation?* switch is set to *True*, the client
|
||||
will display the animated tree control otherwise it will be unanimated.
|
||||
|
||||
@@ -49,6 +45,23 @@ Use the fields on the *Display* panel to specify general display preferences:
|
||||
the client will display the animated dialogues/notifications otherwise it
|
||||
will be unanimated.
|
||||
|
||||
* Use the *Lock layout* field to lock the UI layout at different levels. This
|
||||
can also be changed from File menu on the :ref:`menu bar <menu_bar>`
|
||||
|
||||
+---------------------+-------------------------------------------------------------------+
|
||||
| Option | Action |
|
||||
+=====================+===================================================================+
|
||||
| *None* | No locking. Every panel is resizable and dockable. |
|
||||
+---------------------+-------------------------------------------------------------------+
|
||||
| *Prevent docking* | This will disable the docking/undocking of the panels |
|
||||
+---------------------+-------------------------------------------------------------------+
|
||||
| *Full* | This will disable resizing, docking/undocking of the panels |
|
||||
+---------------------+-------------------------------------------------------------------+
|
||||
|
||||
* When the *Show system objects?* switch is set to *True*, the client will
|
||||
display system objects such as system schemas (for example, *pg_temp*) or
|
||||
system columns (for example, *xmin* or *ctid*) in the tree control.
|
||||
|
||||
Use the fields on the *Keyboard shortcuts* panel to configure shortcuts for the
|
||||
main window navigation:
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ notes for it.
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
release_notes_4_8
|
||||
release_notes_4_7
|
||||
release_notes_4_6
|
||||
release_notes_4_5
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
***********
|
||||
Version 4.8
|
||||
***********
|
||||
|
||||
Release date: 2019-06-03
|
||||
|
||||
This release contains a number of bug fixes and new features since the release of pgAdmin4 4.7.
|
||||
|
||||
New features
|
||||
************
|
||||
|
||||
| `Feature #2653 <https://redmine.postgresql.org/issues/2653>`_ - Allow the UI layout to be fully locked or to prevent docking changes.
|
||||
|
||||
Bug fixes
|
||||
*********
|
||||
|
||||
| `Bug #4169 <https://redmine.postgresql.org/issues/4169>`_ - Omit the geometry viewer in the Query Tool from layout saving.
|
||||
| `Bug #4307 <https://redmine.postgresql.org/issues/4307>`_ - Improve the performance of explain plan by embedding the images only when downloading it.
|
||||
| `Bug #4308 <https://redmine.postgresql.org/issues/4308>`_ - Fix the issue of accessing the SQL for Views and Materialized Views. Regression of pluralisation of folder names.
|
||||
+2
-2
@@ -8,9 +8,9 @@
|
||||
<key>CFBundleName</key>
|
||||
<string>pgAdmin 4</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>4.7.0</string>
|
||||
<string>4.8.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>4.7.0</string>
|
||||
<string>4.8.0</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.10</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
VERSION = 4.7.0.0
|
||||
VERSION = 4.8.0.0
|
||||
QMAKE_TARGET_COMPANY = "The pgAdmin Development Team"
|
||||
QMAKE_TARGET_PRODUCT = "pgAdmin 4"
|
||||
QMAKE_TARGET_DESCRIPTION = "pgAdmin 4 Desktop Runtime"
|
||||
|
||||
+2
-2
@@ -50,7 +50,7 @@ APP_ICON = 'pg-icon'
|
||||
|
||||
# Application version number components
|
||||
APP_RELEASE = 4
|
||||
APP_REVISION = 7
|
||||
APP_REVISION = 8
|
||||
|
||||
# Application version suffix, e.g. 'beta1', 'dev'. Usually an empty string
|
||||
# for GA releases.
|
||||
@@ -59,7 +59,7 @@ APP_SUFFIX = ''
|
||||
# Numeric application version for upgrade checks. Should be in the format:
|
||||
# [X]XYYZZ, where X is the release version, Y is the revision, with a leading
|
||||
# zero if needed, and Z represents the suffix, with a leading zero if needed
|
||||
APP_VERSION_INT = 40700
|
||||
APP_VERSION_INT = 40800
|
||||
|
||||
# DO NOT CHANGE!
|
||||
# The application version string, constructed from the components
|
||||
|
||||
+1
-1
@@ -89,7 +89,7 @@
|
||||
"underscore": "^1.9.1",
|
||||
"underscore.string": "^3.3.5",
|
||||
"watchify": "~3.11.1",
|
||||
"webcabin-docker": "git+https://github.com/EnterpriseDB/wcDocker/#e191a1063c111d5542ff26a7c163940be180333d",
|
||||
"webcabin-docker": "git+https://github.com/EnterpriseDB/wcDocker/#f3c79e4b9a9e76a8e34736fc33473e65c524050c",
|
||||
"wkx": "^0.4.6"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -236,6 +236,7 @@ def create_app(app_name=None):
|
||||
os.environ['PGADMIN_TESTING_MODE'] == '1'
|
||||
):
|
||||
config.SQLITE_PATH = config.TEST_SQLITE_PATH
|
||||
config.MASTER_PASSWORD_REQUIRED = False
|
||||
|
||||
# Ensure the various working directories exist
|
||||
from pgadmin.setup import create_app_data_directory, db_upgrade
|
||||
|
||||
@@ -39,6 +39,7 @@ from pgadmin.utils import PgAdminModule
|
||||
from pgadmin.utils.ajax import make_json_response
|
||||
from pgadmin.utils.csrf import pgCSRFProtect
|
||||
from pgadmin.utils.preferences import Preferences
|
||||
from pgadmin.utils.menu import MenuItem
|
||||
from pgadmin.browser.register_browser_preferences import \
|
||||
register_browser_preferences
|
||||
from pgadmin.utils.master_password import validate_master_password, \
|
||||
@@ -215,6 +216,40 @@ class BrowserModule(PgAdminModule):
|
||||
scripts.extend(module.get_own_javascripts())
|
||||
return scripts
|
||||
|
||||
def get_own_menuitems(self):
|
||||
return {
|
||||
'file_items': [
|
||||
MenuItem(
|
||||
name='mnu_locklayout',
|
||||
module='pgAdmin.Browser',
|
||||
label=gettext('Lock Layout'),
|
||||
priority=999,
|
||||
menu_items=[MenuItem(
|
||||
name='mnu_lock_none',
|
||||
module='pgAdmin.Browser',
|
||||
callback='mnu_lock_none',
|
||||
priority=0,
|
||||
label=gettext('None'),
|
||||
checked=True
|
||||
), MenuItem(
|
||||
name='mnu_lock_docking',
|
||||
module='pgAdmin.Browser',
|
||||
callback='mnu_lock_docking',
|
||||
priority=1,
|
||||
label=gettext('Prevent Docking'),
|
||||
checked=False
|
||||
), MenuItem(
|
||||
name='mnu_lock_full',
|
||||
module='pgAdmin.Browser',
|
||||
callback='mnu_lock_full',
|
||||
priority=2,
|
||||
label=gettext('Full Lock'),
|
||||
checked=False
|
||||
)]
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
def register_preferences(self):
|
||||
register_browser_preferences(self)
|
||||
|
||||
@@ -226,7 +261,8 @@ class BrowserModule(PgAdminModule):
|
||||
return ['browser.index', 'browser.nodes',
|
||||
'browser.check_master_password',
|
||||
'browser.set_master_password',
|
||||
'browser.reset_master_password']
|
||||
'browser.reset_master_password',
|
||||
'browser.lock_layout']
|
||||
|
||||
|
||||
blueprint = BrowserModule(MODULE_NAME, __name__)
|
||||
@@ -815,6 +851,21 @@ def set_master_password():
|
||||
)
|
||||
|
||||
|
||||
@blueprint.route("/lock_layout", endpoint="lock_layout", methods=["PUT"])
|
||||
def lock_layout():
|
||||
data = None
|
||||
|
||||
if hasattr(request.data, 'decode'):
|
||||
data = request.data.decode('utf-8')
|
||||
|
||||
if data != '':
|
||||
data = json.loads(data)
|
||||
|
||||
blueprint.lock_layout.set(data['value'])
|
||||
|
||||
return make_json_response()
|
||||
|
||||
|
||||
# Only register route if SECURITY_CHANGEABLE is set to True
|
||||
# We can't access app context here so cannot
|
||||
# use app.config['SECURITY_CHANGEABLE']
|
||||
|
||||
@@ -8,6 +8,12 @@
|
||||
##########################################################################
|
||||
from flask_babelex import gettext
|
||||
|
||||
LOCK_LAYOUT_LEVEL = {
|
||||
'PREVENT_DOCKING': 'docking',
|
||||
'FULL': 'full',
|
||||
'NONE': 'none'
|
||||
}
|
||||
|
||||
|
||||
def register_browser_preferences(self):
|
||||
self.show_system_objects = self.preference.register(
|
||||
@@ -58,6 +64,21 @@ def register_browser_preferences(self):
|
||||
)
|
||||
)
|
||||
|
||||
self.lock_layout = self.preference.register(
|
||||
'display', 'lock_layout',
|
||||
gettext('Lock Layout'), 'radioModern', LOCK_LAYOUT_LEVEL['NONE'],
|
||||
category_label=gettext('Display'), options=[
|
||||
{'label': gettext('None'), 'value': LOCK_LAYOUT_LEVEL['NONE']},
|
||||
{'label': gettext('Prevent Docking'),
|
||||
'value': LOCK_LAYOUT_LEVEL['PREVENT_DOCKING']},
|
||||
{'label': gettext('Full Lock'),
|
||||
'value': LOCK_LAYOUT_LEVEL['FULL']},
|
||||
],
|
||||
help_str=gettext(
|
||||
'Lock the UI layout at different levels'
|
||||
)
|
||||
)
|
||||
|
||||
self.table_row_count_threshold = self.preference.register(
|
||||
'properties', 'table_row_count_threshold',
|
||||
gettext("Count rows if estimated less than"), 'integer', 2000,
|
||||
|
||||
@@ -1540,7 +1540,7 @@ class FunctionView(PGChildNodeView, DataTypeReader):
|
||||
# Get schema name
|
||||
status, schema_name = self.conn.execute_scalar(
|
||||
render_template(
|
||||
'schema/pg/#{0}#/sql/get_name.sql'.format(
|
||||
'schemas/pg/#{0}#/sql/get_name.sql'.format(
|
||||
self.manager.version),
|
||||
scid=scid
|
||||
)
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ SELECT pg_proc.oid,
|
||||
WHEN proaccess = '-' THEN 'Private'
|
||||
ELSE 'Unknown' END AS visibility
|
||||
FROM pg_proc, pg_namespace, pg_language lng
|
||||
WHERE pr.prokind IN ('f', 'w')
|
||||
WHERE prokind IN ('f', 'w')
|
||||
AND pronamespace = {{pkgid}}::oid
|
||||
AND pg_proc.pronamespace = pg_namespace.oid
|
||||
AND lng.oid=prolang
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ SELECT pg_proc.oid,
|
||||
WHEN proaccess = '-' THEN 'Private'
|
||||
ELSE 'Unknown' END AS visibility
|
||||
FROM pg_proc, pg_namespace, pg_language lng
|
||||
WHERE pr.prokind = 'p'
|
||||
WHERE prokind = 'p'
|
||||
AND pronamespace = {{pkgid}}::oid
|
||||
AND pg_proc.pronamespace = pg_namespace.oid
|
||||
AND lng.oid=prolang
|
||||
|
||||
@@ -844,7 +844,7 @@ class SequenceView(PGChildNodeView):
|
||||
# Get schema name
|
||||
status, schema_name = self.conn.execute_scalar(
|
||||
render_template(
|
||||
'schema/pg/#{0}#/sql/get_name.sql'.format(
|
||||
'schemas/pg/#{0}#/sql/get_name.sql'.format(
|
||||
self.manager.version
|
||||
),
|
||||
scid=scid
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ class IndexesModule(CollectionNodeModule):
|
||||
if 'vid' not in kwargs:
|
||||
return True
|
||||
|
||||
template_path = 'index/sql/#{0}#'.format(manager.version)
|
||||
template_path = 'indexes/sql/#{0}#'.format(manager.version)
|
||||
SQL = render_template(
|
||||
"/".join([template_path, 'backend_support.sql']),
|
||||
vid=kwargs['vid']
|
||||
|
||||
Whitespace-only changes.
@@ -771,7 +771,7 @@ class ViewNode(PGChildNodeView, VacuumSettings):
|
||||
Updated properties data with column details
|
||||
"""
|
||||
|
||||
self.index_temp_path = 'index'
|
||||
self.index_temp_path = 'indexes'
|
||||
SQL = render_template("/".join([self.index_temp_path,
|
||||
'sql/#{0}#/column_details.sql'.format(
|
||||
self.manager.version)]), idx=idx)
|
||||
@@ -825,7 +825,7 @@ class ViewNode(PGChildNodeView, VacuumSettings):
|
||||
Updated properties data with column
|
||||
"""
|
||||
|
||||
self.trigger_temp_path = 'schema/trigger'
|
||||
self.trigger_temp_path = 'schemas/triggers'
|
||||
SQL = render_template("/".join([self.trigger_temp_path,
|
||||
'get_columns.sql']),
|
||||
tid=tid, clist=clist)
|
||||
@@ -887,7 +887,7 @@ class ViewNode(PGChildNodeView, VacuumSettings):
|
||||
import trigger_definition
|
||||
|
||||
# Define template path
|
||||
self.trigger_temp_path = 'trigger'
|
||||
self.trigger_temp_path = 'triggers'
|
||||
|
||||
SQL_data = ''
|
||||
SQL = render_template("/".join(
|
||||
@@ -970,7 +970,7 @@ class ViewNode(PGChildNodeView, VacuumSettings):
|
||||
into sql tab
|
||||
"""
|
||||
|
||||
self.index_temp_path = 'index'
|
||||
self.index_temp_path = 'indexes'
|
||||
SQL_data = ''
|
||||
SQL = render_template("/".join(
|
||||
[self.index_temp_path,
|
||||
|
||||
@@ -15,7 +15,7 @@ define('pgadmin.browser', [
|
||||
'sources/csrf', 'sources/keyboard_shortcuts', 'pgadmin.browser.utils',
|
||||
'wcdocker', 'jquery.contextmenu', 'jquery.aciplugin', 'jquery.acitree',
|
||||
'pgadmin.browser.preferences', 'pgadmin.browser.messages',
|
||||
'pgadmin.browser.menu', 'pgadmin.browser.panel',
|
||||
'pgadmin.browser.menu', 'pgadmin.browser.panel', 'pgadmin.browser.layout',
|
||||
'pgadmin.browser.error', 'pgadmin.browser.frame',
|
||||
'pgadmin.browser.node', 'pgadmin.browser.collection',
|
||||
'sources/codemirror/addon/fold/pgadmin-sqlfoldcode',
|
||||
@@ -282,22 +282,6 @@ define('pgadmin.browser', [
|
||||
scripts[n].push({'name': m, 'path': p, loaded: false});
|
||||
},
|
||||
masterpass_callback_queue: [],
|
||||
// Build the default layout
|
||||
buildDefaultLayout: function(docker) {
|
||||
var browserPanel = docker.addPanel('browser', wcDocker.DOCK.LEFT);
|
||||
var 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);
|
||||
},
|
||||
// Enable/disable menu options
|
||||
enable_disable_menus: function(item) {
|
||||
// Mechanism to enable/disable menus depending on the condition.
|
||||
@@ -354,35 +338,6 @@ define('pgadmin.browser', [
|
||||
$obj_mnu.append(create_submenu.$el);
|
||||
}
|
||||
},
|
||||
save_current_layout: function(layout_id, docker) {
|
||||
if(docker) {
|
||||
var layout = docker.save(),
|
||||
settings = { setting: layout_id, value: layout };
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: url_for('settings.store_bulk'),
|
||||
data: settings,
|
||||
});
|
||||
}
|
||||
},
|
||||
restore_layout: function(docker, layout, defaultLayoutCallback) {
|
||||
// Try to restore the layout if there is one
|
||||
if (layout != '') {
|
||||
try {
|
||||
docker.restore(layout);
|
||||
}
|
||||
catch(err) {
|
||||
docker.clear();
|
||||
if(defaultLayoutCallback) {
|
||||
defaultLayoutCallback(docker);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(defaultLayoutCallback) {
|
||||
defaultLayoutCallback(docker);
|
||||
}
|
||||
}
|
||||
},
|
||||
init: function() {
|
||||
var obj=this;
|
||||
if (obj.initialized) {
|
||||
@@ -799,8 +754,8 @@ define('pgadmin.browser', [
|
||||
menus = pgMenu[a];
|
||||
}
|
||||
|
||||
if (!_.has(menus, m.name)) {
|
||||
menus[m.name] = new MenuItem({
|
||||
let get_menuitem_obj = function(m) {
|
||||
return 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 || '#',
|
||||
@@ -808,8 +763,21 @@ define('pgadmin.browser', [
|
||||
enable: (m.enable == '' ? true : (_.isString(m.enable) &&
|
||||
m.enable.toLowerCase() == 'false') ?
|
||||
false : m.enable),
|
||||
node: m.node,
|
||||
node: m.node, checked: m.checked,
|
||||
});
|
||||
};
|
||||
|
||||
if (!_.has(menus, m.name)) {
|
||||
menus[m.name] = get_menuitem_obj(m);
|
||||
|
||||
if(m.menu_items) {
|
||||
let sub_menu_items = [];
|
||||
|
||||
for(let i=0; i<m.menu_items.length; i++) {
|
||||
sub_menu_items.push(get_menuitem_obj(m.menu_items[i]));
|
||||
}
|
||||
menus[m.name]['menu_items'] = sub_menu_items;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn(
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2019, 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 $ from 'jquery';
|
||||
import * as Alertify from 'pgadmin.alertifyjs';
|
||||
import gettext from 'sources/gettext';
|
||||
import 'wcdocker';
|
||||
|
||||
const pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
|
||||
|
||||
var 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) {
|
||||
var browserPanel = docker.addPanel('browser', wcDocker.DOCK.LEFT);
|
||||
var 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);
|
||||
},
|
||||
|
||||
save_current_layout: function(layout_id, docker) {
|
||||
if(docker) {
|
||||
var layout = docker.save(),
|
||||
settings = { setting: layout_id, value: layout };
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: url_for('settings.store_bulk'),
|
||||
data: settings,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
restore_layout: function(docker, layout, defaultLayoutCallback) {
|
||||
// Try to restore the layout if there is one
|
||||
if (layout != '') {
|
||||
try {
|
||||
docker.restore(layout);
|
||||
}
|
||||
catch(err) {
|
||||
docker.clear();
|
||||
if(defaultLayoutCallback) {
|
||||
defaultLayoutCallback(docker);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(defaultLayoutCallback) {
|
||||
defaultLayoutCallback(docker);
|
||||
}
|
||||
}
|
||||
|
||||
/* preference available only with top/opener browser. */
|
||||
let browser = window.opener ?
|
||||
window.opener.pgAdmin.Browser : window.top.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);
|
||||
});
|
||||
}
|
||||
}, 500);
|
||||
},
|
||||
|
||||
reflectLocklayoutChange: function(docker) {
|
||||
let browser = window.opener ?
|
||||
window.opener.pgAdmin.Browser : window.top.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 = this.menus['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;
|
||||
}
|
||||
|
||||
_.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 = window.opener ?
|
||||
window.opener.pgAdmin.Browser : window.top.pgAdmin.Browser;
|
||||
|
||||
$.ajax({
|
||||
url: url_for('browser.lock_layout'),
|
||||
method: 'PUT',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({
|
||||
'value': op,
|
||||
}),
|
||||
}).done(function() {
|
||||
browser.cache_preferences('browser');
|
||||
}).fail(function(xhr, error) {
|
||||
Alertify.pgNotifier(error, xhr, 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};
|
||||
@@ -19,6 +19,7 @@ define([
|
||||
var menu_opts = [
|
||||
'name', 'label', 'priority', 'module', 'callback', 'data', 'enable',
|
||||
'category', 'target', 'url' /* Do not show icon in the menus, 'icon' */ , 'node',
|
||||
'checked', 'menu_items',
|
||||
],
|
||||
defaults = {
|
||||
url: '#',
|
||||
@@ -55,31 +56,48 @@ define([
|
||||
* Create the jquery element for the menu-item.
|
||||
*/
|
||||
create_el: function(node, item) {
|
||||
var url = $('<a></a>', {
|
||||
'id': this.name,
|
||||
'href': this.url,
|
||||
'target': this.target,
|
||||
'data-toggle': 'pg-menu',
|
||||
}).data('pgMenu', {
|
||||
module: this.module || pgAdmin.Browser,
|
||||
cb: this.callback,
|
||||
data: this.data,
|
||||
}).addClass('dropdown-item');
|
||||
|
||||
this.is_disabled = this.disabled(node, item);
|
||||
if (this.icon) {
|
||||
url.append($('<i></i>', {
|
||||
'class': this.icon,
|
||||
}));
|
||||
if(this.menu_items) {
|
||||
_.each(this.menu_items, function(submenu_item){
|
||||
submenu_item.generate(node, item);
|
||||
});
|
||||
var create_submenu = pgAdmin.Browser.MenuGroup({
|
||||
'label': this.label,
|
||||
'id': this.name,
|
||||
}, this.menu_items);
|
||||
this.$el = create_submenu.$el;
|
||||
} else {
|
||||
var url = $('<a></a>', {
|
||||
'id': this.name,
|
||||
'href': this.url,
|
||||
'target': this.target,
|
||||
'data-toggle': 'pg-menu',
|
||||
}).data('pgMenu', {
|
||||
module: this.module || pgAdmin.Browser,
|
||||
cb: this.callback,
|
||||
data: this.data,
|
||||
}).addClass('dropdown-item');
|
||||
|
||||
this.is_disabled = this.disabled(node, item);
|
||||
if (this.icon) {
|
||||
url.append($('<i></i>', {
|
||||
'class': this.icon,
|
||||
}));
|
||||
} else if(!_.isUndefined(this.checked)) {
|
||||
url.append($('<i></i>', {
|
||||
'class': 'fa fa-check '+ (this.checked?'':'visibility-hidden'),
|
||||
}));
|
||||
}
|
||||
|
||||
url.addClass((this.is_disabled ? ' disabled' : ''));
|
||||
|
||||
var textSpan = $('<span data-test="menu-item-text"></span>').text(' ' + this.label);
|
||||
|
||||
url.append(textSpan);
|
||||
|
||||
this.$el = $('<li/>').append(url);
|
||||
}
|
||||
|
||||
url.addClass((this.is_disabled ? ' disabled' : ''));
|
||||
|
||||
var textSpan = $('<span data-test="menu-item-text"></span>').text(' ' + this.label);
|
||||
|
||||
url.append(textSpan);
|
||||
|
||||
this.$el = $('<li/>').append(url);
|
||||
},
|
||||
/*
|
||||
* Updates the enable/disable state of the menu-item based on the current
|
||||
@@ -154,6 +172,20 @@ define([
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
/*
|
||||
* Change the checked value and update the checked icon on the menu
|
||||
*/
|
||||
change_checked(isChecked) {
|
||||
if(!_.isUndefined(this.checked)) {
|
||||
this.checked = isChecked;
|
||||
if(this.checked) {
|
||||
this.$el.find('.fa-check').removeClass('visibility-hidden');
|
||||
} else {
|
||||
this.$el.find('.fa-check').addClass('visibility-hidden');
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/*
|
||||
|
||||
@@ -7,6 +7,32 @@
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
{% macro A_MENU_ITEM(key, item) -%}
|
||||
{
|
||||
name: "{{ item.name }}",
|
||||
{% if item.module %}module: {{ item.module }},
|
||||
{% endif %}{% if item.url %}url: "{{ item.url }}",
|
||||
{% endif %}{% if item.target %}target: "{{ item.target }}",
|
||||
{% endif %}{% if item.callback %}callback: "{{ item.callback }}",
|
||||
{% endif %}{% if item.category %}category: "{{ item.category }}",
|
||||
{% endif %}{% if item.icon %}icon: '{{ item.icon }}',
|
||||
{% endif %}{% if item.data %}data: {{ item.data }},
|
||||
{% endif %}label: '{{ item.label }}', applies: ['{{ key.lower() }}'],
|
||||
priority: {{ item.priority }},
|
||||
enable: '{{ item.enable }}',
|
||||
{% if item.checked is defined %}checked: {% if item.checked %}true{% else %}false{% endif %},
|
||||
{% endif %}
|
||||
{% if item.menu_items %}menu_items: {{MENU_ITEMS(key, item.menu_items)}}
|
||||
{% endif %}
|
||||
}
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro MENU_ITEMS(key, items) -%}
|
||||
[
|
||||
{% for item in items %}{% if loop.index != 1 %}, {% endif %}
|
||||
{{ A_MENU_ITEM(key, item) }}{% set hasMenus = True %}{% endfor %}
|
||||
]
|
||||
{%- endmacro %}
|
||||
|
||||
define('pgadmin.browser.utils',
|
||||
['sources/pgadmin'], function(pgAdmin) {
|
||||
@@ -56,19 +82,7 @@ define('pgadmin.browser.utils',
|
||||
var self = this;
|
||||
if (this.counter.total == this.counter.loaded) {
|
||||
{% for key in ('File', 'Edit', 'Object' 'Tools', 'Management', 'Help') %}
|
||||
obj.add_menus([{% for item in current_app.menu_items['%s_items' % key.lower()] %}{% if loop.index != 1 %}, {% endif %}{
|
||||
name: "{{ item.name }}",
|
||||
{% if item.module %}module: {{ item.module }},
|
||||
{% endif %}{% if item.url %}url: "{{ item.url }}",
|
||||
{% endif %}{% if item.target %}target: "{{ item.target }}",
|
||||
{% endif %}{% if item.callback %}callback: "{{ item.callback }}",
|
||||
{% endif %}{% if item.category %}category: "{{ item.category }}",
|
||||
{% endif %}{% if item.icon %}icon: '{{ item.icon }}',
|
||||
{% endif %}{% if item.data %}data: {{ item.data }},
|
||||
{% endif %}label: '{{ item.label }}', applies: ['{{ key.lower() }}'],
|
||||
priority: {{ item.priority }},
|
||||
enable: '{{ item.enable }}'
|
||||
}{% set hasMenus = True %}{% endfor %}]);
|
||||
obj.add_menus({{ MENU_ITEMS(key, current_app.menu_items['%s_items' % key.lower()])}});
|
||||
{% endfor %}
|
||||
obj.create_menus();
|
||||
} else {
|
||||
|
||||
+245
-212
File diff suppressed because it is too large.
Load diff
@@ -10,11 +10,13 @@
|
||||
define('pgadmin.misc.explain', [
|
||||
'sources/url_for', 'jquery', 'underscore', 'underscore.string',
|
||||
'sources/pgadmin', 'backbone', 'snapsvg', 'explain_statistics',
|
||||
'svg_downloader',
|
||||
], function(url_for, $, _, S, pgAdmin, Backbone, Snap, StatisticsModel, svgDownloader) {
|
||||
'svg_downloader', 'image_maper',
|
||||
], function(url_for, $, _, S, pgAdmin, Backbone, Snap, StatisticsModel,
|
||||
svgDownloader, imageMapper) {
|
||||
|
||||
pgAdmin = pgAdmin || window.pgAdmin || {};
|
||||
svgDownloader = svgDownloader.default;
|
||||
var pgBrowser = pgAdmin.Browser;
|
||||
|
||||
// Snap.svg plug-in to write multitext as image name
|
||||
Snap.plugin(function(Snap, Element, Paper) {
|
||||
@@ -97,279 +99,9 @@ define('pgadmin.misc.explain', [
|
||||
var pgExplain = pgAdmin.Explain = {
|
||||
// Prefix path where images are stored
|
||||
prefix: url_for('misc.index') + 'static/explain/img/',
|
||||
};
|
||||
|
||||
/*
|
||||
* A map which is used to fetch the image to be drawn and
|
||||
* text which will appear below it
|
||||
*/
|
||||
var imageMapper = {
|
||||
'Aggregate': {
|
||||
'image': 'ex_aggregate.svg',
|
||||
'image_text': 'Aggregate',
|
||||
},
|
||||
'Append': {
|
||||
'image': 'ex_append.svg',
|
||||
'image_text': 'Append',
|
||||
},
|
||||
'Bitmap Index Scan': function(data) {
|
||||
return {
|
||||
'image': 'ex_bmp_index.svg',
|
||||
'image_text': data['Index Name'],
|
||||
};
|
||||
},
|
||||
'Bitmap Heap Scan': function(data) {
|
||||
return {
|
||||
'image': 'ex_bmp_heap.svg',
|
||||
'image_text': data['Relation Name'],
|
||||
};
|
||||
},
|
||||
'BitmapAnd': {
|
||||
'image': 'ex_bmp_and.svg',
|
||||
'image_text': 'Bitmap AND',
|
||||
},
|
||||
'BitmapOr': {
|
||||
'image': 'ex_bmp_or.svg',
|
||||
'image_text': 'Bitmap OR',
|
||||
},
|
||||
'CTE Scan': {
|
||||
'image': 'ex_cte_scan.svg',
|
||||
'image_text': 'CTE Scan',
|
||||
},
|
||||
'Function Scan': {
|
||||
'image': 'ex_result.svg',
|
||||
'image_text': 'Function Scan',
|
||||
},
|
||||
'Foreign Scan': {
|
||||
'image': 'ex_foreign_scan.svg',
|
||||
'image_text': 'Foreign Scan',
|
||||
},
|
||||
'Gather': {
|
||||
'image': 'ex_gather_motion.svg',
|
||||
'image_text': 'Gather',
|
||||
},
|
||||
'Group': {
|
||||
'image': 'ex_group.svg',
|
||||
'image_text': 'Group',
|
||||
},
|
||||
'GroupAggregate': {
|
||||
'image': 'ex_aggregate.svg',
|
||||
'image_text': 'Group Aggregate',
|
||||
},
|
||||
'Hash': {
|
||||
'image': 'ex_hash.svg',
|
||||
'image_text': 'Hash',
|
||||
},
|
||||
'Hash Join': function(data) {
|
||||
if (!data['Join Type']) return {
|
||||
'image': 'ex_join.svg',
|
||||
'image_text': 'Join',
|
||||
};
|
||||
switch (data['Join Type']) {
|
||||
case 'Anti':
|
||||
return {
|
||||
'image': 'ex_hash_anti_join.svg',
|
||||
'image_text': 'Hash Anti Join',
|
||||
};
|
||||
case 'Semi':
|
||||
return {
|
||||
'image': 'ex_hash_semi_join.svg',
|
||||
'image_text': 'Hash Semi Join',
|
||||
};
|
||||
default:
|
||||
return {
|
||||
'image': 'ex_hash.svg',
|
||||
'image_text': String('Hash ' + data['Join Type'] + ' Join'),
|
||||
};
|
||||
}
|
||||
},
|
||||
'HashAggregate': {
|
||||
'image': 'ex_aggregate.svg',
|
||||
'image_text': 'Hash Aggregate',
|
||||
},
|
||||
'Index Only Scan': function(data) {
|
||||
return {
|
||||
'image': 'ex_index_only_scan.svg',
|
||||
'image_text': data['Index Name'],
|
||||
};
|
||||
},
|
||||
'Index Scan': function(data) {
|
||||
return {
|
||||
'image': 'ex_index_scan.svg',
|
||||
'image_text': data['Index Name'],
|
||||
};
|
||||
},
|
||||
'Index Scan Backword': {
|
||||
'image': 'ex_index_scan.svg',
|
||||
'image_text': 'Index Backward Scan',
|
||||
},
|
||||
'Limit': {
|
||||
'image': 'ex_limit.svg',
|
||||
'image_text': 'Limit',
|
||||
},
|
||||
'LockRows': {
|
||||
'image': 'ex_lock_rows.svg',
|
||||
'image_text': 'Lock Rows',
|
||||
},
|
||||
'Materialize': {
|
||||
'image': 'ex_materialize.svg',
|
||||
'image_text': 'Materialize',
|
||||
},
|
||||
'Merge Append': {
|
||||
'image': 'ex_merge_append.svg',
|
||||
'image_text': 'Merge Append',
|
||||
},
|
||||
'Merge Join': function(data) {
|
||||
switch (data['Join Type']) {
|
||||
case 'Anti':
|
||||
return {
|
||||
'image': 'ex_merge_anti_join.svg',
|
||||
'image_text': 'Merge Anti Join',
|
||||
};
|
||||
case 'Semi':
|
||||
return {
|
||||
'image': 'ex_merge_semi_join.svg',
|
||||
'image_text': 'Merge Semi Join',
|
||||
};
|
||||
default:
|
||||
return {
|
||||
'image': 'ex_merge.svg',
|
||||
'image_text': String('Merge ' + data['Join Type'] + ' Join'),
|
||||
};
|
||||
}
|
||||
},
|
||||
'ModifyTable': function(data) {
|
||||
switch (data['Operation']) {
|
||||
case 'Insert':
|
||||
return {
|
||||
'image': 'ex_insert.svg',
|
||||
'image_text': 'Insert',
|
||||
};
|
||||
case 'Update':
|
||||
return {
|
||||
'image': 'ex_update.svg',
|
||||
'image_text': 'Update',
|
||||
};
|
||||
case 'Delete':
|
||||
return {
|
||||
'image': 'ex_delete.svg',
|
||||
'image_text': 'Delete',
|
||||
};
|
||||
}
|
||||
},
|
||||
'Nested Loop': function(data) {
|
||||
switch (data['Join Type']) {
|
||||
case 'Anti':
|
||||
return {
|
||||
'image': 'ex_nested_loop_anti_join.svg',
|
||||
'image_text': 'Nested Loop Anti Join',
|
||||
};
|
||||
case 'Semi':
|
||||
return {
|
||||
'image': 'ex_nested_loop_semi_join.svg',
|
||||
'image_text': 'Nested Loop Semi Join',
|
||||
};
|
||||
default:
|
||||
return {
|
||||
'image': 'ex_nested.svg',
|
||||
'image_text': 'Nested Loop ' + data['Join Type'] + ' Join',
|
||||
};
|
||||
}
|
||||
},
|
||||
'Recursive Union': {
|
||||
'image': 'ex_recursive_union.svg',
|
||||
'image_text': 'Recursive Union',
|
||||
},
|
||||
'Result': {
|
||||
'image': 'ex_result.svg',
|
||||
'image_text': 'Result',
|
||||
},
|
||||
'Sample Scan': {
|
||||
'image': 'ex_scan.svg',
|
||||
'image_text': 'Sample Scan',
|
||||
},
|
||||
'Scan': {
|
||||
'image': 'ex_scan.svg',
|
||||
'image_text': 'Scan',
|
||||
},
|
||||
'Seek': {
|
||||
'image': 'ex_seek.svg',
|
||||
'image_text': 'Seek',
|
||||
},
|
||||
'SetOp': function(data) {
|
||||
var strategy = data['Strategy'],
|
||||
command = data['Command'];
|
||||
|
||||
if (strategy == 'Hashed') {
|
||||
if (S.startsWith(command, 'Intersect')) {
|
||||
if (command == 'Intersect All')
|
||||
return {
|
||||
'image': 'ex_hash_setop_intersect_all.svg',
|
||||
'image_text': 'Hashed Intersect All',
|
||||
};
|
||||
return {
|
||||
'image': 'ex_hash_setop_intersect.svg',
|
||||
'image_text': 'Hashed Intersect',
|
||||
};
|
||||
} else if (S.startsWith(command, 'Except')) {
|
||||
if (command == 'Except All')
|
||||
return {
|
||||
'image': 'ex_hash_setop_except_all.svg',
|
||||
'image_text': 'Hashed Except All',
|
||||
};
|
||||
return {
|
||||
'image': 'ex_hash_setop_except.svg',
|
||||
'image_text': 'Hash Except',
|
||||
};
|
||||
}
|
||||
return {
|
||||
'image': 'ex_hash_setop_unknown.svg',
|
||||
'image_text': 'Hashed SetOp Unknown',
|
||||
};
|
||||
}
|
||||
return {
|
||||
'image': 'ex_setop.svg',
|
||||
'image_text': 'SetOp',
|
||||
};
|
||||
},
|
||||
'Seq Scan': function(data) {
|
||||
return {
|
||||
'image': 'ex_scan.svg',
|
||||
'image_text': data['Relation Name'],
|
||||
};
|
||||
},
|
||||
'Subquery Scan': {
|
||||
'image': 'ex_subplan.svg',
|
||||
'image_text': 'SubQuery Scan',
|
||||
},
|
||||
'Sort': {
|
||||
'image': 'ex_sort.svg',
|
||||
'image_text': 'Sort',
|
||||
},
|
||||
'Tid Scan': {
|
||||
'image': 'ex_tid_scan.svg',
|
||||
'image_text': 'Tid Scan',
|
||||
},
|
||||
'Unique': {
|
||||
'image': 'ex_unique.svg',
|
||||
'image_text': 'Unique',
|
||||
},
|
||||
'Values Scan': {
|
||||
'image': 'ex_values_scan.svg',
|
||||
'image_text': 'Values Scan',
|
||||
},
|
||||
'WindowAgg': {
|
||||
'image': 'ex_window_aggregate.svg',
|
||||
'image_text': 'Window Aggregate',
|
||||
},
|
||||
'WorkTable Scan': {
|
||||
'image': 'ex_worktable_scan.svg',
|
||||
'image_text': 'WorkTable Scan',
|
||||
},
|
||||
'Undefined': {
|
||||
'image': 'ex_unknown.svg',
|
||||
'image_text': 'Undefined',
|
||||
},
|
||||
totalNodes: 0,
|
||||
totalDownloadedNodes: 0,
|
||||
isDownloaded: false,
|
||||
};
|
||||
|
||||
// Some predefined constants used to calculate image location and its border
|
||||
@@ -433,9 +165,10 @@ define('pgadmin.misc.explain', [
|
||||
node_type = node_type.substring(0, 7);
|
||||
|
||||
// Get the image information for current node
|
||||
var mappedImage = (_.isFunction(imageMapper[node_type]) &&
|
||||
imageMapper[node_type].apply(undefined, [data])) ||
|
||||
imageMapper[node_type] || {
|
||||
let imageStore = imageMapper.default;
|
||||
var mappedImage = (_.isFunction(imageStore[node_type]) &&
|
||||
imageStore[node_type].apply(undefined, [data])) ||
|
||||
imageStore[node_type] || {
|
||||
'image': 'ex_unknown.svg',
|
||||
'image_text': node_type,
|
||||
};
|
||||
@@ -576,34 +309,7 @@ define('pgadmin.misc.explain', [
|
||||
});
|
||||
}
|
||||
|
||||
/* Check the current browser, if it is Internet Explorer then we will not
|
||||
* embed the SVG files for download feature as we are not bale to figure
|
||||
* out the solution for IE.
|
||||
*/
|
||||
var current_browser = pgAdmin.Browser.get_browser();
|
||||
if (current_browser.name === 'IE' ||
|
||||
(current_browser.name === 'Safari' && parseInt(current_browser.version) < 10)) {
|
||||
this.draw_image(g, pgExplain.prefix + this.get('image'), currentXpos, currentYpos, graphContainer, toolTipContainer);
|
||||
} else {
|
||||
/* This function is a callback function called when we load any svg file
|
||||
* using Snap. In this function we append the SVG binary data to the new
|
||||
* temporary Snap object and then embedded it to the original Snap() object.
|
||||
*/
|
||||
var that = this;
|
||||
var onSVGLoaded = function(data) {
|
||||
var svg_image = Snap();
|
||||
svg_image.append(data);
|
||||
|
||||
that.draw_image(g, svg_image.toDataURL(), currentXpos, currentYpos, graphContainer, toolTipContainer);
|
||||
|
||||
// This attribute is required to download the file as SVG image.
|
||||
s.parent().attr({'xmlns:xlink':'http://www.w3.org/1999/xlink'});
|
||||
};
|
||||
|
||||
var svg_file = pgExplain.prefix + this.get('image');
|
||||
// Load the SVG file for explain plan
|
||||
Snap.load(svg_file, onSVGLoaded);
|
||||
}
|
||||
this.draw_image(g, pgExplain.prefix + this.get('image'), currentXpos, currentYpos, graphContainer, toolTipContainer);
|
||||
|
||||
// Draw text below the node
|
||||
var node_label = this.get('Schema') == undefined ?
|
||||
@@ -760,6 +466,224 @@ define('pgadmin.misc.explain', [
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* NOTE: embedding using .toDataURL() method hits the performance of the
|
||||
* plan rendering a lot, that is why we have written seprate Model for the same
|
||||
* which is used only when downloading of SVG is called
|
||||
*/
|
||||
// We override the PlanModel's draw() function so that we can embbed all the
|
||||
// svg in to main one SVG so that we can download it.
|
||||
let DownloadPlanModel = PlanModel.extend({
|
||||
// Draw image, its name and its tooltip
|
||||
parse: function(data) {
|
||||
var idx = 1,
|
||||
lvl = data.level = data.level || [idx],
|
||||
plans = [],
|
||||
node_type = data['Node Type'],
|
||||
// Calculating relative xpos of current node from top node
|
||||
xpos = data.xpos = data.xpos - pWIDTH,
|
||||
// Calculating relative ypos of current node from top node
|
||||
ypos = data.ypos,
|
||||
maxChildWidth = 0;
|
||||
|
||||
data['width'] = pWIDTH;
|
||||
data['height'] = pHEIGHT;
|
||||
|
||||
/*
|
||||
* calculating xpos, ypos, width and height if current node is a subplan
|
||||
*/
|
||||
if (data['Parent Relationship'] === 'SubPlan') {
|
||||
data['width'] += (xMargin * 2) + (xMargin / 2);
|
||||
data['height'] += (yMargin * 2);
|
||||
data['ypos'] += yMargin;
|
||||
xpos -= xMargin;
|
||||
ypos += yMargin;
|
||||
}
|
||||
|
||||
if (S.startsWith(node_type, '(slice'))
|
||||
node_type = node_type.substring(0, 7);
|
||||
// Get the image information for current node
|
||||
let imageStore = imageMapper.default;
|
||||
var mappedImage = (_.isFunction(imageStore[node_type]) &&
|
||||
imageStore[node_type].apply(undefined, [data])) ||
|
||||
imageStore[node_type] || {
|
||||
'image': 'ex_unknown.svg',
|
||||
'image_text': node_type,
|
||||
};
|
||||
|
||||
data['image'] = mappedImage['image'];
|
||||
data['image_text'] = mappedImage['image_text'];
|
||||
pgExplain.totalNodes++;
|
||||
|
||||
// Start calculating xpos, ypos, width and height for child plans if any
|
||||
if ('Plans' in data) {
|
||||
|
||||
data['width'] += offsetX;
|
||||
|
||||
_.each(data['Plans'], function(p) {
|
||||
var level = _.clone(lvl),
|
||||
plan = new DownloadPlanModel({ 'parse': true });
|
||||
|
||||
level.push(idx);
|
||||
plan.set(plan.parse(_.extend(
|
||||
p, {
|
||||
'level': level,
|
||||
xpos: xpos - offsetX,
|
||||
ypos: ypos,
|
||||
})));
|
||||
|
||||
if (maxChildWidth < plan.get('width')) {
|
||||
maxChildWidth = plan.get('width');
|
||||
}
|
||||
|
||||
var childHeight = plan.get('height');
|
||||
|
||||
if (idx !== 1) {
|
||||
data['height'] = data['height'] + childHeight + offsetY;
|
||||
} else if (childHeight > data['height']) {
|
||||
data['height'] = childHeight;
|
||||
}
|
||||
ypos += childHeight + offsetY;
|
||||
|
||||
plans.push(plan);
|
||||
idx++;
|
||||
});
|
||||
}
|
||||
|
||||
// Final Width and Height of current node
|
||||
data['width'] += maxChildWidth;
|
||||
data['Plans'] = plans;
|
||||
|
||||
return data;
|
||||
},
|
||||
draw: function(s, xpos, ypos, pXpos, pYpos, graphContainer, toolTipContainer) {
|
||||
var g = s.g();
|
||||
var currentXpos = xpos + this.get('xpos'),
|
||||
currentYpos = ypos + this.get('ypos'),
|
||||
isSubPlan = (this.get('Parent Relationship') === 'SubPlan');
|
||||
|
||||
// Draw the subplan rectangle
|
||||
if (isSubPlan) {
|
||||
g.rect(
|
||||
currentXpos - this.get('width') + pWIDTH + xMargin,
|
||||
currentYpos - this.get('height') + pHEIGHT + yMargin - TXT_ALIGN,
|
||||
this.get('width') - xMargin,
|
||||
this.get('height') + (currentYpos - yMargin),
|
||||
5
|
||||
).attr({
|
||||
stroke: '#444444',
|
||||
'strokeWidth': 1.2,
|
||||
fill: 'gray',
|
||||
fillOpacity: 0.2,
|
||||
});
|
||||
|
||||
// Provide subplan name
|
||||
g.text(
|
||||
currentXpos + pWIDTH - (this.get('width') / 2) - xMargin,
|
||||
currentYpos + pHEIGHT - (this.get('height') / 2) - yMargin,
|
||||
this.get('Subplan Name')
|
||||
).attr({
|
||||
fontSize: TXT_SIZE,
|
||||
'text-anchor': 'start',
|
||||
fill: 'red',
|
||||
});
|
||||
}
|
||||
|
||||
/* Check the current browser, if it is Internet Explorer then we will not
|
||||
* embed the SVG files for download feature as we are not bale to figure
|
||||
* out the solution for IE.
|
||||
*/
|
||||
|
||||
var current_browser = pgAdmin.Browser.get_browser();
|
||||
if (current_browser.name === 'IE' ||
|
||||
(current_browser.name === 'Safari' && parseInt(current_browser.version) < 10)) {
|
||||
this.draw_image(g, pgExplain.prefix + this.get('image'), currentXpos, currentYpos, graphContainer, toolTipContainer);
|
||||
} else {
|
||||
/* This function is a callback function called when we load any svg file
|
||||
* using Snap. In this function we append the SVG binary data to the new
|
||||
* temporary Snap object and then embedded it to the original Snap() object.
|
||||
*/
|
||||
var that = this;
|
||||
var onSVGLoaded = function(data) {
|
||||
var svg_image = Snap();
|
||||
svg_image.append(data);
|
||||
|
||||
that.draw_image(g, svg_image.toDataURL(), currentXpos, currentYpos, graphContainer, toolTipContainer);
|
||||
pgExplain.totalDownloadedNodes++;
|
||||
|
||||
// This attribute is required to download the file as SVG image.
|
||||
s.parent().attr({'xmlns:xlink':'http://www.w3.org/1999/xlink'});
|
||||
setTimeout(() => {
|
||||
pgBrowser.Events.trigger('pga:explain_plan:node_icon:fetched');
|
||||
}, 100);
|
||||
};
|
||||
|
||||
var svg_file = pgExplain.prefix + this.get('image');
|
||||
// Load the SVG file for explain plan
|
||||
Snap.load(svg_file, onSVGLoaded);
|
||||
}
|
||||
|
||||
// Draw text below the node
|
||||
var node_label = this.get('Schema') == undefined ?
|
||||
this.get('image_text') :
|
||||
(this.get('Schema') + '.' + this.get('image_text'));
|
||||
g.multitext(
|
||||
currentXpos + (pWIDTH / 2) + TXT_ALIGN,
|
||||
currentYpos + pHEIGHT - TXT_ALIGN,
|
||||
node_label,
|
||||
150, {
|
||||
'font-size': TXT_SIZE,
|
||||
'text-anchor': 'middle',
|
||||
}
|
||||
);
|
||||
|
||||
// Draw Arrow to parent only its not the first node
|
||||
if (!_.isUndefined(pYpos)) {
|
||||
var startx = currentXpos + pWIDTH;
|
||||
var starty = currentYpos + (pHEIGHT / 2);
|
||||
var endx = pXpos - ARROW_WIDTH;
|
||||
var endy = pYpos + (pHEIGHT / 2);
|
||||
var start_cost = this.get('Startup Cost'),
|
||||
total_cost = this.get('Total Cost');
|
||||
var arrow_size = DEFAULT_ARROW_SIZE;
|
||||
|
||||
// Calculate arrow width according to cost of a particular plan
|
||||
if (start_cost != undefined && total_cost != undefined) {
|
||||
arrow_size = Math.round(Math.log((start_cost + total_cost) / 2 + start_cost));
|
||||
arrow_size = arrow_size < 1 ? 1 : arrow_size > 10 ? 10 : arrow_size;
|
||||
}
|
||||
|
||||
var arrow_view_box = [0, 0, 2 * ARROW_WIDTH, 2 * ARROW_HEIGHT];
|
||||
var opts = {
|
||||
stroke: '#000000',
|
||||
strokeWidth: arrow_size + 2,
|
||||
},
|
||||
subplanOpts = {
|
||||
stroke: '#866486',
|
||||
strokeWidth: arrow_size + 2,
|
||||
},
|
||||
arrowOpts = {
|
||||
viewBox: arrow_view_box.join(' '),
|
||||
};
|
||||
|
||||
// Draw an arrow from current node to its parent
|
||||
this.drawPolyLine(
|
||||
g, startx, starty, endx, endy,
|
||||
isSubPlan ? subplanOpts : opts, arrowOpts
|
||||
);
|
||||
}
|
||||
|
||||
var plans = this.get('Plans');
|
||||
|
||||
// Draw nodes for current plan's children
|
||||
_.each(plans, function(p) {
|
||||
p.draw(s, xpos, ypos, currentXpos, currentYpos, graphContainer, toolTipContainer);
|
||||
});
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
// Main backbone model to store JSON object
|
||||
var MainPlanModel = Backbone.Model.extend({
|
||||
defaults: {
|
||||
@@ -840,8 +764,12 @@ define('pgadmin.misc.explain', [
|
||||
// Parse and draw full graphical explain
|
||||
_.extend(pgExplain, {
|
||||
// Assumption container is a jQuery object
|
||||
DrawJSONPlan: function(container, plan) {
|
||||
DrawJSONPlan: function(container, plan, isDownload) {
|
||||
pgExplain.totalNodes = 0;
|
||||
pgExplain.totalDownloadedNodes = 0;
|
||||
pgExplain.isDownloaded = false;
|
||||
container.empty();
|
||||
var orignalPlan = $.extend(true, [], plan);
|
||||
var curr_zoom_factor = 1.0;
|
||||
|
||||
var zoomArea = $('<div></div>', {
|
||||
@@ -931,7 +859,20 @@ define('pgadmin.misc.explain', [
|
||||
h = yMargin;
|
||||
|
||||
_.each(plan, function(p) {
|
||||
var main_plan = new MainPlanModel();
|
||||
var main_plan;
|
||||
if(isDownload) {
|
||||
// If user opt to download then we will use the DownloadPlanModel model
|
||||
// so that it will embed the images while regenrating the plan
|
||||
let DownloadMainPlanModel = MainPlanModel.extend({
|
||||
initialize: function() {
|
||||
this.set('Plan', new DownloadPlanModel({ parse: true }));
|
||||
this.set('Statistics', new StatisticsModel());
|
||||
},
|
||||
});
|
||||
main_plan = new DownloadMainPlanModel({ 'parse': true });
|
||||
} else {
|
||||
main_plan = new MainPlanModel();
|
||||
}
|
||||
|
||||
// Parse JSON data to backbone model
|
||||
main_plan.set(main_plan.parse(p));
|
||||
@@ -1018,14 +959,20 @@ define('pgadmin.misc.explain', [
|
||||
});
|
||||
|
||||
downloadBtn.on('click', function() {
|
||||
var s = Snap('.pgadmin-explain-container svg');
|
||||
var today = new Date();
|
||||
var filename = 'explain_plan_' + today.getTime() + '.svg';
|
||||
svgDownloader.downloadSVG(s.toString(), filename);
|
||||
downloadBtn.trigger('blur');
|
||||
// Lets regenrate the plan with embedded images
|
||||
pgExplain.DrawJSONPlan(container, orignalPlan, true);
|
||||
pgBrowser.Events.on('pga:explain_plan:node_icon:fetched', function() {
|
||||
if (!pgExplain.isDownloaded && pgExplain.totalNodes === pgExplain.totalDownloadedNodes) {
|
||||
pgExplain.isDownloaded = true;
|
||||
var s = Snap('.pgadmin-explain-container svg');
|
||||
var today = new Date();
|
||||
var filename = 'explain_plan_' + today.getTime() + '.svg';
|
||||
svgDownloader.downloadSVG(s.toString(), filename);
|
||||
downloadBtn.trigger('blur');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2019, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import S from 'underscore.string';
|
||||
/*
|
||||
* A map which is used to fetch the image to be drawn and
|
||||
* text which will appear below it
|
||||
*/
|
||||
|
||||
let imageMapper = {
|
||||
'Aggregate': {
|
||||
'image': 'ex_aggregate.svg',
|
||||
'image_text': 'Aggregate',
|
||||
},
|
||||
'Append': {
|
||||
'image': 'ex_append.svg',
|
||||
'image_text': 'Append',
|
||||
},
|
||||
'Bitmap Index Scan': function(data) {
|
||||
return {
|
||||
'image': 'ex_bmp_index.svg',
|
||||
'image_text': data['Index Name'],
|
||||
};
|
||||
},
|
||||
'Bitmap Heap Scan': function(data) {
|
||||
return {
|
||||
'image': 'ex_bmp_heap.svg',
|
||||
'image_text': data['Relation Name'],
|
||||
};
|
||||
},
|
||||
'BitmapAnd': {
|
||||
'image': 'ex_bmp_and.svg',
|
||||
'image_text': 'Bitmap AND',
|
||||
},
|
||||
'BitmapOr': {
|
||||
'image': 'ex_bmp_or.svg',
|
||||
'image_text': 'Bitmap OR',
|
||||
},
|
||||
'CTE Scan': {
|
||||
'image': 'ex_cte_scan.svg',
|
||||
'image_text': 'CTE Scan',
|
||||
},
|
||||
'Function Scan': {
|
||||
'image': 'ex_result.svg',
|
||||
'image_text': 'Function Scan',
|
||||
},
|
||||
'Foreign Scan': {
|
||||
'image': 'ex_foreign_scan.svg',
|
||||
'image_text': 'Foreign Scan',
|
||||
},
|
||||
'Gather': {
|
||||
'image': 'ex_gather_motion.svg',
|
||||
'image_text': 'Gather',
|
||||
},
|
||||
'Group': {
|
||||
'image': 'ex_group.svg',
|
||||
'image_text': 'Group',
|
||||
},
|
||||
'GroupAggregate': {
|
||||
'image': 'ex_aggregate.svg',
|
||||
'image_text': 'Group Aggregate',
|
||||
},
|
||||
'Hash': {
|
||||
'image': 'ex_hash.svg',
|
||||
'image_text': 'Hash',
|
||||
},
|
||||
'Hash Join': function(data) {
|
||||
if (!data['Join Type']) return {
|
||||
'image': 'ex_join.svg',
|
||||
'image_text': 'Join',
|
||||
};
|
||||
switch (data['Join Type']) {
|
||||
case 'Anti':
|
||||
return {
|
||||
'image': 'ex_hash_anti_join.svg',
|
||||
'image_text': 'Hash Anti Join',
|
||||
};
|
||||
case 'Semi':
|
||||
return {
|
||||
'image': 'ex_hash_semi_join.svg',
|
||||
'image_text': 'Hash Semi Join',
|
||||
};
|
||||
default:
|
||||
return {
|
||||
'image': 'ex_hash.svg',
|
||||
'image_text': String('Hash ' + data['Join Type'] + ' Join'),
|
||||
};
|
||||
}
|
||||
},
|
||||
'HashAggregate': {
|
||||
'image': 'ex_aggregate.svg',
|
||||
'image_text': 'Hash Aggregate',
|
||||
},
|
||||
'Index Only Scan': function(data) {
|
||||
return {
|
||||
'image': 'ex_index_only_scan.svg',
|
||||
'image_text': data['Index Name'],
|
||||
};
|
||||
},
|
||||
'Index Scan': function(data) {
|
||||
return {
|
||||
'image': 'ex_index_scan.svg',
|
||||
'image_text': data['Index Name'],
|
||||
};
|
||||
},
|
||||
'Index Scan Backword': {
|
||||
'image': 'ex_index_scan.svg',
|
||||
'image_text': 'Index Backward Scan',
|
||||
},
|
||||
'Limit': {
|
||||
'image': 'ex_limit.svg',
|
||||
'image_text': 'Limit',
|
||||
},
|
||||
'LockRows': {
|
||||
'image': 'ex_lock_rows.svg',
|
||||
'image_text': 'Lock Rows',
|
||||
},
|
||||
'Materialize': {
|
||||
'image': 'ex_materialize.svg',
|
||||
'image_text': 'Materialize',
|
||||
},
|
||||
'Merge Append': {
|
||||
'image': 'ex_merge_append.svg',
|
||||
'image_text': 'Merge Append',
|
||||
},
|
||||
'Merge Join': function(data) {
|
||||
switch (data['Join Type']) {
|
||||
case 'Anti':
|
||||
return {
|
||||
'image': 'ex_merge_anti_join.svg',
|
||||
'image_text': 'Merge Anti Join',
|
||||
};
|
||||
case 'Semi':
|
||||
return {
|
||||
'image': 'ex_merge_semi_join.svg',
|
||||
'image_text': 'Merge Semi Join',
|
||||
};
|
||||
default:
|
||||
return {
|
||||
'image': 'ex_merge.svg',
|
||||
'image_text': String('Merge ' + data['Join Type'] + ' Join'),
|
||||
};
|
||||
}
|
||||
},
|
||||
'ModifyTable': function(data) {
|
||||
switch (data['Operation']) {
|
||||
case 'Insert':
|
||||
return {
|
||||
'image': 'ex_insert.svg',
|
||||
'image_text': 'Insert',
|
||||
};
|
||||
case 'Update':
|
||||
return {
|
||||
'image': 'ex_update.svg',
|
||||
'image_text': 'Update',
|
||||
};
|
||||
case 'Delete':
|
||||
return {
|
||||
'image': 'ex_delete.svg',
|
||||
'image_text': 'Delete',
|
||||
};
|
||||
}
|
||||
},
|
||||
'Nested Loop': function(data) {
|
||||
switch (data['Join Type']) {
|
||||
case 'Anti':
|
||||
return {
|
||||
'image': 'ex_nested_loop_anti_join.svg',
|
||||
'image_text': 'Nested Loop Anti Join',
|
||||
};
|
||||
case 'Semi':
|
||||
return {
|
||||
'image': 'ex_nested_loop_semi_join.svg',
|
||||
'image_text': 'Nested Loop Semi Join',
|
||||
};
|
||||
default:
|
||||
return {
|
||||
'image': 'ex_nested.svg',
|
||||
'image_text': 'Nested Loop ' + data['Join Type'] + ' Join',
|
||||
};
|
||||
}
|
||||
},
|
||||
'Recursive Union': {
|
||||
'image': 'ex_recursive_union.svg',
|
||||
'image_text': 'Recursive Union',
|
||||
},
|
||||
'Result': {
|
||||
'image': 'ex_result.svg',
|
||||
'image_text': 'Result',
|
||||
},
|
||||
'Sample Scan': {
|
||||
'image': 'ex_scan.svg',
|
||||
'image_text': 'Sample Scan',
|
||||
},
|
||||
'Scan': {
|
||||
'image': 'ex_scan.svg',
|
||||
'image_text': 'Scan',
|
||||
},
|
||||
'Seek': {
|
||||
'image': 'ex_seek.svg',
|
||||
'image_text': 'Seek',
|
||||
},
|
||||
'SetOp': function(data) {
|
||||
let strategy = data['Strategy'],
|
||||
command = data['Command'];
|
||||
|
||||
if (strategy == 'Hashed') {
|
||||
if (S.startsWith(command, 'Intersect')) {
|
||||
if (command == 'Intersect All')
|
||||
return {
|
||||
'image': 'ex_hash_setop_intersect_all.svg',
|
||||
'image_text': 'Hashed Intersect All',
|
||||
};
|
||||
return {
|
||||
'image': 'ex_hash_setop_intersect.svg',
|
||||
'image_text': 'Hashed Intersect',
|
||||
};
|
||||
} else if (S.startsWith(command, 'Except')) {
|
||||
if (command == 'Except All')
|
||||
return {
|
||||
'image': 'ex_hash_setop_except_all.svg',
|
||||
'image_text': 'Hashed Except All',
|
||||
};
|
||||
return {
|
||||
'image': 'ex_hash_setop_except.svg',
|
||||
'image_text': 'Hash Except',
|
||||
};
|
||||
}
|
||||
return {
|
||||
'image': 'ex_hash_setop_unknown.svg',
|
||||
'image_text': 'Hashed SetOp Unknown',
|
||||
};
|
||||
}
|
||||
return {
|
||||
'image': 'ex_setop.svg',
|
||||
'image_text': 'SetOp',
|
||||
};
|
||||
},
|
||||
'Seq Scan': function(data) {
|
||||
return {
|
||||
'image': 'ex_scan.svg',
|
||||
'image_text': data['Relation Name'],
|
||||
};
|
||||
},
|
||||
'Subquery Scan': {
|
||||
'image': 'ex_subplan.svg',
|
||||
'image_text': 'SubQuery Scan',
|
||||
},
|
||||
'Sort': {
|
||||
'image': 'ex_sort.svg',
|
||||
'image_text': 'Sort',
|
||||
},
|
||||
'Tid Scan': {
|
||||
'image': 'ex_tid_scan.svg',
|
||||
'image_text': 'Tid Scan',
|
||||
},
|
||||
'Unique': {
|
||||
'image': 'ex_unique.svg',
|
||||
'image_text': 'Unique',
|
||||
},
|
||||
'Values Scan': {
|
||||
'image': 'ex_values_scan.svg',
|
||||
'image_text': 'Values Scan',
|
||||
},
|
||||
'WindowAgg': {
|
||||
'image': 'ex_window_aggregate.svg',
|
||||
'image_text': 'Window Aggregate',
|
||||
},
|
||||
'WorkTable Scan': {
|
||||
'image': 'ex_worktable_scan.svg',
|
||||
'image_text': 'WorkTable Scan',
|
||||
},
|
||||
'Undefined': {
|
||||
'image': 'ex_unknown.svg',
|
||||
'image_text': 'Undefined',
|
||||
},
|
||||
};
|
||||
|
||||
export default imageMapper;
|
||||
@@ -48,7 +48,7 @@ class PreferencesModule(PgAdminModule):
|
||||
return {
|
||||
'file_items': [
|
||||
MenuItem(name='mnu_preferences',
|
||||
priority=999,
|
||||
priority=997,
|
||||
module="pgAdmin.Preferences",
|
||||
callback='show',
|
||||
icon='fa fa-cog',
|
||||
|
||||
@@ -271,6 +271,8 @@ define('pgadmin.preferences', [
|
||||
return 'switch';
|
||||
case 'keyboardshortcut':
|
||||
return 'keyboardShortcut';
|
||||
case 'radioModern':
|
||||
return 'radioModern';
|
||||
default:
|
||||
if (console && console.warn) {
|
||||
// Warning for developer only.
|
||||
|
||||
@@ -39,7 +39,7 @@ class SettingsModule(PgAdminModule):
|
||||
'file_items': [
|
||||
MenuItem(
|
||||
name='mnu_resetlayout',
|
||||
priority=999,
|
||||
priority=998,
|
||||
module="pgAdmin.Settings",
|
||||
callback='show',
|
||||
icon='fa fa-retweet',
|
||||
|
||||
@@ -60,6 +60,7 @@ define([
|
||||
'select2': 'select2',
|
||||
'note': 'note',
|
||||
'color': 'color',
|
||||
'radioModern': 'radioModern',
|
||||
};
|
||||
|
||||
Backform.getMappedControl = function(type, mode) {
|
||||
@@ -95,6 +96,17 @@ define([
|
||||
return type;
|
||||
};
|
||||
|
||||
/* Returns raw data as it is */
|
||||
var RawFormatter = Backform.RawFormatter = function() {};
|
||||
_.extend(RawFormatter.prototype, {
|
||||
fromRaw: function(rawData) {
|
||||
return rawData;
|
||||
},
|
||||
toRaw: function(formattedData) {
|
||||
return formattedData;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
var BackformControlInit = Backform.Control.prototype.initialize,
|
||||
BackformControlRemove = Backform.Control.prototype.remove;
|
||||
@@ -422,6 +434,48 @@ define([
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Backform.RadioModernControl = Backform.RadioControl.extend({
|
||||
defaults: {
|
||||
controlLabelClassName: Backform.controlLabelClassName,
|
||||
controlsClassName: Backform.controlsClassName,
|
||||
extraClasses: [],
|
||||
helpMessage: '',
|
||||
name: '',
|
||||
},
|
||||
template: _.template([
|
||||
'<label class="<%=controlLabelClassName%>"><%=label%></label>',
|
||||
'<div class="<%=controlsClassName%> <%=extraClasses.join(\' \')%>">',
|
||||
' <div class="btn-group pgadmin-controls-radio-none" data-toggle="buttons">',
|
||||
' <% for (var i=0; i < options.length; i++) { %>',
|
||||
' <% var option = options[i]; %>',
|
||||
' <label class="btn btn-primary<% if (option.value == value) { %> active<%}%>" tabindex="0">',
|
||||
' <input type="radio" name="<%=name%>" autocomplete="off" value=<%-formatter.fromRaw(option.value)%> <% if (option.value == value) { %> checked<%}%> > <%-option.label%>',
|
||||
' </label>',
|
||||
' <% } %>',
|
||||
' </div>',
|
||||
' <% if (helpMessage && helpMessage.length) { %>',
|
||||
' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
|
||||
' <% } %>',
|
||||
'</div>',
|
||||
].join('\n')),
|
||||
formatter: RawFormatter,
|
||||
getValueFromDOM: function() {
|
||||
return this.formatter.toRaw(this.$el.find('input[type="radio"]:checked').attr('value'), this.model);
|
||||
},
|
||||
render: function() {
|
||||
Backform.RadioControl.prototype.render.apply(this, arguments);
|
||||
this.$el.find('.btn').on('keyup', (e)=>{
|
||||
switch(e.keyCode) {
|
||||
case 32: /* Spacebar click */
|
||||
$(e.currentTarget).trigger('click');
|
||||
break;
|
||||
}
|
||||
});
|
||||
return this;
|
||||
},
|
||||
});
|
||||
|
||||
// Requires the Bootstrap Switch to work.
|
||||
Backform.SwitchControl = Backform.InputControl.extend({
|
||||
defaults: {
|
||||
|
||||
@@ -310,3 +310,8 @@ td.switch-cell > div.toggle {
|
||||
.btn.disabled, .btn:disabled, .btn[disabled] {
|
||||
opacity: $btn-disabled-opacity;
|
||||
}
|
||||
|
||||
.btn-group label.btn.btn-primary.active {
|
||||
background-color: $color-primary-light;
|
||||
color: $color-primary;
|
||||
}
|
||||
@@ -51,8 +51,11 @@ define([
|
||||
id: 'op',
|
||||
label: gettext('Maintenance operation'),
|
||||
cell: 'string',
|
||||
type: 'text',
|
||||
type: 'radioModern',
|
||||
controlsClassName: 'pgadmin-controls col-12 col-sm-8',
|
||||
controlLabelClassName: 'control-label col-sm-4 col-12',
|
||||
group: gettext('Options'),
|
||||
value: 'VACUUM',
|
||||
options: [{
|
||||
'label': 'VACUUM',
|
||||
'value': 'VACUUM',
|
||||
@@ -70,30 +73,6 @@ define([
|
||||
'value': 'CLUSTER',
|
||||
},
|
||||
],
|
||||
control: Backform.RadioControl.extend({
|
||||
template: _.template([
|
||||
'<label class="control-label col-sm-4 col-12"><%=label%></label>',
|
||||
'<div class="pgadmin-controls col-12 col-sm-8 btn-group pg-maintenance-op pgadmin-controls-radio-none" data-toggle="buttons">',
|
||||
' <% for (var i=0; i < options.length; i++) { %>',
|
||||
' <% var option = options[i]; %>',
|
||||
' <label class="btn btn-primary<% if (i == 0) { %> active<%}%>" tabindex="0">',
|
||||
' <input type="radio" name="op" id="op" autocomplete="off" value=<%-formatter.fromRaw(option.value)%><% if (i == 0) { %> selected<%}%> > <%-option.label%>',
|
||||
' </label>',
|
||||
' <% } %>',
|
||||
'</div>',
|
||||
].join('\n')),
|
||||
render: function() {
|
||||
Backform.RadioControl.prototype.render.apply(this, arguments);
|
||||
this.$el.find('.pg-maintenance-op .btn').on('keyup', (e)=>{
|
||||
switch(e.keyCode) {
|
||||
case 32: /* Spacebar click */
|
||||
$(e.currentTarget).trigger('click');
|
||||
break;
|
||||
}
|
||||
});
|
||||
return this;
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
type: 'nested',
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
.btn-group.pg-maintenance-op label.btn.btn-primary.active {
|
||||
background-color: $color-primary-light;
|
||||
color: $color-primary;
|
||||
}
|
||||
@@ -276,6 +276,7 @@ define('tools.querytool', [
|
||||
height: '100%',
|
||||
isCloseable: true,
|
||||
isPrivate: true,
|
||||
isLayoutMember: false,
|
||||
content: '<div id ="geometry_viewer_panel" class="sql-editor-geometry-viewer" tabindex: "0"></div>',
|
||||
});
|
||||
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large.
Load diff
Binary file not shown.
File diff suppressed because it is too large.
Load diff
Binary file not shown.
File diff suppressed because it is too large.
Load diff
Binary file not shown.
File diff suppressed because it is too large.
Load diff
Binary file not shown.
File diff suppressed because it is too large.
Load diff
Binary file not shown.
File diff suppressed because it is too large.
Load diff
Binary file not shown.
File diff suppressed because it is too large.
Load diff
Binary file not shown.
File diff suppressed because it is too large.
Load diff
@@ -428,7 +428,7 @@ class Preferences(object):
|
||||
assert _type is not None, "Type for a preference cannot be none!"
|
||||
assert _type in (
|
||||
'boolean', 'integer', 'numeric', 'date', 'datetime',
|
||||
'options', 'multiline', 'switch', 'node', 'text',
|
||||
'options', 'multiline', 'switch', 'node', 'text', 'radioModern',
|
||||
'keyboardshortcut'
|
||||
), "Type cannot be found in the defined list!"
|
||||
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2019, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import {pgBrowser} from 'pgadmin.browser.layout';
|
||||
import 'wcdocker';
|
||||
|
||||
var wcDocker = window.wcDocker;
|
||||
|
||||
describe('layout related functions test', function() {
|
||||
let menu_items = null;
|
||||
let dummy_cache = [{
|
||||
id: 2,
|
||||
mid: 1,
|
||||
module:'browser',
|
||||
name:'lock_layout',
|
||||
value: 'none',
|
||||
}];
|
||||
|
||||
beforeEach(function(){
|
||||
pgBrowser.preferences_cache = dummy_cache;
|
||||
pgBrowser.docker = {
|
||||
'lockLayout': ()=>{},
|
||||
};
|
||||
|
||||
_.extend(pgBrowser,{
|
||||
'menus': {
|
||||
'file': {
|
||||
'mnu_locklayout': {
|
||||
'menu_items': [
|
||||
{'name': 'mnu_lock_none', change_checked: ()=> {}},
|
||||
{'name': 'mnu_lock_docking', change_checked: ()=> {}},
|
||||
{'name': 'mnu_lock_full', change_checked: ()=> {}},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
menu_items = pgBrowser.menus.file.mnu_locklayout.menu_items;
|
||||
});
|
||||
|
||||
describe('for menu actions', function() {
|
||||
beforeEach(function(){
|
||||
spyOn(pgBrowser, 'lock_layout');
|
||||
spyOn(pgBrowser, 'save_lock_layout');
|
||||
});
|
||||
|
||||
it('mnu_lock_none', function() {
|
||||
pgBrowser.mnu_lock_none();
|
||||
expect(pgBrowser.lock_layout).toHaveBeenCalledWith(pgBrowser.docker, 'none');
|
||||
expect(pgBrowser.save_lock_layout).toHaveBeenCalledWith('none');
|
||||
});
|
||||
|
||||
it('mnu_lock_docking', function() {
|
||||
pgBrowser.mnu_lock_docking();
|
||||
expect(pgBrowser.lock_layout).toHaveBeenCalledWith(pgBrowser.docker, 'docking');
|
||||
expect(pgBrowser.save_lock_layout).toHaveBeenCalledWith('docking');
|
||||
});
|
||||
|
||||
it('mnu_lock_full', function() {
|
||||
pgBrowser.mnu_lock_full();
|
||||
expect(pgBrowser.lock_layout).toHaveBeenCalledWith(pgBrowser.docker, 'full');
|
||||
expect(pgBrowser.save_lock_layout).toHaveBeenCalledWith('full');
|
||||
});
|
||||
});
|
||||
|
||||
describe('lock_layout', function() {
|
||||
let change_checked_test= function(menu_name) {
|
||||
for(let i=0; i<menu_items.length; i++) {
|
||||
if(menu_items[i].name == menu_name) {
|
||||
expect(menu_items[i].change_checked).toHaveBeenCalledWith(true);
|
||||
} else {
|
||||
expect(menu_items[i].change_checked).toHaveBeenCalledWith(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(function(){
|
||||
spyOn(pgBrowser.docker, 'lockLayout');
|
||||
for(let i=0; i<menu_items.length; i++) {
|
||||
spyOn(menu_items[i], 'change_checked');
|
||||
}
|
||||
});
|
||||
|
||||
it('none', function() {
|
||||
pgBrowser.lock_layout(pgBrowser.docker, 'none');
|
||||
expect(pgBrowser.docker.lockLayout).toHaveBeenCalledWith(wcDocker.LOCK_LAYOUT_LEVEL.NONE);
|
||||
change_checked_test('mnu_lock_none');
|
||||
});
|
||||
|
||||
it('docking', function() {
|
||||
pgBrowser.lock_layout(pgBrowser.docker, 'docking');
|
||||
expect(pgBrowser.docker.lockLayout).toHaveBeenCalledWith(wcDocker.LOCK_LAYOUT_LEVEL.PREVENT_DOCKING);
|
||||
change_checked_test('mnu_lock_docking');
|
||||
});
|
||||
|
||||
it('full', function() {
|
||||
pgBrowser.lock_layout(pgBrowser.docker, 'full');
|
||||
expect(pgBrowser.docker.lockLayout).toHaveBeenCalledWith(wcDocker.LOCK_LAYOUT_LEVEL.FULL);
|
||||
change_checked_test('mnu_lock_full');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -190,6 +190,7 @@ var webpackShimConfig = {
|
||||
'pgadmin.browser.error': path.join(__dirname, './pgadmin/browser/static/js/error'),
|
||||
'pgadmin.browser.frame': path.join(__dirname, './pgadmin/browser/static/js/frame'),
|
||||
'pgadmin.browser.keyboard': path.join(__dirname, './pgadmin/browser/static/js/keyboard'),
|
||||
'pgadmin.browser.layout': path.join(__dirname, './pgadmin/browser/static/js/layout'),
|
||||
'pgadmin.browser.preferences': path.join(__dirname, './pgadmin/browser/static/js/preferences'),
|
||||
'pgadmin.browser.menu': path.join(__dirname, './pgadmin/browser/static/js/menu'),
|
||||
'pgadmin.browser.messages': '/browser/js/messages',
|
||||
|
||||
@@ -72,6 +72,7 @@ module.exports = {
|
||||
alias: {
|
||||
'top': path.join(__dirname, './pgadmin'),
|
||||
'jquery': path.join(__dirname, './node_modules/jquery/dist/jquery'),
|
||||
'wcdocker': path.join(__dirname, './node_modules/webcabin-docker/Build/wcDocker'),
|
||||
'alertify': path.join(__dirname, './node_modules/alertifyjs/build/alertify'),
|
||||
'jquery.event.drag': path.join(__dirname, './node_modules/slickgrid/lib/jquery.event.drag-2.3.0'),
|
||||
'jquery.ui': path.join(__dirname, './node_modules/slickgrid/lib/jquery-ui-1.11.3'),
|
||||
@@ -100,6 +101,7 @@ module.exports = {
|
||||
'pgadmin.backform': sourcesDir + '/js/backform.pgadmin',
|
||||
'pgbrowser': path.resolve(__dirname, 'regression/javascript/fake_browser'),
|
||||
'pgadmin.schema.dir': path.resolve(__dirname, 'pgadmin/browser/server_groups/servers/databases/schemas/static/js'),
|
||||
'pgadmin.browser.layout': path.join(__dirname, './pgadmin/browser/static/js/layout'),
|
||||
'pgadmin.browser.preferences': path.join(__dirname, './pgadmin/browser/static/js/preferences'),
|
||||
'bundled_codemirror': path.join(__dirname, './pgadmin/static/bundle/codemirror'),
|
||||
},
|
||||
|
||||
+2
-2
@@ -8508,9 +8508,9 @@ watchpack@^1.5.0:
|
||||
graceful-fs "^4.1.2"
|
||||
neo-async "^2.5.0"
|
||||
|
||||
"webcabin-docker@git+https://github.com/EnterpriseDB/wcDocker/#e191a1063c111d5542ff26a7c163940be180333d":
|
||||
"webcabin-docker@git+https://github.com/EnterpriseDB/wcDocker/#f3c79e4b9a9e76a8e34736fc33473e65c524050c":
|
||||
version "2.2.4-dev"
|
||||
resolved "git+https://github.com/EnterpriseDB/wcDocker/#e191a1063c111d5542ff26a7c163940be180333d"
|
||||
resolved "git+https://github.com/EnterpriseDB/wcDocker/#f3c79e4b9a9e76a8e34736fc33473e65c524050c"
|
||||
dependencies:
|
||||
FileSaver "^0.10.0"
|
||||
font-awesome "^4.7.0"
|
||||
|
||||
Reference in new issue
Block a user