Re-organise node structure and loading to make things somewhat more

simple. This also adds the ability to display servers on the treeview.
pull/3/head
Dave Page 2015-03-10 13:09:11 +00:00
parent 7f68d6fced
commit fe834d1ed2
24 changed files with 400 additions and 80 deletions

View File

@ -84,7 +84,7 @@ int main(int argc, char * argv[])
}
// Generate the app server URL
QString appServerUrl = QString("http://localhost:%1/utils/test").arg(port);
QString appServerUrl = QString("http://localhost:%1/").arg(port);
// Now the server should be up, we'll attempt to connect and get a response.
// We'll retry in a loop a few time before aborting if necessary. The browser
@ -128,7 +128,7 @@ bool PingServer(QUrl url)
QNetworkReply *reply;
QVariant redirectUrl;
url.setPath("/utils/ping");
url.setPath("/ping");
do
{

View File

@ -15,7 +15,9 @@ import os, sys
# We need to include the root directory in sys.path to ensure that we can
# find everything we need when running in the standalone runtime.
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
root = os.path.dirname(os.path.realpath(__file__))
if sys.path[0] != root:
sys.path.insert(0, root)
import config
from pgadmin import create_app
@ -47,7 +49,6 @@ if not os.path.isfile(config.SQLITE_PATH):
# Create the app!
app = create_app()
app.logger.debug("Python syspath: %s", sys.path)
# Start the web server. The port number should have already been set by the
# runtime if we're running in desktop mode, otherwise we'll just use the

View File

@ -19,7 +19,7 @@ from flask_mail import Mail
from htmlmin.minify import html_minify
from settings.settings_model import db, Role, User
import inspect, imp, logging, os
import inspect, imp, logging, os, sys
# Configuration settings
import config
@ -66,7 +66,8 @@ def create_app(app_name=config.APP_NAME):
app.logger.info('################################################################################')
app.logger.info('Starting %s v%s...', config.APP_NAME, config.APP_VERSION)
app.logger.info('################################################################################')
app.logger.debug("Python syspath: %s", sys.path)
##########################################################################
# Setup i18n
##########################################################################
@ -117,6 +118,9 @@ def create_app(app_name=config.APP_NAME):
app.logger.info('Skipping blacklisted module: %s' % f)
continue
# Construct the "real" module name
f = 'pgadmin.' + f
# 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 'hooks' module for

View File

@ -7,5 +7,6 @@
#
##########################################################################
# Define the global node list
nodes = [ ]
# Define the node lists
all_nodes = [ ]
sub_nodes = [ ]

View File

@ -12,40 +12,10 @@
import os, sys
import config
from . import nodes
from pgadmin.browser.utils import register_modules
from pgadmin.browser import all_nodes
from . import sub_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)
register_modules(app, __file__, all_nodes, sub_nodes, 'pgadmin.browser')

View File

@ -7,8 +7,9 @@
#
##########################################################################
"""Base class for Collection Nodes on the browser treeview"""
# Node meta data
NODE_TYPE = 'server-group'
NODE_PATH = '/browser/nodes/' + NODE_TYPE
def get_name():
"""Returns the display name of the collection"""
return ""
# Define the child node list
sub_nodes = [ ]

View File

@ -13,15 +13,23 @@ from flask import render_template, url_for
from flask.ext.babel import gettext
from flask.ext.security import current_user
from pgadmin.browser.utils import register_modules
from pgadmin.settings.settings_model import db, ServerGroup
from pgadmin.browser import all_nodes
from . import NODE_TYPE, sub_nodes
def register_submodules(app):
"""Register any child node blueprints"""
register_modules(app, __file__, all_nodes, sub_nodes, 'pgadmin.browser.server_groups')
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 += '{"id":"%s/%d","label":"%s","icon":"icon-%s","inode":true,"_type":"%s"},' % (NODE_TYPE, group.id, group.name, NODE_TYPE, NODE_TYPE)
value = value[:-1]
return value
@ -38,10 +46,10 @@ def get_file_menu_items():
def get_context_menu_items():
"""Return a (set) of dicts of content menu items with name, label, priority and JS"""
"""Return a (set) of dicts of content menu items with name, node type, label, priority and JS"""
return [
{'name': 'delete', 'label': gettext('Delete server group'), 'priority': 100, 'onclick': 'delete_server_group(item);'},
{'name': 'rename', 'label': gettext('Rename server group...'), 'priority': 200, 'onclick': 'rename_server_group(item);'}
{'name': 'delete_server_group', 'type': NODE_TYPE, 'label': gettext('Delete server group'), 'priority': 10, 'onclick': 'delete_server_group(item);'},
{'name': 'rename_server_group', 'type': NODE_TYPE, 'label': gettext('Rename server group...'), 'priority': 20, 'onclick': 'rename_server_group(item);'}
]
@ -54,7 +62,7 @@ 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 += "{"
url_for('NODE-%s.static' % NODE_TYPE, filename='img/server-group.png')
css += "}\n"
return css

View File

@ -7,9 +7,10 @@
#
##########################################################################
"""Base class for Object Nodes on the browser treeview"""
# Node meta data
NODE_TYPE = 'server'
NODE_PATH = '/browser/nodes/' + NODE_TYPE
# Define the child node list
sub_nodes = [ ]
def get_name():
"""Returns the object's name"""
return ""

View File

@ -0,0 +1,61 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2015, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""Integration hooks for servers."""
from flask import render_template, url_for
from flask.ext.babel import gettext
from flask.ext.security import current_user
from pgadmin.settings.settings_model import db, Server
from . import NODE_TYPE
def get_nodes(server_group):
"""Return a JSON document listing the server groups for the user"""
servers = Server.query.filter_by(user_id=current_user.id, servergroup_id=server_group)
value = ''
for server in servers:
value += '{"id":"%s/%d","label":"%s","icon":"icon-%s","inode":true,"_type":"%s"},' % (NODE_TYPE, server.id, server.name, NODE_TYPE, NODE_TYPE)
value = value[:-1]
return value
def get_file_menu_items():
"""Return a (set) of dicts of file menu items, with name, priority, URL,
target and onclick code."""
return [
{'name': 'mnu_add_server', 'label': gettext('Add a server...'), 'priority': 50, 'url': '#', 'onclick': 'add_server()'},
{'name': 'mnu_delete_server', 'label': gettext('Delete server'), 'priority': 60, 'url': '#', 'onclick': 'delete_server()'},
{'name': 'mnu_rename_server', 'label': gettext('Rename server...'), 'priority': 70, 'url': '#', 'onclick': 'rename_server()'}
]
def get_context_menu_items():
"""Return a (set) of dicts of content menu items with name, node type, label, priority and JS"""
return [
{'name': 'delete_server', 'type': NODE_TYPE, 'label': gettext('Delete server'), 'priority': 50, 'onclick': 'delete_server(item);'},
{'name': 'rename_server', 'type': NODE_TYPE, 'label': gettext('Rename server...'), 'priority': 60, 'onclick': 'rename_server(item);'}
]
def get_script_snippets():
"""Return the script snippets needed to handle treeview node operations."""
return render_template('servers/servers.js')
def get_css_snippets():
"""Return the CSS needed to display the treeview node image."""
css = ".icon-server {\n"
css += " background: url('%s') 0 0 no-repeat !important;\n" % \
url_for('NODE-%s.static' % NODE_TYPE, filename='img/server.png')
css += "}\n"
return css

Binary file not shown.

After

Width:  |  Height:  |  Size: 553 B

View File

@ -0,0 +1,82 @@
// Add a server
function add_server() {
var alert = alertify.prompt(
'{{ _('Add a server') }}',
'{{ _('Enter a name for the new server') }}',
'',
function(evt, value) {
$.post("{{ url_for('NODE-server.add') }}", { 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'
}
tree.append(null, {
itemData: item
});
}
}
)
},
null
);
alert.show();
}
// Delete a server
function delete_server(item) {
alertify.confirm(
'{{ _('Delete server?') }}',
'{{ _('Are you sure you wish to delete the server "{0}"?') }}'.replace('{0}', tree.getLabel(item)),
function() {
var id = tree.getId(item)
$.post("{{ url_for('NODE-server.delete') }}", { id: id })
.done(function(data) {
if (data.success == 0) {
report_error(data.errormsg, data.info);
} else {
var next = tree.next(item);
var prev = tree.prev(item);
tree.remove(item);
if (next.length) {
tree.select(next);
} else if (prev.length) {
tree.select(prev);
}
}
}
)
},
null
)
}
// Rename a server
function rename_server(item) {
alertify.prompt(
'{{ _('Rename server') }}',
'{{ _('Enter a new name for the server') }}',
tree.getLabel(item),
function(evt, value) {
var id = tree.getId(item)
$.post("{{ url_for('NODE-server.rename') }}", { id: id, name: value })
.done(function(data) {
if (data.success == 0) {
report_error(data.errormsg, data.info);
} else {
tree.setLabel(item, { label: value });
}
}
)
},
null
)
}

View File

@ -0,0 +1,117 @@
##########################################################################
#
# 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 servers"""
from flask import Blueprint, Response, current_app, request
from flask.ext.babel import gettext
from flask.ext.security import current_user, login_required
from . import NODE_TYPE, NODE_PATH
from pgadmin.utils.ajax import make_json_response
from pgadmin.settings.settings_model import db, ServerGroup
import config
# Initialise the module
blueprint = Blueprint("NODE-" + NODE_TYPE, __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 node to the settings database"""
success = 1
errormsg = ''
data = { }
if request.form['name'] != '':
server = Server(user_id=current_user.id, name=request.form['name'])
try:
db.session.add(server)
db.session.commit()
except Exception as e:
success = 0
errormsg = e.message
else:
success = 0
errormsg = gettext('No server name was specified')
if success == 1:
data['id'] = server.id
data['name'] = server.name
return make_json_response(success=success,
errormsg=errormsg,
info=traceback.format_exc(),
result=request.form,
data=data)
@blueprint.route('/delete/', methods=['POST'])
@login_required
def delete():
"""Delete a server node in the settings database"""
success = 1
errormsg = ''
if request.form['id'] != '':
# There can be only one record at most
servergroup = Server.query.filter_by(user_id=current_user.id, id=int(request.form['id'])).first()
if server is None:
success = 0
errormsg = gettext('The specified server could not be found.')
else:
try:
db.session.delete(server)
db.session.commit()
except Exception as e:
success = 0
errormsg = e.message
else:
success = 0
errormsg = gettext('No server was specified.')
return make_json_response(success=success,
errormsg=errormsg,
info=traceback.format_exc(),
result=request.form)
@blueprint.route('/rename/', methods=['POST'])
@login_required
def rename():
"""Rename a server node in the settings database"""
success = 1
errormsg = ''
if request.form['id'] != '':
# There can be only one record at most
servergroup = Server.query.filter_by(user_id=current_user.id, id=int(request.form['id'])).first()
if server is None:
success = 0
errormsg = gettext('The specified server could not be found.')
else:
try:
server.name = request.form['name']
db.session.commit()
except Exception as e:
success = 0
errormsg = e.message
else:
success = 0
errormsg = gettext('No server was specified.')
return make_json_response(success=success,
errormsg=errormsg,
info=traceback.format_exc(),
result=request.form)

View File

Before

Width:  |  Height:  |  Size: 504 B

After

Width:  |  Height:  |  Size: 504 B

View File

@ -9,22 +9,40 @@
"""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.babel import gettext
from flask.ext.security import current_user, login_required
from utils.ajax import make_json_response
from . import NODE_TYPE, NODE_PATH, sub_nodes
from pgadmin.utils.ajax import make_json_response
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 = Blueprint("NODE-" + NODE_TYPE, __name__, static_folder='static', static_url_path='', template_folder='templates', url_prefix=NODE_PATH)
@blueprint.route("/<server_group>")
@login_required
def get_nodes(server_group):
"""Build a list of treeview nodes from the child nodes."""
value = '['
for node in sub_nodes:
if 'hooks' in dir(node) and 'get_nodes' in dir(node.hooks):
value += node.hooks.get_nodes(server_group) + ','
if value[-1:] == ',':
value = value[:-1]
value += ']'
resp = Response(response=value,
status=200,
mimetype="text/json")
return resp
@blueprint.route('/add/', methods=['POST'])
@login_required
def add():

View File

@ -170,10 +170,12 @@ ALTER TABLE tickets_detail \n\
};
{% if context_items is defined %}
{% for context_item in context_items %}
menu['{{ context_item.name }}'] = {
name: '{{ context_item.label }}',
callback: function() { {{ context_item.onclick }} }
};
if (tree.itemData(item)._type == '{{ context_item.type }}') {
menu['{{ context_item.name }}'] = {
name: '{{ context_item.label }}',
callback: function() { {{ context_item.onclick }} }
};
}
{% endfor %}{% endif %}
return {
autoHide: true,
@ -184,6 +186,7 @@ ALTER TABLE tickets_detail \n\
});
});
function report_error(message, info) {
text = '<div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">\

View File

@ -0,0 +1,53 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2015, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""Browser helper utilities"""
import os, sys
import config
def register_modules(app, file, all_nodes, sub_nodes, prefix):
"""Register any child node blueprints for the specified file"""
path = os.path.dirname(os.path.realpath(file))
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
# Construct the 'real' module name
if prefix != '':
f = prefix + '.' + f
# 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 node lists
all_nodes.append(node)
sub_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)

View File

@ -16,10 +16,12 @@ from flask.ext.security import login_required
from flask.ext.login import current_user
from inspect import getmoduleinfo, getmembers
from . import nodes
from . import sub_nodes
from pgadmin.browser import all_nodes
from pgadmin import modules
from pgadmin.settings import get_setting
import config
# Initialise the module
@ -47,7 +49,7 @@ def index():
stylesheets = [ ]
scripts = [ ]
modules_and_nodes = modules + nodes
modules_and_nodes = modules + all_nodes
# Add browser stylesheets
stylesheets.append(url_for('static', filename='css/codemirror/codemirror.css'))
@ -129,15 +131,13 @@ def index():
def browser_js():
"""Render and return JS snippets from the nodes and modules."""
snippets = ''
modules_and_nodes = modules + nodes
modules_and_nodes = modules + all_nodes
# Load the core browser code first
# Get the context menu items
context_items = [ ]
panel_items = [ ]
modules_and_nodes = modules + nodes
for module in modules_and_nodes:
# Get any context menu items
@ -174,7 +174,7 @@ def browser_js():
def browser_css():
"""Render and return CSS snippets from the nodes and modules."""
snippets = ''
modules_and_nodes = modules + nodes
modules_and_nodes = modules + all_nodes
for module in modules_and_nodes:
if 'hooks' in dir(module) and 'get_css_snippets' in dir(module.hooks):
@ -187,13 +187,13 @@ def browser_css():
return resp
@blueprint.route("/root-nodes.json")
@blueprint.route("/nodes/")
@login_required
def get_nodes():
"""Build a list of treeview nodes from the child modules."""
"""Build a list of treeview nodes from the child nodes."""
value = '['
for node in nodes:
for node in sub_nodes:
if 'hooks' in dir(node) and 'get_nodes' in dir(node.hooks):
value += node.hooks.get_nodes() + ','

View File

@ -8,14 +8,14 @@
##########################################################################
"""A blueprint module providing utility functions for the application."""
MODULE_NAME = 'utils'
MODULE_NAME = 'misc'
import config
from flask import Blueprint, render_template
from flask.ext.security import login_required
# Initialise the module
blueprint = Blueprint(MODULE_NAME, __name__, static_folder='static', template_folder='templates', url_prefix='/' + MODULE_NAME)
blueprint = Blueprint(MODULE_NAME, __name__, static_folder='static', template_folder='templates', url_prefix='')
##########################################################################
# A special URL used to "ping" the server

View File

@ -15,7 +15,7 @@ from flask import Blueprint, Response, abort, request, render_template
from flask.ext.security import login_required
import config
from utils.ajax import make_json_response
from pgadmin.utils.ajax import make_json_response
from . import get_setting, store_setting
# Initialise the module

View File