Introduced a PgAdmin class inherited from the Flask, which looks for

submodules inherited from the PgAdminModule instead of regular
Blueprint. This allows us to load the module automatically from the
under the pgadmin directory, and will work to extend the pgAdmin
extension module.

PgAdminModule is inherited from the Blueprint, and bring several
methods:
-  get_own_stylesheets, which returns the stylesheets used by the module
   (excluding its submodules stylesheets)
- get_own_javascripts
- menu_items, which returns a dictionray mapping the old hook names
  (context_items etc) to a list of MenuItem instances

For more specialized modules (as for now, any module that should be part
of the browser tree construction), one can define an abstract base class
defining additional methods.

For example, the BrowserPluginModule abstract base class defines the
following methods:
- jssnippets
- csssnipeets
- node_type
- get_nodes
This commit is contained in:
Ronan Dunklau
2015-06-29 12:28:41 +05:30
committed by Ashesh Vashi
parent 9e0b011ec8
commit eb6580b43a
25 changed files with 876 additions and 943 deletions

View File

@@ -7,7 +7,7 @@
#
##########################################################################
"""This is the main application entry point for pgAdmin 4. If running on
"""This is the main application entry point for pgAdmin 4. If running on
a webserver, this will provide the WSGI interface, otherwise, we're going
to start a web server."""
@@ -50,8 +50,11 @@ if not os.path.isfile(config.SQLITE_PATH):
# Create the app!
app = create_app()
#if config.DEBUG:
# app.debug = True
# 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
# runtime if we're running in desktop mode, otherwise we'll just use the
# Flask default.
if 'PGADMIN_PORT' in globals():
app.logger.debug('PGADMIN_PORT set in the runtime environment to %s', PGADMIN_PORT)
@@ -64,4 +67,3 @@ try:
app.run(port=server_port)
except IOError:
app.logger.error("Error starting the app server: %s", sys.exc_info())

View File

@@ -10,27 +10,50 @@
"""The main pgAdmin module. This handles the application initialisation tasks,
such as setup of logging, dynamic loading of modules etc."""
from flask import Flask, abort, request
from flask import Flask, abort, request, current_app
from flask.ext.babel import Babel
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.security import Security, SQLAlchemyUserDatastore, login_required
from flask.ext.security import Security, SQLAlchemyUserDatastore
from flask_security.utils import login_user
from flask_mail import Mail
from htmlmin.minify import html_minify
from settings.settings_model import db, Role, User
import inspect, imp, logging, os, sys
from importlib import import_module
from werkzeug.local import LocalProxy
from pgadmin.utils import PgAdminModule
from werkzeug.utils import find_modules
import sys
import logging
# Configuration settings
import config
# Global module list
modules = [ ]
class PgAdmin(Flask):
def find_submodules(self, basemodule):
for module_name in find_modules(basemodule, True):
if module_name in self.config['MODULE_BLACKLIST']:
self.logger.info('Skipping blacklisted module: %s' %
module_name)
continue
self.logger.info('Examining potential module: %s' % module_name)
module = import_module(module_name)
for key, value in module.__dict__.items():
if isinstance(value, PgAdminModule):
yield value
def _find_blueprint():
if request.blueprint:
return current_app.blueprints[request.blueprint]
current_blueprint = LocalProxy(_find_blueprint)
def create_app(app_name=config.APP_NAME):
"""Create the Flask application, startup logging and dynamically load
additional modules (blueprints) that are found in this directory."""
app = Flask(__name__, static_url_path='/static')
app = PgAdmin(__name__, static_url_path='/static')
app.config.from_object(config)
##########################################################################
@@ -42,7 +65,7 @@ def create_app(app_name=config.APP_NAME):
app.logger.setLevel(logging.DEBUG)
app.logger.handlers = []
# We also need to update the handler on the webserver in order to see request.
# We also need to update the handler on the webserver in order to see request.
# Setting the level prevents werkzeug from setting up it's own stream handler
# thus ensuring all the logging goes through the pgAdmin logger.
logger = logging.getLogger('werkzeug')
@@ -67,82 +90,48 @@ def create_app(app_name=config.APP_NAME):
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
##########################################################################
# Initialise i18n
babel = Babel(app)
app.logger.debug('Available translations: %s' % babel.list_translations())
@babel.localeselector
def get_locale():
"""Get the best language for the user."""
language = request.accept_languages.best_match(config.LANGUAGES.keys())
return language
return language
##########################################################################
# Setup authentication
##########################################################################
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + config.SQLITE_PATH.replace('\\', '/')
# Only enable password related functionality in server mode.
if config.SERVER_MODE == True:
if config.SERVER_MODE is True:
# TODO: Figure out how to disable /logout and /login
app.config['SECURITY_RECOVERABLE'] = True
app.config['SECURITY_CHANGEABLE'] = True
# Create database connection object and mailer
db.init_app(app)
mail = Mail(app)
Mail(app)
# Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)
Security(app, user_datastore)
##########################################################################
# Load plugin modules
##########################################################################
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.MODULE_BLACKLIST:
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
# the browser integration hooks and other similar functions.
app.logger.info('Examining potential module: %s' % d)
module = __import__(f, globals(), locals(), ['hooks', 'views'], -1)
# Add the module to the global module list
modules.append(module)
# Register the blueprint if present
if 'views' in dir(module) and 'blueprint' in dir(module.views):
app.logger.info('Registering blueprint module: %s' % f)
app.register_blueprint(module.views.blueprint)
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)
for module in app.find_submodules('pgadmin'):
app.logger.info('Registering blueprint module: %s' % module)
app.register_blueprint(module)
##########################################################################
# Handle the desktop login
@@ -151,7 +140,7 @@ def create_app(app_name=config.APP_NAME):
@app.before_request
def before_request():
"""Login the default user if running in desktop mode"""
if config.SERVER_MODE == False:
if config.SERVER_MODE is False:
user = user_datastore.get_user(config.DESKTOP_USER)
# Throw an error if we failed to find the desktop user, to give
@@ -165,7 +154,7 @@ def create_app(app_name=config.APP_NAME):
##########################################################################
# Minify output
##########################################################################
##########################################################################
@app.after_request
def response_minify(response):
"""Minify html response to decrease traffic"""
@@ -177,10 +166,16 @@ def create_app(app_name=config.APP_NAME):
return response
@app.context_processor
def inject_blueprint():
"""Inject a reference to the current blueprint, if any."""
return {
'current_blueprint': current_blueprint,
'menu_items': getattr(current_blueprint, "menu_items", {})}
##########################################################################
# All done!
##########################################################################
app.logger.debug('URL map: %s' % app.url_map)
return app

View File

@@ -6,7 +6,160 @@
# This software is released under the PostgreSQL Licence
#
##########################################################################
from abc import ABCMeta, abstractmethod, abstractproperty
from pgadmin import current_blueprint
from pgadmin.utils import PgAdminModule
from pgadmin.utils.ajax import make_json_response
from pgadmin.settings import get_setting
from flask import current_app, render_template, url_for, make_response
from flask.ext.security import login_required
from flask.ext.login import current_user
from flaskext.gravatar import Gravatar
# Define the node lists
all_nodes = [ ]
sub_nodes = [ ]
MODULE_NAME = 'browser'
class BrowserModule(PgAdminModule):
def get_own_stylesheets(self):
stylesheets = []
# Add browser stylesheets
for (endpoint, filename) in [
('static', 'css/codemirror/codemirror.css'),
('static', 'css/wcDocker/theme.css'),
('static', 'css/jQuery-contextMenu/jquery.contextMenu.css'),
('browser.static', 'css/browser.css'),
('browser.static', 'css/aciTree/css/aciTree.css')
]:
stylesheets.append(url_for(endpoint, filename=filename))
stylesheets.append(url_for('browser.browser_css'))
if current_app.debug:
stylesheets.append(url_for('static', filename='css/wcDocker/wcDockerSkeleton.css'))
else:
stylesheets.append(url_for('static', filename='css/wcDocker/wcDockerSkeleton.min.css'))
return stylesheets
def get_own_javascripts(self):
scripts = []
for (endpoint, filename) in [
('static', 'js/codemirror/codemirror.js'),
('static', 'js/codemirror/mode/sql.js'),
('static', 'js/jQuery-contextMenu/jquery.ui.position.js'),
('static', 'js/jQuery-contextMenu/jquery.contextMenu.js'),
('browser.static', 'js/aciTree/jquery.aciPlugin.min.js'),
('browser.static', 'js/aciTree/jquery.aciTree.dom.js'),
('browser.static', 'js/aciTree/jquery.aciTree.min.js')]:
scripts.append(url_for(endpoint, filename=filename))
scripts.append(url_for('browser.browser_js'))
if current_app.debug:
scripts.append(url_for(
'static',
filename='js/wcDocker/wcDocker.js'))
else:
scripts.append(url_for(
'static',
filename='js/wcDocker/wcDocker.min.js'))
return scripts
blueprint = BrowserModule(MODULE_NAME, __name__)
class BrowserPluginModule(PgAdminModule):
"""
Base class for browser submodules.
"""
__metaclass__ = ABCMeta
def __init__(self, import_name, **kwargs):
kwargs.setdefault("url_prefix", self.node_path)
kwargs.setdefault("static_url_path", '')
super(BrowserPluginModule, self).__init__("NODE-%s" % self.node_type,
import_name,
**kwargs)
@property
@abstractmethod
def jssnippets(self):
"""
Returns a snippet of javascript to include in the page
"""
# TODO: move those methods to BrowserModule subclass ?
return []
@property
def csssnippets(self):
"""
Returns a snippet of css to include in the page
"""
# TODO: move those methods to BrowserModule subclass ?
return render_template("browser/css/node.css",
node_type=self.node_type)
@abstractmethod
def get_nodes(self):
"""
Each browser module is responsible for fetching
its own tree subnodes.
"""
return []
@abstractproperty
def node_type(self):
pass
@property
def node_path(self):
return '/browser/nodes/' + self.node_type
@blueprint.route("/")
@login_required
def index():
"""Render and process the main browser window."""
# Get the Gravatar
gravatar = Gravatar(current_app,
size=100,
rating='g',
default='retro',
force_default=False,
use_ssl=False,
base_url=None)
return render_template(MODULE_NAME + "/index.html",
username=current_user.email)
@blueprint.route("/browser.js")
@login_required
def browser_js():
layout = get_setting('Browser/Layout', default='')
snippets = []
for submodule in current_blueprint.submodules:
snippets.extend(submodule.jssnippets)
return make_response(render_template(
'browser/js/browser.js',
layout=layout,
jssnippets=snippets),
200, {'Content-Type': 'application/x-javascript'})
@blueprint.route("/browser.css")
@login_required
def browser_css():
"""Render and return CSS snippets from the nodes and modules."""
snippets = []
for submodule in current_blueprint.submodules:
snippets.extend(submodule.csssnippets)
return make_response(render_template('browser/css/browser.css',
snippets=snippets),
200, {'Content-Type': 'text/css'})
@blueprint.route("/nodes/")
@login_required
def get_nodes():
"""Build a list of treeview nodes from the child nodes."""
nodes = []
for submodule in current_blueprint.submodules:
nodes.extend(submodule.get_nodes())
return make_json_response(data=nodes)

View File

@@ -1,21 +0,0 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2015, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""Browser application hooks"""
import os, sys
import config
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"""
register_modules(app, __file__, all_nodes, sub_nodes, 'pgadmin.browser')

View File

@@ -6,10 +6,202 @@
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""Defines views for management of server groups"""
# Node meta data
NODE_TYPE = 'server-group'
NODE_PATH = '/browser/nodes/' + NODE_TYPE
from abc import ABCMeta, abstractmethod
import traceback
from flask import Blueprint, Response, current_app, request, render_template
from flask.ext.babel import gettext
from flask.ext.security import current_user, login_required
from pgadmin import current_blueprint
from pgadmin.utils.ajax import make_json_response
from pgadmin.browser import BrowserPluginModule
from pgadmin.utils.menu import MenuItem
from pgadmin.settings.settings_model import db, ServerGroup
import config
# Define the child node list
sub_nodes = [ ]
class ServerGroupModule(BrowserPluginModule):
NODE_TYPE = "server-group"
def get_own_menuitems(self):
return {
'standard_items': [
ServerGroupMenuItem(action="drop", priority=10, function="drop_server_group"),
ServerGroupMenuItem(action="rename", priority=10, function="rename_server_group")
],
'create_items': [
ServerGroupMenuItem(name="create_server_group",
label=gettext('Server Group...'),
priority=10,
function="create_server_group",
types=[self.node_type])
],
'context_items': [
ServerGroupMenuItem(name="delete_server_group",
label=gettext('Delete server group'),
priority=10,
onclick='drop_server_group(item);'),
ServerGroupMenuItem(name="rename_server_group",
label=gettext('Rename server group...'),
priority=10,
onclick='rename_server_group(item);')
]
}
@property
def jssnippets(self):
snippets = [render_template("server_groups/server_groups.js")]
for module in self.submodules:
snippets.extend(module.jssnippets)
return snippets
def get_nodes(self, **kwargs):
"""Return a JSON document listing the server groups for the user"""
groups = ServerGroup.query.filter_by(user_id=current_user.id)
# TODO: Move this JSON generation to a Server method
# this code is duplicated somewhere else
for group in groups:
yield {
"id": "%s/%d" % (self.node_type, group.id),
"label": group.name,
"icon": "icon-%s" % self.node_type,
"inode": True,
"_type": self.node_type
}
@property
def node_type(self):
return self.NODE_TYPE
class ServerGroupMenuItem(MenuItem):
def __init__(self, **kwargs):
kwargs.setdefault("type", ServerGroupModule.NODE_TYPE)
super(ServerGroupMenuItem, self).__init__(**kwargs)
class ServerGroupPluginModule(BrowserPluginModule):
"""
Base class for server group plugins.
"""
__metaclass__ = ABCMeta
@abstractmethod
def get_nodes(self, servergroup):
pass
# Initialise the module
blueprint = ServerGroupModule( __name__, static_url_path='')
@blueprint.route("/<server_group>")
@login_required
def get_nodes(server_group):
"""Build a list of treeview nodes from the child nodes."""
nodes = []
for module in current_blueprint.submodules:
nodes.extend(module.get_nodes(server_group=server_group))
return make_json_response(data=nodes)
@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 = gettext('No server group name was specified')
if success == 1:
data['id'] = servergroup.id
data['name'] = servergroup.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 group node in the settings database"""
success = 1
errormsg = ''
if request.form['id'] != '':
# There can be only one record at most
servergroup = ServerGroup.query.filter_by(user_id=current_user.id, id=int(request.form['id'])).first()
if servergroup is None:
success = 0
errormsg = gettext('The specified server group could not be found.')
else:
try:
db.session.delete(servergroup)
db.session.commit()
except Exception as e:
success = 0
errormsg = e.message
else:
success = 0
errormsg = gettext('No server group 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 group node in the settings database"""
success = 1
errormsg = ''
if request.form['id'] != '':
# There can be only one record at most
servergroup = ServerGroup.query.filter_by(user_id=current_user.id, id=int(request.form['id'])).first()
if servergroup is None:
success = 0
errormsg = gettext('The specified server group could not be found.')
else:
try:
servergroup.name = request.form['name']
db.session.commit()
except Exception as e:
success = 0
errormsg = e.message
else:
success = 0
errormsg = gettext('No server group was specified.')
return make_json_response(success=success,
errormsg=errormsg,
info=traceback.format_exc(),
result=request.form)

View File

@@ -1,78 +0,0 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2015, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""Integration hooks for server groups."""
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)
# TODO: Move this JSON generation to a Server method
# this code is duplicated somewhere else
for group in groups:
yield {
"id": "%s/%d" % (NODE_TYPE, group.id),
"label": group.name,
"icon": "icon-%s" % NODE_TYPE,
"inode": True,
"_type": NODE_TYPE
}
def get_standard_menu_items():
"""Return a (set) of dicts of standard menu items (create/drop/rename), with
object type, action and the function name (no parens) to call on click."""
return [
{'type': 'server-group', 'action': 'drop', 'priority': 10, 'function': 'drop_server_group'},
{'type': 'server-group', 'action': 'rename', 'priority': 20, 'function': 'rename_server_group'}
]
def get_create_menu_items():
"""Return a (set) of dicts of create menu items, with a Javascript array of
object types on which the option should appear, name, label and the function
name (no parens) to call on click."""
return [
{'type': "['server-group']", 'name': 'create_server_group', 'label': gettext('Server Group...'), 'priority': 10, 'function': 'create_server_group'}
]
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_group', 'type': NODE_TYPE, 'label': gettext('Delete server group'), 'priority': 10, 'onclick': 'drop_server_group(item);'},
{'name': 'rename_server_group', 'type': NODE_TYPE, 'label': gettext('Rename server group...'), 'priority': 20, 'onclick': 'rename_server_group(item);'}
]
def get_script_snippets():
"""Return the script snippets needed to handle treeview node operations."""
return render_template('server_groups/server_groups.js')
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-%s.static' % NODE_TYPE, filename='img/server-group.png')
css += "}\n"
return css

View File

@@ -6,11 +6,167 @@
# This software is released under the PostgreSQL Licence
#
##########################################################################
from flask import render_template, request
from pgadmin.browser.server_groups import ServerGroupPluginModule
from flask.ext.security import login_required, current_user
from pgadmin.settings.settings_model import db, Server
from pgadmin.utils.menu import MenuItem
from pgadmin.utils.ajax import make_json_response
import traceback
from flask.ext.babel import gettext
# Node meta data
NODE_TYPE = 'server'
NODE_PATH = '/browser/nodes/' + NODE_TYPE
# Define the child node list
sub_nodes = [ ]
class ServerModule(ServerGroupPluginModule):
NODE_TYPE = "server"
@property
def node_type(self):
return self.NODE_TYPE
def get_nodes(self, 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)
# TODO: Move this JSON generation to a Server method
for server in servers:
yield {
"id": "%s/%d" % (NODE_TYPE, server.id),
"label": server.name,
"icon": "icon-%s" % NODE_TYPE,
"inode": True,
"_type": NODE_TYPE
}
def get_own_menuitems(self):
return {
'standard_items': [
ServerMenuItem(action="drop", priority=50, function='drop_server'),
ServerMenuItem(action="rename", priority=50, function='rename_server')
],
'create_items': [
ServerMenuItem(types=["server-group", "server"],
name="create_server",
label=gettext('Server...'),
priority=50,
function='create_server')
],
'context_items': [
ServerMenuItem(name='delete_server',
label=gettext('Delete server'),
priority=50,
onclick='drop_server'),
ServerMenuItem(name='rename_server',
label=gettext('Rename server...'),
priority=60,
onclick='rename_server(item);')
]
}
@property
def jssnippets(self):
return [render_template("servers/servers.js")]
class ServerMenuItem(MenuItem):
def __init__(self, **kwargs):
kwargs.setdefault("type", ServerModule.NODE_TYPE)
super(ServerMenuItem, self).__init__(**kwargs)
blueprint = ServerModule(__name__)
@blueprint.route('/add/', methods=['POST'])
@login_required
def add():
"""Add a server node to the settings database"""
success = 1
errormsg = ''
data = {}
success = False
errormsg = ''
if request.form['name'] != '':
server = Server(user_id=current_user.id, name=request.form['name'])
try:
db.session.add(server)
db.session.commit()
success = True
except Exception as e:
errormsg = e.message
else:
errormsg = gettext('No server name was specified')
if success:
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

@@ -1,72 +0,0 @@
##########################################################################
#
# 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)
# TODO: Move this JSON generation to a Server method
for server in servers:
yield {
"id": "%s/%d" % (NODE_TYPE, server.id),
"label": server.name,
"icon": "icon-%s" % NODE_TYPE,
"inode": True,
"_type": NODE_TYPE
}
def get_standard_menu_items():
"""Return a (set) of dicts of standard menu items (create/drop/rename), with
object type, action, priority and the function to call on click."""
return [
{'type': 'server', 'action': 'drop', 'priority': 50, 'function': 'drop_server'},
{'type': 'server', 'action': 'rename', 'priority': 60, 'function': 'rename_server'}
]
def get_create_menu_items():
"""Return a (set) of dicts of create menu items, with a Javascript array of
object types on which the option should appear, name, label, priority and
the function name (no parens) to call on click."""
return [
{'type': "['server-group', 'server']", 'name': 'create_server', 'label': gettext('Server...'), 'priority': 50, 'function': 'create_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': 'drop_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

View File

@@ -1,121 +0,0 @@
##########################################################################
#
# 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, 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, Server
import traceback
# 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 = {}
success = False
errormsg = ''
if request.form['name'] != '':
server = Server(user_id=current_user.id, name=request.form['name'])
try:
db.session.add(server)
db.session.commit()
success = True
except Exception as e:
errormsg = e.message
else:
errormsg = gettext('No server name was specified')
if success:
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

@@ -79,4 +79,4 @@ function rename_server_group(item) {
},
null
)
}
}

View File

@@ -1,129 +0,0 @@
##########################################################################
#
# 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"""
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 . 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_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."""
nodes = []
for node in sub_nodes:
if hasattr(node, 'hooks') and hasattr(node.hooks, 'get_nodes'):
nodes.extend(node.hooks.get_nodes(server_group))
return make_json_response(data=nodes)
@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 = gettext('No server group name was specified')
if success == 1:
data['id'] = servergroup.id
data['name'] = servergroup.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 group node in the settings database"""
success = 1
errormsg = ''
if request.form['id'] != '':
# There can be only one record at most
servergroup = ServerGroup.query.filter_by(user_id=current_user.id, id=int(request.form['id'])).first()
if servergroup is None:
success = 0
errormsg = gettext('The specified server group could not be found.')
else:
try:
db.session.delete(servergroup)
db.session.commit()
except Exception as e:
success = 0
errormsg = e.message
else:
success = 0
errormsg = gettext('No server group 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 group node in the settings database"""
success = 1
errormsg = ''
if request.form['id'] != '':
# There can be only one record at most
servergroup = ServerGroup.query.filter_by(user_id=current_user.id, id=int(request.form['id'])).first()
if servergroup is None:
success = 0
errormsg = gettext('The specified server group could not be found.')
else:
try:
servergroup.name = request.form['name']
db.session.commit()
except Exception as e:
success = 0
errormsg = e.message
else:
success = 0
errormsg = gettext('No server group was specified.')
return make_json_response(success=success,
errormsg=errormsg,
info=traceback.format_exc(),
result=request.form)

View File

@@ -0,0 +1,3 @@
{% for snip in snippets %}
{{ snip }}
{% endfor %}

View File

@@ -0,0 +1,3 @@
.icon-{{node_type}} {
background: url('{{ url_for('NODE-%s.static' % node_type, filename='img/%s.png' % node_type )}}') 0 0 no-repeat;
}

View File

@@ -56,7 +56,7 @@
<li><a id="{{ management_item.name }}" href="{{ management_item.url }}"{% if management_item.target %} target="{{ management_item.target }}"{% endif %}{% if management_item.onclick %} onclick="{{ management_item.onclick|safe }}"{% endif %}>{{ management_item.label }}</a></li>{% endfor %}
</ul>
</li>{% endif %}
{% if help_items is defined and help_items|count > 0 %}<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{ _('Help') }} <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">{% for help_item in help_items %}

View File

@@ -1,21 +1,21 @@
// Page globals
var docker
var editor
var tree
var dashboardPanel
var propertiesPanel
var statisticsPanel
var dependenciesPanel
var dependentsPanel
var sqlPanel
var browserPanel
var docker;
var editor;
var tree;
var dashboardPanel;
var propertiesPanel;
var statisticsPanel;
var dependenciesPanel;
var dependentsPanel;
var sqlPanel;
var browserPanel;
// Store the main browser layout
$(window).bind('unload', function() {
$(window).bind('unload', function() {
state = docker.save();
settings = { setting: "Browser/Layout",
settings = { setting: "Browser/Layout",
value: state }
$.post("{{ url_for('settings.store') }}", settings);
return true
@@ -28,8 +28,8 @@ function buildPanel(docker, name, title, width, height, showTitle, isCloseable,
isPrivate: isPrivate,
onCreate: function(myPanel) {
myPanel.initSize(width, height);
if (showTitle == false)
if (showTitle == false)
myPanel.title(false);
myPanel.closeable(isCloseable);
@@ -46,8 +46,8 @@ function buildIFramePanel(docker, name, title, width, height, showTitle, isClose
isPrivate: isPrivate,
onCreate: function(myPanel) {
myPanel.initSize(width, height);
if (showTitle == false)
if (showTitle == false)
myPanel.title(false);
myPanel.closeable(isCloseable);
@@ -87,7 +87,7 @@ function report_error(message, info) {
<div class="panel-body" style="overflow: scroll;">' + message + '</div>\
</div>\
</div>'
if (info != null && info != '') {
text += '<div class="panel panel-default">\
<div class="panel-heading" role="tab" id="headingTwo">\
@@ -103,9 +103,9 @@ function report_error(message, info) {
</div>\
</div>'
}
text += '</div>'
alertify.alert(
'{{ _('An error has occurred') }}',
text
@@ -115,35 +115,35 @@ function report_error(message, info) {
// Enable/disable menu options
function enable_disable_menus() {
// Disable everything first
$("#mnu_create").html('<li class="menu-item disabled"><a href="#">{{ _('No object selected') }}</a></li>\n');
$("#mnu_drop_object").addClass("mnu-disabled");
$("#mnu_rename_object").addClass("mnu-disabled");
$("#mnu_drop_object").addClass("mnu-disabled");
$("#mnu_rename_object").addClass("mnu-disabled");
node_type = get_selected_node_type()
// List the possible standard items, their types and actions
var handlers = [{% if standard_items is defined %}{% for standard_item in standard_items %}
"{{ standard_item.type }}:{{ standard_item.action }}",{% endfor %}{% endif %}
var handlers = [{% for standard_item in menu_items.standard_items %}
"{{ standard_item.type }}:{{ standard_item.action }}",{% endfor %}
]
// Check if we have a matching action for the object type in the list, and
// if so, enable the menu item
if ($.inArray(node_type + ":drop", handlers) >= 0)
$("#mnu_drop_object").removeClass("mnu-disabled");
$("#mnu_drop_object").removeClass("mnu-disabled");
if ($.inArray(node_type + ":rename", handlers) >= 0)
$("#mnu_rename_object").removeClass("mnu-disabled");
// List the possibe create items
var creators = [{% if create_items is defined %}{% for create_item in create_items %}
[{{ create_item.type }}, "{{ create_item.name }}", "{{ create_item.label }}", "{{ create_item.function }}"],{% endfor %}{% endif %}
var creators = [{% for create_item in menu_items.create_items %}
[{{ create_item.types | tojson }}, "{{ create_item.name }}", "{{ create_item.label }}", "{{ create_item.function }}"],{% endfor %}
]
// Loop through the list of creators and add links for any that apply to this
// node type to the Create menu's UL element
items = ''
for (i = 0; i < creators.length; ++i) {
if ($.inArray(node_type, creators[i][0]) >= 0) {
items = items + '<li class="menu-item"><a href="#" onclick="' + creators[i][3] + '()">' + creators[i][2] + '</a></li>\n'
@@ -158,22 +158,22 @@ function get_selected_node_type() {
item = tree.selected()
if (!item || item.length != 1)
return "";
return tree.itemData(tree.selected())._type;
}
// Create a new object of the type currently selected
function create_object() {
node_type = get_selected_node_type()
if (node_type == "")
return;
switch(node_type) {
{% if standard_items is defined %}{% for standard_item in standard_items %}{% if standard_item.action == 'create' %}
{% for standard_item in menu_items.standard_items %}{% if standard_item.action == 'create' %}
case '{{ standard_item.type }}':
{{ standard_item.function }}()
break;
{% endif %}{% endfor %}{% endif %}
{% endif %}{% endfor %}
}
}
@@ -182,13 +182,13 @@ function drop_object() {
node_type = get_selected_node_type()
if (node_type == "")
return;
switch(node_type) {
{% if standard_items is defined %}{% for standard_item in standard_items %}{% if standard_item.action == 'drop' %}
{% for standard_item in menu_items.standard_items %}{% if standard_item.action == 'drop' %}
case '{{ standard_item.type }}':
{{ standard_item.function }}(tree.selected())
break;
{% endif %}{% endfor %}{% endif %}
{% endif %}{% endfor %}
}
}
@@ -197,13 +197,13 @@ function rename_object() {
node_type = get_selected_node_type()
if (node_type == "")
return;
switch(node_type) {
{% if standard_items is defined %}{% for standard_item in standard_items %}{% if standard_item.action == 'rename' %}
{% for standard_item in menu_items.standard_items %}{% if standard_item.action == 'rename' %}
case '{{ standard_item.type }}':
{{ standard_item.function }}(tree.selected())
break;
{% endif %}{% endfor %}{% endif %}
{% endif %}{% endfor %}
}
}
@@ -237,37 +237,37 @@ WITH ( \n\
); \n\
ALTER TABLE tickets_detail \n\
OWNER TO helpdesk;\n';
buildPanel(docker, 'pnl_browser', '{{ _('Browser') }}', 300, 600, false, false, true,
buildPanel(docker, 'pnl_browser', '{{ _('Browser') }}', 300, 600, false, false, true,
'<div id="tree" class="aciTree">')
buildIFramePanel(docker, 'pnl_dashboard', '{{ _('Dashboard') }}', 500, 600, true, false, true,
buildIFramePanel(docker, 'pnl_dashboard', '{{ _('Dashboard') }}', 500, 600, true, false, true,
'http://www.pgadmin.org/')
buildPanel(docker, 'pnl_properties', '{{ _('Properties') }}', 500, 600, true, false, true,
buildPanel(docker, 'pnl_properties', '{{ _('Properties') }}', 500, 600, true, false, true,
'<p>Properties pane</p>')
buildPanel(docker, 'pnl_sql', '{{ _('SQL') }}', 500, 600, true, false, true,
buildPanel(docker, 'pnl_sql', '{{ _('SQL') }}', 500, 600, true, false, true,
'<textarea id="sql-textarea" name="sql-textarea">' + demoSql + '</textarea>')
buildPanel(docker, 'pnl_statistics', '{{ _('Statistics') }}', 500, 600, true, false, true,
buildPanel(docker, 'pnl_statistics', '{{ _('Statistics') }}', 500, 600, true, false, true,
'<p>Statistics pane</p>')
buildPanel(docker, 'pnl_dependencies', '{{ _('Dependencies') }}', 500, 600, true, false, true,
buildPanel(docker, 'pnl_dependencies', '{{ _('Dependencies') }}', 500, 600, true, false, true,
'<p>Depedencies pane</p>')
buildPanel(docker, 'pnl_dependents', '{{ _('Dependents') }}', 500, 600, true, false, true,
buildPanel(docker, 'pnl_dependents', '{{ _('Dependents') }}', 500, 600, true, false, true,
'<p>Dependents pane</p>')
// Add hooked-in panels
{% if panel_items is defined and panel_items|count > 0 %}{% for panel_item in panel_items %}{% if panel_item.isIframe %}
buildIFramePanel(docker, '{{ panel_item.name }}', '{{ panel_item.title }}',
{{ panel_item.width }}, {{ panel_item.height }},
{{ panel_item.showTitle|lower }}, {{ panel_item.isCloseable|lower }},
{% for panel_item in menu_items.panel_items %}{% if panel_item.isIframe %}
buildIFramePanel(docker, '{{ panel_item.name }}', '{{ panel_item.title }}',
{{ panel_item.width }}, {{ panel_item.height }},
{{ panel_item.showTitle|lower }}, {{ panel_item.isCloseable|lower }},
{{ panel_item.isPrivate|lower }}, '{{ panel_item.content }}')
{% else %}
buildPanel(docker, '{{ panel_item.name }}', '{{ panel_item.title }}',
{{ panel_item.width }}, {{ panel_item.height }},
{{ panel_item.showTitle|lower }}, {{ panel_item.isCloseable|lower }},
{{ panel_item.isPrivate|lower }}, '{{ panel_item.content }}')
{% endif %}{% endfor %}{% endif %}
buildPanel(docker, '{{ panel_item.name }}', '{{ panel_item.title }}',
{{ panel_item.width }}, {{ panel_item.height }},
{{ panel_item.showTitle|lower }}, {{ panel_item.isCloseable|lower }},
{{ panel_item.isPrivate|lower }}, '{{ panel_item.content }}')
{% endif %}{% endfor %}
var layout = '{{ layout }}';
// Try to restore the layout if there is one
if (layout != '') {
try {
@@ -281,7 +281,7 @@ ALTER TABLE tickets_detail \n\
buildDefaultLayout()
}
}
// Syntax highlight the SQL Pane
editor = CodeMirror.fromTextArea(document.getElementById("sql-textarea"), {
lineNumbers: true,
@@ -298,7 +298,7 @@ ALTER TABLE tickets_detail \n\
return $.parseJSON(payload).data;
}
}
},
}
});
tree = $('#tree').aciTree('api');
@@ -310,22 +310,20 @@ ALTER TABLE tickets_detail \n\
var menu = { };
var createMenu = { };
{% if create_items is defined %}
{% for create_item in create_items %}
if ($.inArray(tree.itemData(item)._type, {{ create_item.type }}) >= 0) {
{% for create_item in menu_items.create_items %}
if ($.inArray(tree.itemData(item)._type, {{ create_item.types | tojson }}) >= 0) {
createMenu['{{ create_item.name }}'] = { name: '{{ create_item.label }}', callback: function() { {{ create_item.function }}() }};
}
{% endfor %}{% endif %}
menu["create"] = { "name": "Create" }
{% endfor %}
menu["create"] = { "name": "Create" }
menu["create"]["items"] = createMenu
{% if context_items is defined %}
{% for context_item in context_items %}
{% for context_item in menu_items.context_items %}
if (tree.itemData(item)._type == '{{ context_item.type }}') {
menu['{{ context_item.name }}'] = { name: '{{ context_item.label }}', callback: function() { {{ context_item.onclick }} }};
}
{% endfor %}{% endif %}
{% endfor %}
return {
autoHide: true,
items: menu,
@@ -333,7 +331,7 @@ ALTER TABLE tickets_detail \n\
};
}
});
// Treeview event handler
$('#tree').on('acitree', function(event, api, item, eventName, options){
switch (eventName){
@@ -343,9 +341,11 @@ ALTER TABLE tickets_detail \n\
}
});
// Setup the menus
enable_disable_menus()
});
{% for snippet in jssnippets %}
{{ snippet }}
{% endfor %}

View File

@@ -1,214 +0,0 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2015, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""A blueprint module implementing the core pgAdmin browser."""
MODULE_NAME = 'browser'
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 sub_nodes
from pgadmin.browser import all_nodes
from pgadmin import modules
from pgadmin.settings import get_setting
from pgadmin.utils.ajax import make_json_response
import config
# Initialise the module
blueprint = Blueprint(MODULE_NAME, __name__, static_folder='static', template_folder='templates', url_prefix='/' + MODULE_NAME)
@blueprint.route("/")
@login_required
def index():
"""Render and process the main browser window."""
# Get the Gravatar
gravatar = Gravatar(current_app,
size=100,
rating='g',
default='retro',
force_default=False,
use_ssl=False,
base_url=None)
# Get the plugin elements from the module
file_items = [ ]
edit_items = [ ]
tools_items = [ ]
management_items = [ ]
help_items = [ ]
stylesheets = [ ]
scripts = [ ]
modules_and_nodes = modules + all_nodes
# Add browser stylesheets
stylesheets.append(url_for('static', filename='css/codemirror/codemirror.css'))
if config.DEBUG:
stylesheets.append(url_for('static', filename='css/wcDocker/wcDockerSkeleton.css'))
else:
stylesheets.append(url_for('static', filename='css/wcDocker/wcDockerSkeleton.min.css'))
stylesheets.append(url_for('static', filename='css/wcDocker/theme.css'))
stylesheets.append(url_for('static', filename='css/jQuery-contextMenu/jquery.contextMenu.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'))
if config.DEBUG:
scripts.append(url_for('static', filename='js/wcDocker/wcDocker.js'))
else:
scripts.append(url_for('static', filename='js/wcDocker/wcDocker.min.js'))
scripts.append(url_for('static', filename='js/jQuery-contextMenu/jquery.ui.position.js'))
scripts.append(url_for('static', filename='js/jQuery-contextMenu/jquery.contextMenu.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_and_nodes:
# Get the edit 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 '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 'hooks' in dir(module) and 'get_tools_menu_items' in dir(module.hooks):
tools_items.extend(module.hooks.get_tools_menu_items())
# Get the management menu items
if 'hooks' in dir(module) and 'get_management_menu_items' in dir(module.hooks):
management_items.extend(module.hooks.get_management_menu_items())
# Get the 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 'hooks' in dir(module) and 'get_stylesheets' in dir(module.hooks):
stylesheets += module.hooks.get_stylesheets()
# Get any 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'])
tools_items = sorted(tools_items, key=lambda k: k['priority'])
management_items = sorted(management_items, key=lambda k: k['priority'])
help_items = sorted(help_items, key=lambda k: k['priority'])
return render_template(MODULE_NAME + '/index.html',
username=current_user.email,
file_items=file_items,
edit_items=edit_items,
tools_items=tools_items,
management_items=management_items,
help_items=help_items,
stylesheets = stylesheets,
scripts = scripts)
@blueprint.route("/browser.js")
@login_required
def browser_js():
"""Render and return JS snippets from the nodes and modules."""
snippets = ''
modules_and_nodes = modules + all_nodes
# Load the core browser code first
# Get the context menu items
standard_items = [ ]
create_items = [ ]
context_items = [ ]
panel_items = [ ]
for module in modules_and_nodes:
# Get any standard menu items
if 'hooks' in dir(module) and 'get_standard_menu_items' in dir(module.hooks):
standard_items.extend(module.hooks.get_standard_menu_items())
# Get any create menu items
if 'hooks' in dir(module) and 'get_create_menu_items' in dir(module.hooks):
create_items.extend(module.hooks.get_create_menu_items())
# Get any context menu items
if 'hooks' in dir(module) and 'get_context_menu_items' in dir(module.hooks):
context_items.extend(module.hooks.get_context_menu_items())
# Get any panels
if 'hooks' in dir(module) and 'get_panels' in dir(module.hooks):
panel_items += module.hooks.get_panels()
standard_items = sorted(standard_items, key=lambda k: k['priority'])
create_items = sorted(create_items, key=lambda k: k['priority'])
context_items = sorted(context_items, key=lambda k: k['priority'])
panel_items = sorted(panel_items, key=lambda k: k['priority'])
layout = get_setting('Browser/Layout', default='')
snippets += render_template('browser/js/browser.js',
layout = layout,
standard_items = standard_items,
create_items = create_items,
context_items = context_items,
panel_items = panel_items)
# Add module and node specific code
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 + all_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("/nodes/")
@login_required
def get_nodes():
"""Build a list of treeview nodes from the child nodes."""
value = '['
nodes = []
for node in sub_nodes:
if hasattr(node, 'hooks') and hasattr(node.hooks, 'get_nodes'):
nodes.extend(node.hooks.get_nodes())
return make_json_response(data=nodes)

View File

@@ -0,0 +1,25 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2015, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""A blueprint module providing utility functions for the application."""
MODULE_NAME = 'misc'
from pgadmin.utils import PgAdminModule
# Initialise the module
blueprint = PgAdminModule(MODULE_NAME, __name__,
url_prefix='')
##########################################################################
# A special URL used to "ping" the server
##########################################################################
@blueprint.route("/ping")
def ping():
"""Generate a "PING" response to indicate that the server is alive."""
return "PING"

View File

@@ -1,27 +0,0 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2015, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""A blueprint module providing utility functions for the application."""
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='')
##########################################################################
# A special URL used to "ping" the server
##########################################################################
@blueprint.route("/ping")
def ping():
"""Generate a "PING" response to indicate that the server is alive."""
return "PING"

View File

@@ -0,0 +1,19 @@
from pgadmin import PgAdminModule
from flask.ext.security import login_required
from flask import redirect, url_for
MODULE_NAME = 'redirects'
blueprint = PgAdminModule(MODULE_NAME, __name__,
url_prefix='/')
@blueprint.route('/')
@login_required
def index():
"""Redirect users hitting the root to the browser"""
return redirect(url_for('browser.index'))
@blueprint.route('/favicon.ico')
def favicon():
"""Redirect to the favicon"""
return redirect(url_for('static', filename='favicon.ico'))

View File

@@ -1,29 +0,0 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2015, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""A blueprint module providing URL redirects."""
MODULE_NAME = 'redirects'
import config
from flask import Blueprint, redirect, url_for
from flask.ext.security import login_required
# Initialise the module
blueprint = Blueprint(MODULE_NAME, __name__)
@blueprint.route('/')
@login_required
def index():
"""Redirect users hitting the root to the browser"""
return redirect(url_for('browser.index'))
@blueprint.route('/favicon.ico')
def favicon():
"""Redirect to the favicon"""
return redirect(url_for('static', filename='favicon.ico'))

View File

@@ -14,20 +14,95 @@ from flask.ext.login import current_user
from flask.ext.sqlalchemy import SQLAlchemy
from settings_model import db, Setting
import traceback
from flask import Blueprint, Response, abort, request, render_template
from flask.ext.security import login_required
import config
from pgadmin.utils.ajax import make_json_response
from pgadmin.utils import PgAdminModule
MODULE_NAME = 'settings'
def store_setting(setting, value):
"""Set a configuration setting for the current user."""
data = Setting(user_id=current_user.id, setting=setting, value=value)
db.session.merge(data)
db.session.commit()
def get_setting(setting, default=None):
"""Retrieve a configuration setting for the current user, or return the
"""Retrieve a configuration setting for the current user, or return the
default value specified by the caller."""
data = Setting.query.filter_by(user_id=current_user.id, setting=setting).first()
if not data or data.value is None:
return default
else:
return data.value
return data.value
# Initialise the module
blueprint = PgAdminModule(MODULE_NAME, __name__, template_folder='templates', url_prefix='/' + MODULE_NAME)
@blueprint.route("/settings.js")
@login_required
def script():
"""Render the required Javascript"""
return Response(response=render_template("settings/settings.js"),
status=200,
mimetype="application/javascript")
@blueprint.route("/store", methods=['POST'])
@blueprint.route("/store/<setting>/<value>", methods=['GET'])
@login_required
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."""
success = 1
errorcode = 0
errormsg = ''
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
return make_json_response(success=success,
errormsg=errormsg,
info=traceback.format_exc(),
result=request.form)
@blueprint.route("/get", methods=['POST'])
@blueprint.route("/get/<setting>", methods=['GET'])
@blueprint.route("/get/<setting>/<default>", methods=['GET'])
@login_required
def get(setting=None, default=None):
"""Get a configuration setting."""
if request.method == 'POST':
setting = request.form['setting']
default = request.form['default']
success = 1
errorcode = 0
errormsg = ''
try:
value = get_setting(setting, default)
except Exception as e:
success = 0
errormsg = e.message
return make_json_response(success=success,
errormsg=errormsg,
info=traceback.format_exc(),
result=request.form)

View File

@@ -1,83 +0,0 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2015, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""Views for setting and storing configuration options."""
MODULE_NAME = 'settings'
import traceback
from flask import Blueprint, Response, abort, request, render_template
from flask.ext.security import login_required
import config
from pgadmin.utils.ajax import make_json_response
from . import get_setting, store_setting
# Initialise the module
blueprint = Blueprint(MODULE_NAME, __name__, template_folder='templates', url_prefix='/' + MODULE_NAME)
@blueprint.route("/settings.js")
@login_required
def script():
"""Render the required Javascript"""
return Response(response=render_template("settings/settings.js"),
status=200,
mimetype="application/javascript")
@blueprint.route("/store", methods=['POST'])
@blueprint.route("/store/<setting>/<value>", methods=['GET'])
@login_required
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."""
success = 1
errorcode = 0
errormsg = ''
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
return make_json_response(success=success,
errormsg=errormsg,
info=traceback.format_exc(),
result=request.form)
@blueprint.route("/get", methods=['POST'])
@blueprint.route("/get/<setting>", methods=['GET'])
@blueprint.route("/get/<setting>/<default>", methods=['GET'])
@login_required
def get(setting=None, default=None):
"""Get a configuration setting."""
if request.method == 'POST':
setting = request.form['setting']
default = request.form['default']
success = 1
errorcode = 0
errormsg = ''
try:
value = get_setting(setting, default)
except Exception as e:
success = 0
errormsg = e.message
return make_json_response(success=success,
errormsg=errormsg,
info=traceback.format_exc(),
result=request.form)

View File

@@ -20,23 +20,21 @@
{% if config.DEBUG %}<link rel="stylesheet" href="{{ url_for('static', filename='css/alertifyjs/themes/bootstrap.css') }}" />{% else %}<link rel="stylesheet" href="{{ url_for('static', filename='css/alertifyjs/themes/bootstrap.min.css') }}" />{% endif %}
{% if config.DEBUG %}<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap-theme.min.css') }}">{% else %}<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap-theme.min.css') }}">{% endif %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/overrides.css') }}">
{% if stylesheets is defined %}
<!-- View specified stylesheets -->
{% for stylesheet in stylesheets %}
<link rel="stylesheet" href="{{ stylesheet }}">{% endfor %}
{% endif %}
{% for stylesheet in current_blueprint.stylesheets %}
<link rel="stylesheet" href="{{ stylesheet }}">
{% endfor %}
<!-- Base template scripts -->
<script src="{{ url_for('static', filename='js/modernizr-2.6.2-respond-1.1.0.min.js') }}"></script>
{% if config.DEBUG %}<script src="{{ url_for('static', filename='js/jquery-1.11.2.js') }}">{% else %}<script src="{{ url_for('static', filename='js/jquery-1.11.2.min.js') }}">{% endif %}</script>
{% if config.DEBUG %}<script src="{{ url_for('static', filename='js/bootstrap.js') }}">{% else %}<script src="{{ url_for('static', filename='js/bootstrap.min.js') }}">{% endif %}</script>
{% if config.DEBUG %}<script src="{{ url_for('static', filename='js/alertifyjs/alertify.js') }}">{% else %}<script src="{{ url_for('static', filename='js/alertifyjs/alertify.min.js') }}">{% endif %}</script>
<script src="{{ url_for('static', filename='js/alertifyjs/pgadmin.defaults.js') }}"></script>
{% if scripts is defined %}
<!-- View specified scripts -->
{% for script in scripts %}
<script src="{{ script }}"></script>{% endfor %}
{% endif %}
{% for script in current_blueprint.javascripts %}
<script src="{{ script }}"></script>
{% endfor %}
</head>
<body>
<!--[if lt IE 7]>

View File

@@ -0,0 +1,80 @@
from flask import Blueprint
from collections import defaultdict
from operator import attrgetter
import sys
class PgAdminModule(Blueprint):
"""
Base class for every PgAdmin Module.
This class defines a set of method and attributes that
every module should implement.
"""
def __init__(self, name, import_name, **kwargs):
kwargs.setdefault('url_prefix', '/' + name)
kwargs.setdefault('template_folder', 'templates')
kwargs.setdefault('static_folder', 'static')
self.submodules = []
super(PgAdminModule, self).__init__(name, import_name, **kwargs)
def register(self, app, options, first_registration=False):
"""
Override the default register function to automagically register
sub-modules at once.
"""
if first_registration:
self.submodules = list(app.find_submodules(self.import_name))
super(PgAdminModule, self).register(app, options, first_registration)
for module in self.submodules:
app.register_blueprint(module)
def get_own_stylesheets(self):
"""
Returns:
list: the stylesheets used by this module, not including any
stylesheet needed by the submodules.
"""
return []
def get_own_javascripts(self):
"""
Returns:
list: the javascripts used by this module, not including
any script needed by the submodules.
"""
return []
def get_own_menuitems(self):
"""
Returns:
dict: the menuitems for this module, not including
any needed from the submodules.
"""
return defaultdict(list)
@property
def stylesheets(self):
stylesheets = self.get_own_stylesheets()
for module in self.submodules:
stylesheets.extend(module.stylesheets)
return stylesheets
@property
def javascripts(self):
javascripts = self.get_own_javascripts()
for module in self.submodules:
javascripts.extend(module.javascripts)
return javascripts
@property
def menu_items(self):
menu_items = self.get_own_menuitems()
for module in self.submodules:
for key, value in module.menu_items.items():
menu_items[key].extend(value)
menu_items = {key: sorted(values, key=attrgetter('priority'))
for key, values in menu_items.items()}
return menu_items

View File

@@ -0,0 +1,6 @@
from collections import namedtuple
class MenuItem(object):
def __init__(self, **kwargs):
self.__dict__.update(**kwargs)