diff --git a/web/config.py b/web/config.py
index c656ebfa3..5dcfc80c9 100644
--- a/web/config.py
+++ b/web/config.py
@@ -39,6 +39,10 @@ APP_VERSION = '%s.%s.%s-%s' % (APP_MAJOR, APP_MINOR, APP_REVISION, APP_SUFFIX)
# List of modules to skip when dynamically loading
MODULE_BLACKLIST = [ 'test' ]
+# DO NOT CHANGE UNLESS YOU KNOW WHAT YOU ARE DOING!
+# List of treeview browser nodes to skip when dynamically loading
+NODE_BLACKLIST = [ ]
+
##########################################################################
# Log settings
##########################################################################
diff --git a/web/pgadmin/__init__.py b/web/pgadmin/__init__.py
index b5c47af1d..74620e62d 100644
--- a/web/pgadmin/__init__.py
+++ b/web/pgadmin/__init__.py
@@ -103,10 +103,10 @@ def create_app(app_name=config.APP_NAME):
# Looks like a module, so import it, and register the blueprint if present
# We rely on the ordering of syspath to ensure we actually get the right
- # module here. Note that we also try to load the 'browser' module for
- # the browser integration hooks.
+ # module here. Note that we also try to load the 'hooks' module for
+ # the browser integration hooks and other similar functions.
app.logger.info('Examining potential module: %s' % d)
- module = __import__(f, globals(), locals(), ['browser', 'views'], -1)
+ module = __import__(f, globals(), locals(), ['hooks', 'views'], -1)
# Add the module to the global module list
modules.append(module)
@@ -118,6 +118,11 @@ def create_app(app_name=config.APP_NAME):
app.logger.debug(' - root_path: %s' % module.views.blueprint.root_path)
app.logger.debug(' - static_folder: %s' % module.views.blueprint.static_folder)
app.logger.debug(' - template_folder: %s' % module.views.blueprint.template_folder)
+
+ # Register any sub-modules
+ if 'hooks' in dir(module) and 'register_submodules' in dir(module.hooks):
+ app.logger.info('Registering sub-modules in %s' % f)
+ module.hooks.register_submodules(app)
##########################################################################
# Handle the desktop login
diff --git a/web/pgadmin/about/browser.py b/web/pgadmin/about/hooks.py
similarity index 100%
rename from web/pgadmin/about/browser.py
rename to web/pgadmin/about/hooks.py
diff --git a/web/pgadmin/browser/__init__.py b/web/pgadmin/browser/__init__.py
index e69de29bb..50115f062 100644
--- a/web/pgadmin/browser/__init__.py
+++ b/web/pgadmin/browser/__init__.py
@@ -0,0 +1,11 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2014, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+# Define the global node list
+nodes = [ ]
\ No newline at end of file
diff --git a/web/pgadmin/browser/hooks.py b/web/pgadmin/browser/hooks.py
new file mode 100644
index 000000000..b6b90cff8
--- /dev/null
+++ b/web/pgadmin/browser/hooks.py
@@ -0,0 +1,51 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2014, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Browser application hooks"""
+
+import os, sys
+import config
+
+from . import nodes
+
+def register_submodules(app):
+ """Register any child node blueprints"""
+ path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'nodes')
+ sys.path.insert(0, path)
+ files = os.listdir(path)
+
+ for f in files:
+ d = os.path.join(path, f)
+ if os.path.isdir(d) and os.path.isfile(os.path.join(d, '__init__.py')):
+
+ if f in config.NODE_BLACKLIST:
+ app.logger.info('Skipping blacklisted node: %s' % f)
+ continue
+
+ # Looks like a node, so import it, and register the blueprint if present
+ # We rely on the ordering of syspath to ensure we actually get the right
+ # module here.
+ app.logger.info('Examining potential node: %s' % d)
+ node = __import__(f, globals(), locals(), ['hooks', 'views'], -1)
+
+ # Add the node to the global module list
+ nodes.append(node)
+
+ # Register the blueprint if present
+ if 'views' in dir(node) and 'blueprint' in dir(node.views):
+ app.logger.info('Registering blueprint node: %s' % f)
+ app.register_blueprint(node.views.blueprint)
+ app.logger.debug(' - root_path: %s' % node.views.blueprint.root_path)
+ app.logger.debug(' - static_folder: %s' % node.views.blueprint.static_folder)
+ app.logger.debug(' - template_folder: %s' % node.views.blueprint.template_folder)
+
+ # Register any sub-modules
+ if 'hooks' in dir(node) and 'register_submodules' in dir(node.hooks):
+ app.logger.info('Registering sub-modules in %s' % f)
+ node.hooks.register_submodules(app)
\ No newline at end of file
diff --git a/web/pgadmin/browser/nodes/__init__.py b/web/pgadmin/browser/nodes/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/web/pgadmin/browser/nodes/server_groups/__init__.py b/web/pgadmin/browser/nodes/server_groups/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/web/pgadmin/browser/nodes/server_groups/hooks.py b/web/pgadmin/browser/nodes/server_groups/hooks.py
new file mode 100644
index 000000000..b94fb6f24
--- /dev/null
+++ b/web/pgadmin/browser/nodes/server_groups/hooks.py
@@ -0,0 +1,75 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2014, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Integration hooks for server groups."""
+
+from flask import url_for
+from flask.ext.security import current_user
+
+from pgadmin.settings.settings_model import db, ServerGroup
+
+def get_nodes():
+ """Return a JSON document listing the server groups for the user"""
+ groups = ServerGroup.query.filter_by(user_id=current_user.id)
+
+ value = ''
+ for group in groups:
+ value += '{"id":%d,"label":"%s","icon":"icon-server-group","inode":true},' % (group.id, group.name)
+ value = value[:-1]
+
+ return value
+
+def get_file_menu_items():
+ """Return a (set) of dicts of file menu items, with name, priority and URL."""
+ return [
+ {'name': 'Add a server group...', 'priority': 10, 'url': '#', 'onclick': 'add_server_group()'}
+ ]
+
+def get_script_snippets():
+ """Return the script snippets needed to handle treeview node operations."""
+ script = """function add_server_group() {
+ var alert = alertify.prompt(
+ 'Add a server group',
+ 'Enter a name for the new server group',
+ '',
+ function(evt, value) { $.post("%s", { name: value })
+ .done(function(data) {
+ if (data.success == 0) {
+ report_error(data.errormsg, data.info);
+ } else {
+ var item = {
+ id: data.data.id,
+ label: data.data.name,
+ inode: true,
+ open: false,
+ icon: 'icon-server-group'
+ }
+
+ treeApi.append(null, {
+ itemData: item
+ });
+
+ }
+ })
+ },
+ function(evt, value) { }
+ );
+ alert.show();
+}
+""" % url_for('NODE-server-group.add')
+ return script
+
+def get_css_snippets():
+ """Return the CSS needed to display the treeview node image."""
+ css = ".icon-server-group {\n"
+ css += " background: url('%s') 0 0 no-repeat !important;\n" % \
+ url_for('NODE-server-group.static', filename='img/server-group.png')
+ css += "{"
+
+ return css
\ No newline at end of file
diff --git a/web/pgadmin/browser/nodes/server_groups/static/img/server-group.png b/web/pgadmin/browser/nodes/server_groups/static/img/server-group.png
new file mode 100644
index 000000000..bd49bd271
Binary files /dev/null and b/web/pgadmin/browser/nodes/server_groups/static/img/server-group.png differ
diff --git a/web/pgadmin/browser/nodes/server_groups/views.py b/web/pgadmin/browser/nodes/server_groups/views.py
new file mode 100644
index 000000000..acc82deea
--- /dev/null
+++ b/web/pgadmin/browser/nodes/server_groups/views.py
@@ -0,0 +1,64 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2015, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Defines views for management of server groups"""
+
+NODE_NAME = 'server-group'
+
+NODE_PATH = '/browser/' + NODE_NAME
+
+import traceback
+from flask import Blueprint, Response, current_app, request
+from flask.ext.security import current_user, login_required
+
+from utils.ajax import make_json_result
+from pgadmin.settings.settings_model import db, ServerGroup
+import config
+
+# Initialise the module
+blueprint = Blueprint("NODE-" + NODE_NAME, __name__, static_folder='static', static_url_path='', template_folder='templates', url_prefix=NODE_PATH)
+
+@blueprint.route('/add/', methods=['POST'])
+@login_required
+def add():
+ """Add a server group node to the settings database"""
+ success = 1
+ errormsg = ''
+ data = { }
+
+ if request.form['name'] != '':
+ servergroup = ServerGroup(user_id=current_user.id, name=request.form['name'])
+
+ try:
+ db.session.add(servergroup)
+ db.session.commit()
+ except Exception as e:
+ success = 0
+ errormsg = e.message
+
+ else:
+ success = 0
+ errormsg = "No server group name was specified"
+
+ if success == 1:
+ data['id'] = servergroup.id
+ data['name'] = servergroup.name
+
+ value = make_json_result(success=success,
+ errormsg=errormsg,
+ info=traceback.format_exc(),
+ result=request.form,
+ data=data)
+
+ resp = Response(response=value,
+ status=200,
+ mimetype="text/json")
+
+ return resp
+
\ No newline at end of file
diff --git a/web/pgadmin/browser/static/js/utils.js b/web/pgadmin/browser/static/js/utils.js
new file mode 100644
index 000000000..7550fa62c
--- /dev/null
+++ b/web/pgadmin/browser/static/js/utils.js
@@ -0,0 +1,39 @@
+function report_error(message, info) {
+
+ text = '
\
+
'
+
+ if (info != '') {
+ text += '
\
+
'
+ }
+
+ text += ''
+
+ alertify.alert(
+ 'An error has occurred',
+ text
+ )
+}
\ No newline at end of file
diff --git a/web/pgadmin/browser/templates/browser/body.html b/web/pgadmin/browser/templates/browser/body.html
index 282691953..12944cff7 100644
--- a/web/pgadmin/browser/templates/browser/body.html
+++ b/web/pgadmin/browser/templates/browser/body.html
@@ -153,17 +153,18 @@ $('#dependents a').click(function (e) {
// Syntax highlight the SQL Pane
var editor = CodeMirror.fromTextArea(document.getElementById("sql-textarea"), {
- lineNumbers: true,
- mode: "text/x-sql",
- readOnly: true,
+ lineNumbers: true,
+ mode: "text/x-sql",
+ readOnly: true,
});
// Initialise the treeview
$('#tree').aciTree({
- ajax: {
- url: '/static/tree.json'
- }
+ ajax: {
+ url: '{{ url_for('browser.get_nodes') }}'
+ },
});
+var treeApi = $('#tree').aciTree('api');
diff --git a/web/pgadmin/browser/templates/browser/index.html b/web/pgadmin/browser/templates/browser/index.html
index 4f0508bda..15dd0f86a 100644
--- a/web/pgadmin/browser/templates/browser/index.html
+++ b/web/pgadmin/browser/templates/browser/index.html
@@ -66,8 +66,4 @@
{% include 'browser/body.html' %}
{% include 'browser/messages.html' %}
-
-
{% endblock %}
diff --git a/web/pgadmin/browser/views.py b/web/pgadmin/browser/views.py
index c9ecc73fd..bd3868e10 100644
--- a/web/pgadmin/browser/views.py
+++ b/web/pgadmin/browser/views.py
@@ -10,12 +10,13 @@
"""A blueprint module implementing the core pgAdmin browser."""
MODULE_NAME = 'browser'
-from flask import Blueprint, current_app, render_template, url_for
+from flask import Blueprint, Response, current_app, render_template, url_for
from flaskext.gravatar import Gravatar
from flask.ext.security import login_required
from flask.ext.login import current_user
from inspect import getmoduleinfo, getmembers
+from . import nodes
from pgadmin import modules
from pgadmin.settings import get_setting
@@ -24,9 +25,6 @@ import config
# Initialise the module
blueprint = Blueprint(MODULE_NAME, __name__, static_folder='static', template_folder='templates', url_prefix='/' + MODULE_NAME)
-##########################################################################
-# A test page
-##########################################################################
@blueprint.route("/")
@login_required
def index():
@@ -47,42 +45,48 @@ def index():
help_items = [ ]
stylesheets = [ ]
scripts = [ ]
+
+ modules_and_nodes = modules + nodes
# Add browser stylesheets
stylesheets.append(url_for('static', filename='css/codemirror/codemirror.css'))
stylesheets.append(url_for('browser.static', filename='css/browser.css'))
stylesheets.append(url_for('browser.static', filename='css/aciTree/css/aciTree.css'))
+ stylesheets.append(url_for('browser.browser_css'))
# Add browser scripts
scripts.append(url_for('static', filename='js/codemirror/codemirror.js'))
scripts.append(url_for('static', filename='js/codemirror/mode/sql.js'))
+ scripts.append(url_for('browser.static', filename='js/utils.js'))
scripts.append(url_for('browser.static', filename='js/aciTree/jquery.aciPlugin.min.js'))
+ scripts.append(url_for('browser.static', filename='js/aciTree/jquery.aciTree.dom.js'))
scripts.append(url_for('browser.static', filename='js/aciTree/jquery.aciTree.min.js'))
+ scripts.append(url_for('browser.browser_js'))
- for module in modules:
+ for module in modules_and_nodes:
# Get the edit menu items
- if 'browser' in dir(module) and 'get_file_menu_items' in dir(module.browser):
- file_items.extend(module.browser.get_file_menu_items())
+ if 'hooks' in dir(module) and 'get_file_menu_items' in dir(module.hooks):
+ file_items.extend(module.hooks.get_file_menu_items())
# Get the edit menu items
- if 'browser' in dir(module) and 'get_edit_menu_items' in dir(module.browser):
- edit_items.extend(module.browser.get_edit_menu_items())
+ if 'hooks' in dir(module) and 'get_edit_menu_items' in dir(module.hooks):
+ edit_items.extend(module.hooks.get_edit_menu_items())
# Get the tools menu items
- if 'browser' in dir(module) and 'get_tools_menu_items' in dir(module.browser):
- tools_items.extend(module.browser.get_tools_menu_items())
+ if 'hooks' in dir(module) and 'get_tools_menu_items' in dir(module.hooks):
+ tools_items.extend(module.hooks.get_tools_menu_items())
# Get the help menu items
- if 'browser' in dir(module) and 'get_help_menu_items' in dir(module.browser):
- help_items.extend(module.browser.get_help_menu_items())
+ if 'hooks' in dir(module) and 'get_help_menu_items' in dir(module.hooks):
+ help_items.extend(module.hooks.get_help_menu_items())
# Get any stylesheets
- if 'browser' in dir(module) and 'get_stylesheets' in dir(module.browser):
- stylesheets += module.browser.get_stylesheets()
+ if 'hooks' in dir(module) and 'get_stylesheets' in dir(module.hooks):
+ stylesheets += module.hooks.get_stylesheets()
# Get any scripts
- if 'browser' in dir(module) and 'get_scripts' in dir(module.browser):
- scripts += module.browser.get_scripts()
+ if 'hooks' in dir(module) and 'get_scripts' in dir(module.hooks):
+ scripts += module.hooks.get_scripts()
file_items = sorted(file_items, key=lambda k: k['priority'])
edit_items = sorted(edit_items, key=lambda k: k['priority'])
@@ -105,3 +109,61 @@ def index():
stylesheets = stylesheets,
scripts = scripts,
layout_settings = layout_settings)
+
+@blueprint.route("/browser.js")
+@login_required
+def browser_js():
+ """Render and return JS snippets from the nodes and modules."""
+ snippets = ''
+ modules_and_nodes = modules + nodes
+
+ for module in modules_and_nodes:
+ if 'hooks' in dir(module) and 'get_script_snippets' in dir(module.hooks):
+ snippets += module.hooks.get_script_snippets()
+
+ resp = Response(response=snippets,
+ status=200,
+ mimetype="application/javascript")
+
+ return resp
+
+@blueprint.route("/browser.css")
+@login_required
+def browser_css():
+ """Render and return CSS snippets from the nodes and modules."""
+ snippets = ''
+ modules_and_nodes = modules + nodes
+
+ for module in modules_and_nodes:
+ if 'hooks' in dir(module) and 'get_css_snippets' in dir(module.hooks):
+ snippets += module.hooks.get_css_snippets()
+
+ resp = Response(response=snippets,
+ status=200,
+ mimetype="text/css")
+
+ return resp
+
+@blueprint.route("/root-nodes.json")
+@login_required
+def get_nodes():
+ """Build a list of treeview nodes from the child modules."""
+ value = '['
+
+ for node in nodes:
+ if 'hooks' in dir(node) and 'get_nodes' in dir(node.hooks):
+ value += node.hooks.get_nodes() + ','
+
+ if value[-1:] == ',':
+ value = value[:-1]
+
+ value += ']'
+
+ resp = Response(response=value,
+ status=200,
+ mimetype="text/json")
+
+ return resp
+
+
+
\ No newline at end of file
diff --git a/web/pgadmin/settings/browser.py b/web/pgadmin/settings/hooks.py
similarity index 100%
rename from web/pgadmin/settings/browser.py
rename to web/pgadmin/settings/hooks.py
diff --git a/web/pgadmin/settings/views.py b/web/pgadmin/settings/views.py
index db392ae73..0421694ab 100644
--- a/web/pgadmin/settings/views.py
+++ b/web/pgadmin/settings/views.py
@@ -10,10 +10,12 @@
"""Views for setting and storing configuration options."""
MODULE_NAME = 'settings'
-import config
+import traceback
from flask import Blueprint, Response, abort, request, render_template
from flask.ext.security import login_required
+import config
+from utils.ajax import make_json_result
from . import get_setting, store_setting
# Initialise the module
@@ -33,16 +35,30 @@ def script():
def store(setting=None, value=None):
"""Store a configuration setting, or if this is a POST request and a
count value is present, store multiple settings at once."""
- if request.method == 'POST':
- if 'count' in request.form:
- for x in range(int(request.form['count'])):
- store_setting(request.form['setting%d' % (x+1)], request.form['value%d' % (x+1)])
- else:
- store_setting(request.form['setting'], request.form['value'])
- else:
- store_setting(setting, value)
+ success = 1
+ errorcode = 0
+ errormsg = ''
- return ''
+ try:
+ if request.method == 'POST':
+ if 'count' in request.form:
+ for x in range(int(request.form['count'])):
+ store_setting(request.form['setting%d' % (x+1)], request.form['value%d' % (x+1)])
+ else:
+ store_setting(request.form['setting'], request.form['value'])
+ else:
+ store_setting(setting, value)
+ except Exception as e:
+ success = 0
+ errormsg = e.message
+
+ value = make_json_result(success=success, errormsg=errormsg, info=traceback.format_exc(), result=request.form)
+
+ resp = Response(response=value,
+ status=200,
+ mimetype="text/json")
+
+ return resp
@blueprint.route("/get", methods=['POST'])
@blueprint.route("/get/", methods=['GET'])
@@ -53,14 +69,21 @@ def get(setting=None, default=None):
if request.method == 'POST':
setting = request.form['setting']
default = request.form['default']
-
+
+ success = 1
+ errorcode = 0
+ errormsg = ''
+
try:
value = get_setting(setting, default)
- except:
- return ''
-
+ except Exception as e:
+ success = 0
+ errormsg = e.message
+
+ value = make_json_result(success=success, errormsg=errormsg, info=traceback.format_exc(), result=request.form)
+
resp = Response(response=value,
status=200,
- mimetype="text/plain")
+ mimetype="text/json")
return resp
diff --git a/web/pgadmin/static/css/overrides.css b/web/pgadmin/static/css/overrides.css
index 20f4fea51..cb9be4ce3 100644
--- a/web/pgadmin/static/css/overrides.css
+++ b/web/pgadmin/static/css/overrides.css
@@ -15,6 +15,11 @@
color: #fff;
}
+.alertify .ajs-content {
+ padding-left: 0 !important;
+ padding-right: 0 !important;
+}
+
/* iFrames should have no border */
iframe {
border-width: 0;
@@ -30,6 +35,13 @@ iframe {
padding-left: 0;
}
-.icon-servers {
- background: url('/static/servers.png') 0 0 no-repeat !important;
-}
+/* Alert info panel */
+.alert-info-panel {
+ border: 2px solid #a1a1a1;
+ margin-top: 2em;
+ padding: 5px 5px;
+ background: #dddddd;
+ border-radius: 5px;
+ height: 8em;
+ overflow: scroll;
+}
\ No newline at end of file
diff --git a/web/pgadmin/test/browser.py b/web/pgadmin/test/hooks.py
similarity index 100%
rename from web/pgadmin/test/browser.py
rename to web/pgadmin/test/hooks.py
diff --git a/web/utils/__init__.py b/web/utils/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/web/utils/ajax.py b/web/utils/ajax.py
new file mode 100644
index 000000000..ae5249ca0
--- /dev/null
+++ b/web/utils/ajax.py
@@ -0,0 +1,24 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2015, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Utility functions for dealing with AJAX."""
+
+import json
+
+def make_json_result(success=1, errormsg='', info='', result={}, data={}):
+ """Create a JSON response document describing the results of a request and
+ containing the data."""
+ doc = { }
+ doc['success'] = success
+ doc['errormsg'] = errormsg
+ doc['info'] = info
+ doc['result'] = result
+ doc['data'] = data
+
+ return json.dumps(doc)
\ No newline at end of file