Allow connections to be coloured in the treeview and query tool. Fixes #1383. Fixes #2802

This commit is contained in:
Murtuza Zabuawala
2017-11-21 16:28:01 +00:00
committed by Dave Page
parent 9212699936
commit b284572afe
19 changed files with 365 additions and 30 deletions

BIN
docs/en_US/images/server_general.png Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 103 KiB

View File

@@ -16,6 +16,8 @@ Use the fields in the *General* tab to identify the server:
* Use the *Name* field to add a descriptive name for the server; the name specified will be displayed in the *pgAdmin*
tree control of the client.
* Use the drop-down list box in the *Server group* field to specify the *pgAdmin* tree control parent node for the server.
* Use the color-picker in the *Background* field to specify the background color for the server.
* Use the color-picker in the *Foreground* field to specify the foreground color for the server.
* Uncheck the checkbox next to *Connect now?* to instruct pgAdmin not to attempt a connection upon completion of the
dialog. The default enables connection.
* Provide a comment about the server in the *Comments* field.

View File

@@ -36,3 +36,4 @@ jQuery-UI 1.11.3 MIT https://jqueryui.com/
BigNumber 3.0.1 MIT http://mikemcl.github.io/bignumber.js
Source Code Pro 1.1 SIL OFL https://fonts.googleapis.com/css?family=Source+Code+Pro:400,700
Open Sans 2.0 AL https://fonts.googleapis.com/css?family=Open+Sans:400,400i,600,700
Spectrum 1.8 MIT https://bgrins.github.io/spectrum/

View File

@@ -0,0 +1,35 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2017, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""
Adding new columns to store background & foreground (Feature: RM#1383)
Revision ID: 02b9dccdcfcb
Revises: ef590e979b0d
Create Date: 2017-11-14 19:09:04.674575
"""
from pgadmin.model import db
# revision identifiers, used by Alembic.
revision = '02b9dccdcfcb'
down_revision = 'ef590e979b0d'
branch_labels = None
depends_on = None
def upgrade():
db.engine.execute(
'ALTER TABLE server ADD COLUMN bgcolor TEXT(10)'
)
db.engine.execute(
'ALTER TABLE server ADD COLUMN fgcolor TEXT(10)'
)
def downgrade():
pass

View File

@@ -78,6 +78,7 @@
"shim-loader": "^1.0.1",
"slickgrid": "git+https://github.com/6pac/SlickGrid.git#2.3.7",
"snapsvg": "^0.5.1",
"spectrum-colorpicker": "^1.8.0",
"underscore": "^1.8.3",
"underscore.string": "^3.3.4",
"watchify": "~3.9.0",

View File

@@ -43,7 +43,6 @@ def has_any(data, keys):
return False
def recovery_state(connection, postgres_version):
recovery_check_sql = render_template("connect/sql/#{0}#/check_recovery.sql".format(postgres_version))
@@ -56,6 +55,38 @@ def recovery_state(connection, postgres_version):
wal_paused = None
return in_recovery, wal_paused
def server_icon_and_background(is_connected, manager, server):
"""
Args:
is_connected: Flag to check if server is connected
manager: Connection manager
server: Sever object
Returns:
Server Icon CSS class
"""
server_background_color = ''
if server and server.bgcolor:
server_background_color = ' {0}'.format(
server.bgcolor
)
# If user has set font color also
if server.fgcolor:
server_background_color = '{0} {1}'.format(
server_background_color,
server.fgcolor
)
if is_connected:
return 'icon-{0}{1}'.format(
manager.server_type, server_background_color
)
else:
return 'icon-server-not-connected{0}'.format(
server_background_color
)
class ServerModule(sg.ServerGroupPluginModule):
NODE_TYPE = "server"
@@ -87,14 +118,14 @@ class ServerModule(sg.ServerGroupPluginModule):
connected = conn.connected()
in_recovery = None
wal_paused = None
if connected:
in_recovery, wal_paused = recovery_state(conn, manager.version)
yield self.generate_browser_node(
"%d" % (server.id),
gid,
server.name,
"icon-server-not-connected" if not connected else
"icon-{0}".format(manager.server_type),
server_icon_and_background(connected, manager, server),
True,
self.NODE_TYPE,
connected=connected,
@@ -305,8 +336,7 @@ class ServerNode(PGChildNodeView):
"%d" % (server.id),
gid,
server.name,
"icon-server-not-connected" if not connected else
"icon-{0}".format(manager.server_type),
server_icon_and_background(connected, manager, server),
True,
self.node_type,
connected=connected,
@@ -359,8 +389,7 @@ class ServerNode(PGChildNodeView):
"%d" % (server.id),
gid,
server.name,
"icon-server-not-connected" if not connected else
"icon-{0}".format(manager.server_type),
server_icon_and_background(connected, manager, server),
True,
self.node_type,
connected=connected,
@@ -435,7 +464,9 @@ class ServerNode(PGChildNodeView):
'sslkey': 'sslkey',
'sslrootcert': 'sslrootcert',
'sslcrl': 'sslcrl',
'sslcompression': 'sslcompression'
'sslcompression': 'sslcompression',
'bgcolor': 'bgcolor',
'fgcolor': 'fgcolor'
}
disp_lbl = {
@@ -515,8 +546,7 @@ class ServerNode(PGChildNodeView):
node=self.blueprint.generate_browser_node(
"%d" % (server.id), server.servergroup_id,
server.name,
"icon-server-not-connected" if not connected else
"icon-{0}".format(manager.server_type),
server_icon_and_background(connected, manager, server),
True,
self.node_type,
connected=False,
@@ -610,6 +640,8 @@ class ServerNode(PGChildNodeView):
'version': manager.ver,
'sslmode': server.ssl_mode,
'server_type': manager.server_type if connected else 'pg',
'bgcolor': server.bgcolor,
'fgcolor': server.fgcolor,
'db_res': server.db_res.split(',') if server.db_res else None,
'passfile': server.passfile if server.passfile else None,
'sslcert': server.sslcert if is_ssl else None,
@@ -680,7 +712,12 @@ class ServerNode(PGChildNodeView):
sslkey=data['sslkey'] if is_ssl else None,
sslrootcert=data['sslrootcert'] if is_ssl else None,
sslcrl=data['sslcrl'] if is_ssl else None,
sslcompression=1 if is_ssl and data['sslcompression'] else 0
sslcompression=1 if is_ssl and data['sslcompression'] else 0,
bgcolor=data['bgcolor'] if u'bgcolor' in data
else None,
fgcolor = data['fgcolor'] if u'fgcolor' in data
else None
)
db.session.add(server)
db.session.commit()
@@ -735,7 +772,7 @@ class ServerNode(PGChildNodeView):
node=self.blueprint.generate_browser_node(
"%d" % server.id, server.servergroup_id,
server.name,
'icon-{0}'.format(manager.server_type) if manager and manager.server_type else "icon-pg",
server_icon_and_background(connected, manager, server),
True,
self.node_type,
user=user,
@@ -968,9 +1005,7 @@ class ServerNode(PGChildNodeView):
success=1,
info=gettext("Server connected."),
data={
'icon': 'icon-{0}'.format(
manager.server_type
),
'icon': server_icon_and_background(True, manager, server),
'connected': True,
'server_type': manager.server_type,
'type': manager.server_type,
@@ -1001,7 +1036,7 @@ class ServerNode(PGChildNodeView):
success=1,
info=gettext("Server disconnected."),
data={
'icon': 'icon-server-not-connected',
'icon': server_icon_and_background(False, manager, server),
'connected': False
}
)

View File

@@ -651,6 +651,13 @@ define('pgadmin.node.server', [
},{
id: 'version', label: gettext('Version'), type: 'text', group: null,
mode: ['properties'], visible: 'isConnected'
},{
id: 'bgcolor', label: gettext('Background'), type: 'color',
group: null, mode: ['edit', 'create'], disabled: 'isfgColorSet',
deps: ['fgcolor']
},{
id: 'fgcolor', label: gettext('Foreground'), type: 'color',
group: null, mode: ['edit', 'create'], disabled: 'isConnected',
},{
id: 'connect_now', controlLabel: gettext('Connect now?'), type: 'checkbox',
group: null, mode: ['create']
@@ -881,6 +888,23 @@ define('pgadmin.node.server', [
isConnected: function(model) {
return model.get('connected');
},
isfgColorSet: function(model) {
var bgcolor = model.get('bgcolor'),
fgcolor = model.get('fgcolor');
if(model.get('connected')) {
return true;
}
// If fgcolor is set and bgcolor is not set then force bgcolor
// to set as white
if(_.isUndefined(bgcolor) || _.isNull(bgcolor) || !bgcolor) {
if(fgcolor) {
model.set('bgcolor', '#ffffff');
}
}
return false;
},
isSSL: function(model) {
var ssl_mode = model.get('sslmode');
// If server is not connected and have required SSL option

View File

@@ -1509,7 +1509,15 @@ define(
}
}
if (_old._pid != _new._pid || _old._label != _new._label) {
// 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 {

View File

@@ -732,6 +732,48 @@ define(
// Here call data grid method to render query tool
pgAdmin.DataGrid.show_query_tool('', i);
},
// Logic to change the server background colour
// There is no way of applying CSS to parent element so we have to
// do it via JS code only
change_server_background: function(item, data) {
if (!item || !data)
return;
// Go further only if node type is a Server
if (data && data._type && data._type == 'server') {
var element = $(item).find('span.aciTreeItem').first() || null,
// First element will be icon and second will be colour code
bgcolor = data.icon.split(" ")[1] || null,
fgcolor = data.icon.split(" ")[2] || '';
if(bgcolor) {
// li tag for the current branch
var first_level_element = element.parents()[3] || null,
dynamic_class = 'pga_server_' + data._id + '_bgcolor',
style_tag;
// Prepare dynamic style tag
style_tag = "<style id=" + dynamic_class + " type='text/css'> \n";
style_tag += "." + dynamic_class + ' .aciTreeItem {';
style_tag += " border-radius: 3px; margin-bottom: 2px;";
style_tag += " background: " + bgcolor + "} \n";
if(fgcolor) {
style_tag += "." + dynamic_class + ' .aciTreeText {';
style_tag += " color: " + fgcolor + ";} \n"
}
style_tag += "</style>";
// Prepare dynamic style tag using template
$('#' + dynamic_class).remove();
$(style_tag).appendTo("head");
if(first_level_element)
$(first_level_element).addClass(dynamic_class);
}
}
},
added: function(item, data, browser) {
var b = browser || pgBrowser,
t = b.tree,
@@ -757,6 +799,8 @@ define(
}
);
}
pgBrowser.Node.callbacks.change_server_background(item, data);
},
// Callback called - when a node is selected in browser tree.
selected: function(item, data, browser) {

View File

@@ -138,6 +138,8 @@ class Server(db.Model):
db.CheckConstraint(
'sslcompression >= 0 AND sslcompression <= 1'
), nullable=False)
bgcolor = db.Column(db.Text(10), nullable=True)
fgcolor = db.Column(db.Text(10), nullable=True)
class ModulePreference(db.Model):

View File

@@ -9,13 +9,25 @@
border-color: #84acdd;
}
.aciTree .aciTreeLine.aciTreeHover .aciTreeText {
color: black;
}
/* Selected element and in focus*/
.aciTree.aciTreeFocus .aciTreeFocus > .aciTreeLine .aciTreeItem {
background-color: #e7f2ff;
}
.aciTree.aciTreeFocus .aciTreeFocus > .aciTreeLine .aciTreeText {
color: black;
}
/* Selected element but not in focus */
.aciTree .aciTreeSelected > .aciTreeLine .aciTreeItem {
background-color: #e7f2ff;
border-color: #84acdd;
}
.aciTree .aciTreeItem {
white-space: nowrap !important;
}

View File

@@ -61,11 +61,6 @@ iframe {
padding-left: 0;
}
/* Prevent tree items wrapping */
.aciTree .aciTreeItem {
white-space: nowrap !important;
}
/* Disabled menu items */
.mnu-disabled {
color: #999999 !important;
@@ -1346,3 +1341,61 @@ body {
.alert-dismissable, .alert-dismissible {
padding-right: 35px !important;
}
/* Override CSS for Colour Picker to match it with pgAdmin4 style */
.sp-replacer {
background: #fff;
border: solid 1px #ccc;
border-radius: 3px;
}
.sp-replacer:hover {
border-color: #ccc;
}
.sp-replacer.sp-active {
border-color: #66afe9;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
-o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
}
.sp-replacer.sp-disabled {
background-color: #eee;
}
.sp-container {
background-color: #fff;
border: solid 1px #ccc;
border-radius: 3px !important;
}
.sp-palette-button-container button,
.sp-palette-button-container button:hover {
padding: 5px;
background-color: #2c76b4;
background-image: none;
font-family: 'Open Sans';
font-size: 12px;
color: #fff;
text-shadow: none;
-webkit-font-smoothing: auto;
}
.sp-container button:active {
border: 1px solid #84acdd;
border-bottom: none;
-webkit-box-shadow: inset 0 0 5px 2px #84acdd, 0 1px 0 0 #84acdd;
-moz-box-shadow: inset 0 0 5px 2px #84acdd, 0 1px 0 0 #84acdd;
-ms-box-shadow: inset 0 0 5px 2px #84acdd, 0 1px 0 0 #84acdd;
-o-box-shadow: inset 0 0 5px 2px #84acdd, 0 1px 0 0 #84acdd;
box-shadow: inset 0 0 5px 2px #84acdd, 0 1px 0 0 #84acdd;
}
.sp-dd {
font-size: 8px;
color: #888888;
}

View File

@@ -13,6 +13,7 @@
@import '~jquery-contextmenu/dist/jquery.contextMenu.css';
@import '~webcabin-docker/Build/wcDocker.css';
@import '~acitree/css/aciTree.css';
@import '~spectrum-colorpicker/spectrum.css';
@import '~codemirror/lib/codemirror.css';
@import '~codemirror/addon/dialog/dialog.css';

View File

@@ -4,8 +4,8 @@
if (typeof define === 'function' && define.amd) {
define([
'sources/gettext', 'underscore', 'underscore.string', 'jquery',
'backbone', 'backform', 'backgrid', 'codemirror', 'pgadmin.backgrid',
'select2'
'backbone', 'backform', 'backgrid', 'codemirror', 'spectrum',
'pgadmin.backgrid', 'select2'
],
function(gettext, _, S, $, Backbone, Backform, Backgrid, CodeMirror) {
// Export global even in AMD case in case this script is loaded with
@@ -68,7 +68,8 @@
'uniqueColCollection': ['unique-col-collection', 'unique-col-collection', 'string'],
'switch' : 'switch',
'select2': 'select2',
'note': 'note'
'note': 'note',
'color': 'color'
};
var getMappedControl = Backform.getMappedControl = function(type, mode) {
@@ -2367,5 +2368,95 @@
}
});
// Color Picker control
var ColorControl = Backform.ColorControl = Backform.InputControl.extend({
defaults: {
label: "",
extraClasses: [],
helpMessage: null,
showButtons: false,
showPalette: true,
allowEmpty: true,
colorFormat: "hex",
defaultColor: ""
},
template: _.template([
'<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
'<div class="<%=Backform.controlsClassName%>">',
' <input class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>" name="<%=name%>" value="<%-value%>" <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%> />',
' <% if (helpMessage && helpMessage.length) { %>',
' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
' <% } %>',
'</div>'
].join("\n")),
render: function() {
// Clear first
if(this.$picker && this.$picker.hasOwnProperty('destroy')) {
this.$picker('destroy');
}
var field = _.defaults(this.field.toJSON(), this.defaults),
attributes = this.model.toJSON(),
attrArr = field.name.split('.'),
name = attrArr.shift(),
path = attrArr.join('.'),
rawValue = this.keyPathAccessor(attributes[name], path),
data = _.extend(field, {
rawValue: rawValue,
value: this.formatter.fromRaw(rawValue, this.model),
attributes: attributes,
formatter: this.formatter
}),
evalF = function(f, d, m) {
return (_.isFunction(f) ? !!f.apply(d, [m]) : !!f);
};
// Evaluate the disabled, visible, and required option
_.extend(data, {
disabled: evalF(data.disabled, data, this.model),
visible: evalF(data.visible, data, this.model),
required: evalF(data.required, data, this.model)
});
// Clean up first
this.$el.empty();
if (!data.visible)
this.$el.addClass(Backform.hiddenClassname);
this.$el.html(this.template(data)).addClass(field.name);
// Creating default Color picker
this.$picker = this.$el.find("input").spectrum({
allowEmpty: data.allowEmpty,
preferredFormat: data.colorFormat,
disabled: data.disabled,
hideAfterPaletteSelect:true,
clickoutFiresChange: true,
showButtons: data.showButtons,
showPaletteOnly: data.showPalette,
togglePaletteOnly: data.showPalette,
togglePaletteMoreText: gettext('More'),
togglePaletteLessText: gettext('Less'),
color: data.value || data.defaultColor,
// Predefined palette colors
palette: [
["#000","#444","#666","#999","#ccc","#eee","#f3f3f3","#fff"],
["#f00","#f90","#ff0","#0f0","#0ff","#00f","#90f","#f0f"],
["#f4cccc","#fce5cd","#fff2cc","#d9ead3","#d0e0e3","#cfe2f3","#d9d2e9","#ead1dc"],
["#ea9999","#f9cb9c","#ffe599","#b6d7a8","#a2c4c9","#9fc5e8","#b4a7d6","#d5a6bd"],
["#e06666","#f6b26b","#ffd966","#93c47d","#76a5af","#6fa8dc","#8e7cc3","#c27ba0"],
["#c00","#e69138","#f1c232","#6aa84f","#45818e","#3d85c6","#674ea7","#a64d79"],
["#900","#b45f06","#bf9000","#38761d","#134f5c","#0b5394","#351c75","#741b47"],
["#600","#783f04","#7f6000","#274e13","#0c343d","#073763","#20124d","#4c1130"]
]
});
this.updateInvalid();
return this;
}
});
return Backform;
}));

View File

@@ -26,7 +26,7 @@ from pgadmin.utils.ajax import make_json_response, bad_request, \
from config import PG_DEFAULT_DRIVER
from pgadmin.utils.preferences import Preferences
from pgadmin.model import Server
class DataGridModule(PgAdminModule):
"""
@@ -216,6 +216,23 @@ def panel(trans_id, is_query_tool, editor_title):
else:
new_browser_tab = 'false'
# Fetch the server details
#
bgcolor = None
fgcolor = None
if str(trans_id) in session['gridData']:
# Fetch the object for the specified transaction id.
# Use pickle.loads function to get the command object
session_obj = session['gridData'][str(trans_id)]
trans_obj = pickle.loads(session_obj['command_obj'])
s = Server.query.filter_by(id=trans_obj.sid).first()
if s and s.bgcolor:
# If background is set to white means we do not have to change the
# title background else change it as per user specified background
if s.bgcolor != '#ffffff':
bgcolor = s.bgcolor
fgcolor = s.fgcolor or 'black'
return render_template(
"datagrid/index.html", _=gettext, uniqueId=trans_id,
is_query_tool=is_query_tool,
@@ -224,7 +241,9 @@ def panel(trans_id, is_query_tool, editor_title):
is_linux=is_linux_platform,
is_new_browser_tab=new_browser_tab,
server_type=server_type,
client_platform=user_agent.platform
client_platform=user_agent.platform,
bgcolor=bgcolor,
fgcolor=fgcolor
)

View File

@@ -284,7 +284,7 @@
</button>
</div>
</div>
<div class="editor-title"></div>
<div class="editor-title" style="background-color: {% if fgcolor %}{{ bgcolor or '#FFFFFF' }}{% else %}{{ bgcolor or '#2C76B4' }}{% endif %}; color: {{ fgcolor or 'white' }};"></div>
<div id="filter" class="filter-container hidden">
<div class="filter-title">Filter</div>
<div class="sql-textarea">

View File

@@ -34,9 +34,7 @@
}
.editor-title {
background-color: #2C76B4;
padding: 4px 5px;
color: white;
font-size: 13px;
}

View File

@@ -111,6 +111,10 @@ var webpackShimConfig = {
'pgadmin.browser.messages': {
'deps': ['pgadmin.browser.datamodel'],
},
'spectrum': {
'deps': ['jquery'],
'exports': '$.fn.spectrum',
},
},
// Map module id to file path used in 'define(['baseurl', 'misc']). It is
@@ -138,6 +142,7 @@ var webpackShimConfig = {
'dropzone': path.join(__dirname, './node_modules/dropzone/dist/dropzone'),
'bignumber': path.join(__dirname, './node_modules/bignumber.js/bignumber'),
'snap.svg': path.join(__dirname, './node_modules/snapsvg/dist/snap.svg'),
'spectrum': path.join(__dirname, './node_modules/spectrum-colorpicker/spectrum'),
// AciTree
'jquery.acitree': path.join(__dirname, './node_modules/acitree/js/jquery.aciTree.min'),

View File

@@ -6580,6 +6580,10 @@ spdx-license-ids@^1.0.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57"
spectrum-colorpicker@^1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/spectrum-colorpicker/-/spectrum-colorpicker-1.8.0.tgz#b926cf5002c0a77860b5f8351e1c093c65200107"
sprintf-js@^1.0.3:
version "1.1.1"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.1.tgz#36be78320afe5801f6cea3ee78b6e5aab940ea0c"