diff --git a/web/pgAdmin4.py b/web/pgAdmin4.py index 63710852e..9d2cff2dc 100644 --- a/web/pgAdmin4.py +++ b/web/pgAdmin4.py @@ -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()) - diff --git a/web/pgadmin/__init__.py b/web/pgadmin/__init__.py index ee8bc1200..2eb0982f4 100644 --- a/web/pgadmin/__init__.py +++ b/web/pgadmin/__init__.py @@ -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 - diff --git a/web/pgadmin/browser/__init__.py b/web/pgadmin/browser/__init__.py index 5442b7dc2..e340c836d 100644 --- a/web/pgadmin/browser/__init__.py +++ b/web/pgadmin/browser/__init__.py @@ -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 = [ ] \ No newline at end of file +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) diff --git a/web/pgadmin/browser/hooks.py b/web/pgadmin/browser/hooks.py deleted file mode 100644 index c38279566..000000000 --- a/web/pgadmin/browser/hooks.py +++ /dev/null @@ -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') diff --git a/web/pgadmin/browser/server_groups/__init__.py b/web/pgadmin/browser/server_groups/__init__.py index 95535f709..bfcc7f333 100644 --- a/web/pgadmin/browser/server_groups/__init__.py +++ b/web/pgadmin/browser/server_groups/__init__.py @@ -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 = [ ] \ No newline at end of file + + +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("/") +@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) diff --git a/web/pgadmin/browser/server_groups/hooks.py b/web/pgadmin/browser/server_groups/hooks.py deleted file mode 100644 index 413e0935e..000000000 --- a/web/pgadmin/browser/server_groups/hooks.py +++ /dev/null @@ -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 diff --git a/web/pgadmin/browser/server_groups/servers/__init__.py b/web/pgadmin/browser/server_groups/servers/__init__.py index a9e5e3c5b..855bb19b2 100644 --- a/web/pgadmin/browser/server_groups/servers/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/__init__.py @@ -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) diff --git a/web/pgadmin/browser/server_groups/servers/hooks.py b/web/pgadmin/browser/server_groups/servers/hooks.py deleted file mode 100644 index 30a7c5f2f..000000000 --- a/web/pgadmin/browser/server_groups/servers/hooks.py +++ /dev/null @@ -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 diff --git a/web/pgadmin/browser/server_groups/servers/views.py b/web/pgadmin/browser/server_groups/servers/views.py deleted file mode 100644 index fb38dcd09..000000000 --- a/web/pgadmin/browser/server_groups/servers/views.py +++ /dev/null @@ -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) - diff --git a/web/pgadmin/browser/server_groups/templates/server_groups/server_groups.js b/web/pgadmin/browser/server_groups/templates/server_groups/server_groups.js index 29b5449ec..f1fa85d8f 100644 --- a/web/pgadmin/browser/server_groups/templates/server_groups/server_groups.js +++ b/web/pgadmin/browser/server_groups/templates/server_groups/server_groups.js @@ -79,4 +79,4 @@ function rename_server_group(item) { }, null ) -} \ No newline at end of file +} diff --git a/web/pgadmin/browser/server_groups/views.py b/web/pgadmin/browser/server_groups/views.py deleted file mode 100644 index 5348819a5..000000000 --- a/web/pgadmin/browser/server_groups/views.py +++ /dev/null @@ -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("/") -@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) - diff --git a/web/pgadmin/browser/templates/browser/css/browser.css b/web/pgadmin/browser/templates/browser/css/browser.css new file mode 100644 index 000000000..9fb9ed46f --- /dev/null +++ b/web/pgadmin/browser/templates/browser/css/browser.css @@ -0,0 +1,3 @@ +{% for snip in snippets %} +{{ snip }} +{% endfor %} diff --git a/web/pgadmin/browser/templates/browser/css/node.css b/web/pgadmin/browser/templates/browser/css/node.css new file mode 100644 index 000000000..3fea07422 --- /dev/null +++ b/web/pgadmin/browser/templates/browser/css/node.css @@ -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; +} diff --git a/web/pgadmin/browser/templates/browser/index.html b/web/pgadmin/browser/templates/browser/index.html index 481d268f4..a3b63f67a 100644 --- a/web/pgadmin/browser/templates/browser/index.html +++ b/web/pgadmin/browser/templates/browser/index.html @@ -56,7 +56,7 @@
  • {{ management_item.label }}
  • {% endfor %} {% endif %} - + {% if help_items is defined and help_items|count > 0 %}