mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Added support for the infrastructure for on demand access/create the
server connection. The BaseDriver and BaseConnection are two abstract classes, which allows us to replace the existing driver with the currently used. The current implementation supports to connect the PostgreSQL and Postgres Plus Advanced Server using the psycopg2 driver.
This commit is contained in:
parent
b52d72f176
commit
e27e39a8f3
@ -25,3 +25,4 @@ pytz==2014.10
|
||||
six==1.9.0
|
||||
speaklater==1.3
|
||||
wsgiref==0.1.2
|
||||
pycrypto==2.6.1
|
||||
|
@ -7,7 +7,7 @@
|
||||
# Copyright (C) 2013 - 2015, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
# config.py - Core application configuration settings
|
||||
# config.py - Core application configuration settings
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
@ -34,7 +34,7 @@ APP_SUFFIX = 'dev'
|
||||
# Copyright string for display in the app
|
||||
APP_COPYRIGHT = 'Copyright 2014 - 2015, The pgAdmin Development Team'
|
||||
|
||||
# Path to the online help.
|
||||
# Path to the online help.
|
||||
HELP_PATH = '../../../docs/en_US/_build/html/'
|
||||
|
||||
# Languages we support in the UI
|
||||
@ -49,11 +49,11 @@ APP_VERSION = '%s.%s.%s-%s' % (APP_MAJOR, APP_MINOR, APP_REVISION, APP_SUFFIX)
|
||||
|
||||
# DO NOT CHANGE UNLESS YOU KNOW WHAT YOU ARE DOING!
|
||||
# List of modules to skip when dynamically loading
|
||||
MODULE_BLACKLIST = [ 'test' ]
|
||||
MODULE_BLACKLIST = ['test']
|
||||
|
||||
# DO NOT CHANGE UNLESS YOU KNOW WHAT YOU ARE DOING!
|
||||
# List of treeview browser nodes to skip when dynamically loading
|
||||
NODE_BLACKLIST = [ ]
|
||||
NODE_BLACKLIST = []
|
||||
|
||||
##########################################################################
|
||||
# Log settings
|
||||
@ -63,19 +63,19 @@ NODE_BLACKLIST = [ ]
|
||||
DEBUG = False
|
||||
|
||||
# Application log level - one of:
|
||||
# CRITICAL 50
|
||||
# ERROR 40
|
||||
# WARNING 30
|
||||
# SQL 25
|
||||
# INFO 20
|
||||
# DEBUG 10
|
||||
# NOTSET 0
|
||||
# CRITICAL 50
|
||||
# ERROR 40
|
||||
# WARNING 30
|
||||
# SQL 25
|
||||
# INFO 20
|
||||
# DEBUG 10
|
||||
# NOTSET 0
|
||||
CONSOLE_LOG_LEVEL = WARNING
|
||||
FILE_LOG_LEVEL = INFO
|
||||
|
||||
# Log format.
|
||||
CONSOLE_LOG_FORMAT='%(asctime)s: %(levelname)s\t%(name)s:\t%(message)s'
|
||||
FILE_LOG_FORMAT='%(asctime)s: %(levelname)s\t%(name)s:\t%(message)s'
|
||||
# Log format.
|
||||
CONSOLE_LOG_FORMAT = '%(asctime)s: %(levelname)s\t%(name)s:\t%(message)s'
|
||||
FILE_LOG_FORMAT = '%(asctime)s: %(levelname)s\t%(name)s:\t%(message)s'
|
||||
|
||||
# Log file name
|
||||
LOG_FILE = 'pgadmin4.log'
|
||||
@ -84,6 +84,8 @@ LOG_FILE = 'pgadmin4.log'
|
||||
# Server settings
|
||||
##########################################################################
|
||||
|
||||
PG_DEFAULT_DRIVER = 'psycopg2'
|
||||
|
||||
# The server mode determines whether or not we're running on a web server
|
||||
# requiring user authentication, or desktop mode which uses an automatic
|
||||
# default login.
|
||||
@ -102,11 +104,11 @@ DEFAULT_SERVER_PORT = 5050
|
||||
# Enable CSRF protection?
|
||||
CSRF_ENABLED = True
|
||||
|
||||
# Secret key for signing CSRF data. Override this in config_local.py if
|
||||
# Secret key for signing CSRF data. Override this in config_local.py if
|
||||
# running on a web server
|
||||
CSRF_SESSION_KEY = 'SuperSecret1'
|
||||
|
||||
# Secret key for signing cookies. Override this in config_local.py if
|
||||
# Secret key for signing cookies. Override this in config_local.py if
|
||||
# running on a web server
|
||||
SECRET_KEY = 'SuperSecret2'
|
||||
|
||||
@ -118,7 +120,7 @@ SECURITY_PASSWORD_SALT = 'SuperSecret3'
|
||||
SECURITY_PASSWORD_HASH = 'pbkdf2_sha512'
|
||||
|
||||
# Should HTML be minified on the fly when not in debug mode?
|
||||
MINIFY_HTML = True;
|
||||
MINIFY_HTML = True
|
||||
|
||||
##########################################################################
|
||||
# User account and settings storage
|
||||
@ -126,12 +128,15 @@ MINIFY_HTML = True;
|
||||
|
||||
# The schema version number for the configuration database
|
||||
# DO NOT CHANGE UNLESS YOU ARE A PGADMIN DEVELOPER!!
|
||||
SETTINGS_SCHEMA_VERSION = 3
|
||||
SETTINGS_SCHEMA_VERSION = 7
|
||||
|
||||
# The default path to the SQLite database used to store user accounts and
|
||||
# settings. This default places the file in the same directory as this
|
||||
# config file, but generates an absolute path for use througout the app.
|
||||
SQLITE_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'pgadmin4.db')
|
||||
SQLITE_PATH = os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)),
|
||||
'pgadmin4.db'
|
||||
)
|
||||
|
||||
##########################################################################
|
||||
# Mail server settings
|
||||
@ -150,9 +155,12 @@ MAIL_PASSWORD = 'SuperSecret'
|
||||
##########################################################################
|
||||
|
||||
# These settings define the content of password reset emails
|
||||
SECURITY_EMAIL_SUBJECT_PASSWORD_RESET = "Password reset instructions for %s" % APP_NAME
|
||||
SECURITY_EMAIL_SUBJECT_PASSWORD_NOTICE = "Your %s password has been reset" % APP_NAME
|
||||
SECURITY_EMAIL_SUBJECT_PASSWORD_CHANGE_NOTICE = "Your password for %s has been changed" % APP_NAME
|
||||
SECURITY_EMAIL_SUBJECT_PASSWORD_RESET = "Password reset instructions for %s" \
|
||||
% APP_NAME
|
||||
SECURITY_EMAIL_SUBJECT_PASSWORD_NOTICE = "Your %s password has been reset" \
|
||||
% APP_NAME
|
||||
SECURITY_EMAIL_SUBJECT_PASSWORD_CHANGE_NOTICE = \
|
||||
"Your password for %s has been changed" % APP_NAME
|
||||
|
||||
##########################################################################
|
||||
# Local config settings
|
||||
|
@ -41,9 +41,11 @@ if config.SERVER_MODE is True:
|
||||
|
||||
# Check if the database exists. If it does not, tell the user and exit.
|
||||
if not os.path.isfile(config.SQLITE_PATH):
|
||||
print "The configuration database %s does not exist.\n" % config.SQLITE_PATH
|
||||
print "The configuration database %s does not exist.\n" \
|
||||
% config.SQLITE_PATH
|
||||
print "Please run 'python %s' to create it.\nExiting..." % os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)), 'setup.py')
|
||||
os.path.dirname(os.path.realpath(__file__)), 'setup.py'
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
##########################################################################
|
||||
@ -55,6 +57,8 @@ app = create_app()
|
||||
|
||||
if config.DEBUG:
|
||||
app.debug = True
|
||||
else:
|
||||
app.debug = False
|
||||
|
||||
# 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
|
||||
@ -70,6 +74,6 @@ else:
|
||||
server_port = config.DEFAULT_SERVER_PORT
|
||||
|
||||
try:
|
||||
app.run(port=server_port)
|
||||
app.run(port=server_port, use_reloader=(config.SERVER_MODE and app.debug))
|
||||
except IOError:
|
||||
app.logger.error("Error starting the app server: %s", sys.exc_info())
|
||||
|
@ -19,7 +19,7 @@ from htmlmin.minify import html_minify
|
||||
from settings.settings_model import db, Role, User, Version
|
||||
from importlib import import_module
|
||||
from werkzeug.local import LocalProxy
|
||||
from pgadmin.utils import PgAdminModule
|
||||
from pgadmin.utils import PgAdminModule, driver
|
||||
from werkzeug.utils import find_modules
|
||||
import sys
|
||||
import os
|
||||
@ -34,8 +34,9 @@ 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)
|
||||
self.logger.info(
|
||||
'Skipping blacklisted module: %s' % module_name
|
||||
)
|
||||
continue
|
||||
self.logger.info('Examining potential module: %s' % module_name)
|
||||
module = import_module(module_name)
|
||||
@ -82,6 +83,7 @@ class PgAdmin(Flask):
|
||||
for key, values in menu_items.items()}
|
||||
return menu_items
|
||||
|
||||
|
||||
def _find_blueprint():
|
||||
if request.blueprint:
|
||||
return current_app.blueprints[request.blueprint]
|
||||
@ -104,9 +106,10 @@ 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.
|
||||
# Setting the level prevents werkzeug from setting up it's own stream handler
|
||||
# thus ensuring all the logging goes through the pgAdmin logger.
|
||||
# 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')
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
@ -125,9 +128,9 @@ def create_app(app_name=config.APP_NAME):
|
||||
logger.addHandler(ch)
|
||||
|
||||
# Log the startup
|
||||
app.logger.info('################################################################################')
|
||||
app.logger.info('########################################################')
|
||||
app.logger.info('Starting %s v%s...', config.APP_NAME, config.APP_VERSION)
|
||||
app.logger.info('################################################################################')
|
||||
app.logger.info('########################################################')
|
||||
app.logger.debug("Python syspath: %s", sys.path)
|
||||
|
||||
##########################################################################
|
||||
@ -149,7 +152,9 @@ def create_app(app_name=config.APP_NAME):
|
||||
# Setup authentication
|
||||
##########################################################################
|
||||
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + config.SQLITE_PATH.replace('\\', '/')
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///{0}'.format(
|
||||
config.SQLITE_PATH.replace('\\', '/')
|
||||
)
|
||||
|
||||
# Only enable password related functionality in server mode.
|
||||
if config.SERVER_MODE is True:
|
||||
@ -200,7 +205,10 @@ def create_app(app_name=config.APP_NAME):
|
||||
# the sysadmin a hint. We'll continue to try to login anyway as
|
||||
# that'll through a nice 500 error for us.
|
||||
if user is None:
|
||||
app.logger.error('The desktop user %s was not found in the configuration database.' % config.DESKTOP_USER)
|
||||
app.logger.error(
|
||||
'The desktop user %s was not found in the configuration database.'
|
||||
% config.DESKTOP_USER
|
||||
)
|
||||
abort(401)
|
||||
|
||||
login_user(user)
|
||||
@ -224,11 +232,15 @@ def create_app(app_name=config.APP_NAME):
|
||||
"""Inject a reference to the current blueprint, if any."""
|
||||
return {
|
||||
'current_app': current_app,
|
||||
'current_blueprint': current_blueprint }
|
||||
'current_blueprint': current_blueprint
|
||||
}
|
||||
|
||||
# Load all available serve drivers
|
||||
driver.init_app(app)
|
||||
|
||||
##########################################################################
|
||||
# All done!
|
||||
##########################################################################
|
||||
|
||||
app.logger.debug('URL map: %s' % app.url_map)
|
||||
|
||||
return app
|
||||
|
@ -62,13 +62,13 @@ def index():
|
||||
info['app_mode'] = gettext('Desktop')
|
||||
info['current_user'] = current_user.email
|
||||
|
||||
return render_template(MODULE_NAME + '/index.html', info=info)
|
||||
return render_template(MODULE_NAME + '/index.html', info=info, _=gettext)
|
||||
|
||||
|
||||
@blueprint.route("/about.js")
|
||||
@login_required
|
||||
def script():
|
||||
"""render the required javascript"""
|
||||
return Response(response=render_template("about/about.js"),
|
||||
return Response(response=render_template("about/about.js", _=gettext),
|
||||
status=200,
|
||||
mimetype="application/javascript")
|
||||
|
@ -14,36 +14,38 @@ 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 flask.ext.babel import gettext
|
||||
from flaskext.gravatar import Gravatar
|
||||
|
||||
MODULE_NAME = 'browser'
|
||||
|
||||
class BrowserModule(PgAdminModule):
|
||||
|
||||
class BrowserModule(PgAdminModule):
|
||||
|
||||
def get_own_stylesheets(self):
|
||||
stylesheets = []
|
||||
# Add browser stylesheets
|
||||
for (endpoint, filename) in [
|
||||
('static', 'css/codemirror/codemirror.css'),
|
||||
('static', 'css/jQuery-contextMenu/jquery.contextMenu.css'),
|
||||
('static', 'css/wcDocker/wcDocker.css' if \
|
||||
current_app.debug else \
|
||||
'css/wcDocker/wcDocker.min.css'),
|
||||
('browser.static', 'css/browser.css'),
|
||||
('browser.static', 'css/aciTree/css/aciTree.css')
|
||||
]:
|
||||
('static', 'css/codemirror/codemirror.css'),
|
||||
('static', 'css/jQuery-contextMenu/jquery.contextMenu.css'),
|
||||
('static', 'css/wcDocker/wcDocker.css' if current_app.debug
|
||||
else 'css/wcDocker/wcDocker.min.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'))
|
||||
return stylesheets
|
||||
|
||||
|
||||
def get_own_javascripts(self):
|
||||
scripts = list()
|
||||
scripts.append({
|
||||
'name': 'alertify',
|
||||
'path': url_for('static', filename='js/alertifyjs/alertify' if current_app.debug \
|
||||
else 'js/alertifyjs/alertify.min'),
|
||||
'path': url_for(
|
||||
'static',
|
||||
filename='js/alertifyjs/alertify' if current_app.debug
|
||||
else 'js/alertifyjs/alertify.min'
|
||||
),
|
||||
'exports': 'alertify',
|
||||
'preloaded': True
|
||||
})
|
||||
@ -62,8 +64,10 @@ class BrowserModule(PgAdminModule):
|
||||
})
|
||||
scripts.append({
|
||||
'name': 'jqueryui.position',
|
||||
'path': url_for('static',
|
||||
filename='js/jQuery-contextMenu/jquery.ui.position'),
|
||||
'path': url_for(
|
||||
'static',
|
||||
filename='js/jQuery-contextMenu/jquery.ui.position'
|
||||
),
|
||||
'deps': ['jquery'],
|
||||
'exports': 'jQuery.ui.position',
|
||||
'preloaded': True
|
||||
@ -71,35 +75,42 @@ class BrowserModule(PgAdminModule):
|
||||
})
|
||||
scripts.append({
|
||||
'name': 'jquery.contextmenu',
|
||||
'path': url_for('static',
|
||||
filename='js/jQuery-contextMenu/jquery.contextMenu'),
|
||||
'path': url_for(
|
||||
'static',
|
||||
filename='js/jQuery-contextMenu/jquery.contextMenu'
|
||||
),
|
||||
'deps': ['jquery', 'jqueryui.position'],
|
||||
'exports': 'jQuery.contextMenu',
|
||||
'preloaded': True
|
||||
})
|
||||
scripts.append({
|
||||
'name': 'jquery.aciplugin',
|
||||
'path': url_for('browser.static',
|
||||
filename='js/aciTree/jquery.aciPlugin.min'),
|
||||
'path': url_for(
|
||||
'browser.static',
|
||||
filename='js/aciTree/jquery.aciPlugin.min'
|
||||
),
|
||||
'deps': ['jquery'],
|
||||
'exports': 'aciPluginClass',
|
||||
'preloaded': True
|
||||
})
|
||||
scripts.append({
|
||||
'name': 'jquery.acitree',
|
||||
'path': url_for('browser.static',
|
||||
filename='js/aciTree/jquery.aciTree' \
|
||||
if current_app.debug else \
|
||||
'js/aciTree/jquery.aciTree.min'),
|
||||
'path': url_for(
|
||||
'browser.static',
|
||||
filename='js/aciTree/jquery.aciTree' if
|
||||
current_app.debug else 'js/aciTree/jquery.aciTree.min'
|
||||
),
|
||||
'deps': ['jquery', 'jquery.aciplugin'],
|
||||
'exports': 'aciPluginClass.plugins.aciTree',
|
||||
'preloaded': True
|
||||
})
|
||||
scripts.append({
|
||||
'name': 'wcdocker',
|
||||
'path': url_for('static',
|
||||
filename='js/wcDocker/wcDocker' if current_app.debug else \
|
||||
'js/wcDocker/wcDocker.min'),
|
||||
'path': url_for(
|
||||
'static',
|
||||
filename='js/wcDocker/wcDocker' if current_app.debug
|
||||
else 'js/wcDocker/wcDocker.min'
|
||||
),
|
||||
'deps': ['jquery.contextmenu'],
|
||||
'exports': '',
|
||||
'preloaded': True
|
||||
@ -109,8 +120,11 @@ class BrowserModule(PgAdminModule):
|
||||
['pgadmin.browser', 'js/browser'],
|
||||
['pgadmin.browser.error', 'js/error'],
|
||||
['pgadmin.browser.node', 'js/node']]:
|
||||
scripts.append({ 'name': name,
|
||||
'path': url_for('browser.index') + script, 'preloaded': True })
|
||||
scripts.append({
|
||||
'name': name,
|
||||
'path': url_for('browser.index') + script,
|
||||
'preloaded': True
|
||||
})
|
||||
|
||||
for name, end in [
|
||||
['pgadmin.browser.menu', 'js/menu'],
|
||||
@ -128,6 +142,7 @@ class BrowserModule(PgAdminModule):
|
||||
|
||||
blueprint = BrowserModule(MODULE_NAME, __name__)
|
||||
|
||||
|
||||
class BrowserPluginModule(PgAdminModule):
|
||||
"""
|
||||
Base class for browser submodules.
|
||||
@ -139,21 +154,19 @@ class BrowserPluginModule(PgAdminModule):
|
||||
def __init__(self, import_name, **kwargs):
|
||||
kwargs.setdefault("url_prefix", self.node_path)
|
||||
kwargs.setdefault("static_url_path", '/static')
|
||||
super(BrowserPluginModule, self).__init__("NODE-%s" % self.node_type,
|
||||
super(BrowserPluginModule, self).__init__(
|
||||
"NODE-%s" % self.node_type,
|
||||
import_name,
|
||||
**kwargs)
|
||||
|
||||
**kwargs
|
||||
)
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def jssnippets(self):
|
||||
"""
|
||||
Returns a snippet of javascript to include in the page
|
||||
"""
|
||||
# TODO: move those methods to BrowserModule subclass ?
|
||||
return []
|
||||
|
||||
|
||||
def get_own_javascripts(self):
|
||||
scripts = []
|
||||
|
||||
@ -168,17 +181,21 @@ class BrowserPluginModule(PgAdminModule):
|
||||
|
||||
return scripts
|
||||
|
||||
|
||||
def generate_browser_node(self, node_id, parent_id, label, icon, inode):
|
||||
def generate_browser_node(
|
||||
self, node_id, parent_id, label, icon, inode, node_type, **kwargs
|
||||
):
|
||||
obj = {
|
||||
"id": "%s/%s" % (self.node_type, node_id),
|
||||
"label": label,
|
||||
"icon": icon,
|
||||
"inode": inode,
|
||||
"_type": self.node_type,
|
||||
"_id": node_id,
|
||||
"refid": parent_id
|
||||
}
|
||||
"id": "%s/%s" % (node_type, node_id),
|
||||
"label": label,
|
||||
"icon": icon,
|
||||
"inode": inode,
|
||||
"_type": node_type,
|
||||
"_id": node_id,
|
||||
"refid": parent_id,
|
||||
"module": 'pgadmin.node.%s' % node_type
|
||||
}
|
||||
for key in kwargs:
|
||||
obj.setdefault(key, kwargs[key])
|
||||
return obj
|
||||
|
||||
|
||||
@ -187,15 +204,18 @@ class BrowserPluginModule(PgAdminModule):
|
||||
"""
|
||||
Returns a snippet of css to include in the page
|
||||
"""
|
||||
snippets = [render_template("browser/css/node.css",
|
||||
node_type=self.node_type)]
|
||||
snippets = [
|
||||
render_template(
|
||||
"browser/css/node.css",
|
||||
node_type=self.node_type,
|
||||
_=gettext
|
||||
)]
|
||||
|
||||
for submodule in self.submodules:
|
||||
snippets.extend(submodule.csssnippets)
|
||||
|
||||
return snippets
|
||||
|
||||
|
||||
@abstractmethod
|
||||
def get_nodes(self):
|
||||
"""
|
||||
@ -223,8 +243,7 @@ class BrowserPluginModule(PgAdminModule):
|
||||
|
||||
@property
|
||||
def node_path(self):
|
||||
return url_for('browser.index') + 'nodes/' + self.node_type
|
||||
|
||||
return self.browser_url_prefix + self.node_type
|
||||
|
||||
@property
|
||||
def javascripts(self):
|
||||
@ -243,8 +262,12 @@ def index():
|
||||
force_default=False,
|
||||
use_ssl=False,
|
||||
base_url=None)
|
||||
return render_template(MODULE_NAME + "/index.html",
|
||||
username=current_user.email)
|
||||
return render_template(
|
||||
MODULE_NAME + "/index.html",
|
||||
username=current_user.email,
|
||||
_=gettext
|
||||
)
|
||||
|
||||
|
||||
@blueprint.route("/js/browser.js")
|
||||
@login_required
|
||||
@ -257,21 +280,25 @@ def browser_js():
|
||||
render_template(
|
||||
'browser/js/browser.js',
|
||||
layout=layout,
|
||||
jssnippets=snippets),
|
||||
jssnippets=snippets,
|
||||
_=gettext
|
||||
),
|
||||
200, {'Content-Type': 'application/x-javascript'})
|
||||
|
||||
|
||||
@blueprint.route("/js/error.js")
|
||||
@login_required
|
||||
def error_js():
|
||||
return make_response(
|
||||
render_template('browser/js/error.js'),
|
||||
render_template('browser/js/error.js', _=gettext),
|
||||
200, {'Content-Type': 'application/x-javascript'})
|
||||
|
||||
|
||||
@blueprint.route("/js/node.js")
|
||||
@login_required
|
||||
def node_js():
|
||||
return make_response(
|
||||
render_template('browser/js/node.js'),
|
||||
render_template('browser/js/node.js', _=gettext),
|
||||
200, {'Content-Type': 'application/x-javascript'})
|
||||
|
||||
|
||||
@ -283,7 +310,9 @@ def browser_css():
|
||||
for submodule in blueprint.submodules:
|
||||
snippets.extend(submodule.csssnippets)
|
||||
return make_response(
|
||||
render_template('browser/css/browser.css', snippets=snippets),
|
||||
render_template(
|
||||
'browser/css/browser.css', snippets=snippets, _=gettext
|
||||
),
|
||||
200, {'Content-Type': 'text/css'})
|
||||
|
||||
|
||||
|
@ -20,6 +20,7 @@ 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
|
||||
from pgadmin.browser.utils import NodeView
|
||||
|
||||
|
||||
class ServerGroupModule(BrowserPluginModule):
|
||||
@ -30,13 +31,14 @@ class ServerGroupModule(BrowserPluginModule):
|
||||
"""Return a JSON document listing the server groups for the user"""
|
||||
groups = ServerGroup.query.filter_by(user_id=current_user.id)
|
||||
for group in groups:
|
||||
group = self.generate_browser_node(
|
||||
yield self.generate_browser_node(
|
||||
"%d" % (group.id),
|
||||
None,
|
||||
group.name,
|
||||
"icon-%s" % self.node_type,
|
||||
True)
|
||||
yield group
|
||||
True,
|
||||
self.node_type
|
||||
)
|
||||
|
||||
@property
|
||||
def node_type(self):
|
||||
@ -46,10 +48,6 @@ class ServerGroupModule(BrowserPluginModule):
|
||||
def script_load(self):
|
||||
return None
|
||||
|
||||
@property
|
||||
def node_path(self):
|
||||
return BrowserPluginModule.browser_url_prefix + self.node_type
|
||||
|
||||
|
||||
class ServerGroupMenuItem(MenuItem):
|
||||
|
||||
@ -65,29 +63,19 @@ class ServerGroupPluginModule(BrowserPluginModule):
|
||||
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
|
||||
@abstractmethod
|
||||
def get_nodes(self, *arg, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
@property
|
||||
def node_path(self):
|
||||
return BrowserPluginModule.browser_url_prefix + self.node_type
|
||||
|
||||
|
||||
blueprint = ServerGroupModule( __name__, static_url_path='')
|
||||
|
||||
# Initialise the module
|
||||
from pgadmin.browser.utils import NodeView
|
||||
blueprint = ServerGroupModule(__name__, static_url_path='')
|
||||
|
||||
|
||||
class ServerGroupView(NodeView):
|
||||
|
||||
node_type = ServerGroupModule.NODE_TYPE
|
||||
parent_ids = []
|
||||
ids = [{'type':'int', 'id':'gid'}]
|
||||
|
||||
ids = [{'type': 'int', 'id': 'gid'}]
|
||||
|
||||
def list(self):
|
||||
res = []
|
||||
@ -95,7 +83,6 @@ class ServerGroupView(NodeView):
|
||||
res.append(g)
|
||||
return make_json_response(result=res)
|
||||
|
||||
|
||||
def delete(self, gid):
|
||||
"""Delete a server group node in the settings database"""
|
||||
|
||||
@ -108,18 +95,22 @@ class ServerGroupView(NodeView):
|
||||
return make_json_response(
|
||||
status=417,
|
||||
success=0,
|
||||
errormsg=gettext('The specified server group could not be found.'))
|
||||
errormsg=gettext(
|
||||
'The specified server group could not be found.'
|
||||
)
|
||||
)
|
||||
else:
|
||||
try:
|
||||
for sg in servergroup:
|
||||
db.session.delete(sg)
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
return make_json_response(status=410, success=0, errormsg=e.message)
|
||||
return make_json_response(
|
||||
status=410, success=0, errormsg=e.message
|
||||
)
|
||||
|
||||
return make_json_response(result=request.form)
|
||||
|
||||
|
||||
def update(self, gid):
|
||||
"""Update the server-group properties"""
|
||||
|
||||
@ -134,18 +125,22 @@ class ServerGroupView(NodeView):
|
||||
return make_json_response(
|
||||
status=417,
|
||||
success=0,
|
||||
errormsg=gettext('The specified server group could not be found.'))
|
||||
errormsg=gettext(
|
||||
'The specified server group could not be found.'
|
||||
)
|
||||
)
|
||||
else:
|
||||
try:
|
||||
if u'name' in data:
|
||||
servergroup.name = data[u'name']
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
return make_json_response(status=410, success=0, errormsg=e.message)
|
||||
return make_json_response(
|
||||
status=410, success=0, errormsg=e.message
|
||||
)
|
||||
|
||||
return make_json_response(result=request.form)
|
||||
|
||||
|
||||
def properties(self, gid):
|
||||
"""Update the server-group properties"""
|
||||
|
||||
@ -159,11 +154,15 @@ class ServerGroupView(NodeView):
|
||||
return make_json_response(
|
||||
status=417,
|
||||
success=0,
|
||||
errormsg=gettext('The specified server group could not be found.'))
|
||||
errormsg=gettext(
|
||||
'The specified server group could not be found.'
|
||||
)
|
||||
)
|
||||
else:
|
||||
return ajax_response(response={'id': sg.id, 'name': sg.name},
|
||||
status=200)
|
||||
|
||||
return ajax_response(
|
||||
response={'id': sg.id, 'name': sg.name},
|
||||
status=200
|
||||
)
|
||||
|
||||
def create(self):
|
||||
data = request.form if request.form else json.loads(request.data)
|
||||
@ -179,14 +178,17 @@ class ServerGroupView(NodeView):
|
||||
data[u'id'] = sg.id
|
||||
data[u'name'] = sg.name
|
||||
|
||||
return jsonify(node=blueprint.generate_browser_node(
|
||||
"%d" % (sg.id),
|
||||
None,
|
||||
sg.name,
|
||||
"icon-%s" % self.node_type,
|
||||
True))
|
||||
return jsonify(
|
||||
node=self.blueprint.generate_browser_node(
|
||||
"%d" % (sg.id),
|
||||
None,
|
||||
sg.name,
|
||||
"icon-%s" % self.node_type,
|
||||
True,
|
||||
self.node_type
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
print 'except'
|
||||
return make_json_response(
|
||||
status=410,
|
||||
success=0,
|
||||
@ -198,31 +200,18 @@ class ServerGroupView(NodeView):
|
||||
success=0,
|
||||
errormsg=gettext('No server group name was specified'))
|
||||
|
||||
|
||||
def nodes(self, gid):
|
||||
"""Build a list of treeview nodes from the child nodes."""
|
||||
nodes = []
|
||||
for module in blueprint.submodules:
|
||||
nodes.extend(module.get_nodes(server_group=gid))
|
||||
return make_json_response(data=nodes)
|
||||
|
||||
|
||||
def sql(self, gid):
|
||||
return make_json_response(status=422)
|
||||
|
||||
|
||||
def modified_sql(self, gid):
|
||||
return make_json_response(status=422)
|
||||
|
||||
|
||||
def statistics(self, gid):
|
||||
return make_json_response(status=422)
|
||||
|
||||
|
||||
def dependencies(self, gid):
|
||||
return make_json_response(status=422)
|
||||
|
||||
|
||||
def dependents(self, gid):
|
||||
return make_json_response(status=422)
|
||||
|
||||
|
44
web/pgadmin/browser/server_groups/servers/PPAS/__init__.py
Normal file
44
web/pgadmin/browser/server_groups/servers/PPAS/__init__.py
Normal file
@ -0,0 +1,44 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2015, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
from pgadmin.browser.server_groups.servers import ServerTypeModule
|
||||
from pgadmin.browser.utils import PGChildModule
|
||||
from flask.ext.babel import gettext
|
||||
|
||||
|
||||
class PPASServer(ServerTypeModule, PGChildModule):
|
||||
NODE_TYPE = "ppas"
|
||||
|
||||
@property
|
||||
def node_type(self):
|
||||
return self.NODE_TYPE
|
||||
|
||||
@property
|
||||
def jssnippets(self):
|
||||
return []
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return "PPAS"
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
return "Postgres Plus Advanced Server"
|
||||
|
||||
@property
|
||||
def driver(self):
|
||||
return "psycopg2"
|
||||
|
||||
@property
|
||||
def priority(self):
|
||||
return 1
|
||||
|
||||
def instanceOf(self, ver):
|
||||
return ver.startswith("EnterpriseDB")
|
||||
|
||||
blueprint = PPASServer(__name__, static_url_path='/static')
|
Binary file not shown.
After Width: | Height: | Size: 676 B |
@ -0,0 +1,43 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2015, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
from pgadmin.browser.server_groups.servers import ServerTypeModule
|
||||
from pgadmin.browser.utils import PGChildModule
|
||||
|
||||
|
||||
class PGServer(ServerTypeModule, PGChildModule):
|
||||
NODE_TYPE = "pg"
|
||||
|
||||
@property
|
||||
def node_type(self):
|
||||
return self.NODE_TYPE
|
||||
|
||||
@property
|
||||
def jssnippets(self):
|
||||
return []
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return "PG"
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
return "PostgreSQL"
|
||||
|
||||
@property
|
||||
def driver(self):
|
||||
return "psycopg2"
|
||||
|
||||
@property
|
||||
def priority(self):
|
||||
return 10
|
||||
|
||||
def instanceOf(self, ver):
|
||||
return True
|
||||
|
||||
blueprint = PGServer(__name__, static_url_path='/static')
|
Binary file not shown.
After Width: | Height: | Size: 850 B |
@ -7,17 +7,21 @@
|
||||
#
|
||||
##########################################################################
|
||||
import json
|
||||
from flask import render_template, request, make_response, jsonify
|
||||
from abc import ABCMeta, abstractmethod, abstractproperty
|
||||
from flask import render_template, request, make_response, jsonify, current_app
|
||||
from flask.ext.security import login_required, current_user
|
||||
from pgadmin.settings.settings_model import db, Server, ServerGroup
|
||||
from pgadmin.settings.settings_model import db, Server, ServerGroup, User
|
||||
from pgadmin.utils.menu import MenuItem
|
||||
from pgadmin.utils.ajax import make_json_response, \
|
||||
make_response as ajax_response
|
||||
from pgadmin.browser.utils import NodeView, generate_browser_node
|
||||
make_response as ajax_response, internal_server_error, success_return, \
|
||||
unauthorized, bad_request, precondition_required, forbidden
|
||||
from pgadmin.browser.utils import NodeView
|
||||
import traceback
|
||||
from flask.ext.babel import gettext
|
||||
|
||||
import pgadmin.browser.server_groups as sg
|
||||
from pgadmin.utils.crypto import encrypt, decrypt
|
||||
from pgadmin.browser import BrowserPluginModule
|
||||
from config import PG_DEFAULT_DRIVER
|
||||
|
||||
|
||||
class ServerModule(sg.ServerGroupPluginModule):
|
||||
@ -35,27 +39,48 @@ class ServerModule(sg.ServerGroupPluginModule):
|
||||
"""
|
||||
return sg.ServerGroupModule.NODE_TYPE
|
||||
|
||||
def get_nodes(self, server_group):
|
||||
def get_nodes(self, gid):
|
||||
"""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)
|
||||
servergroup_id=gid)
|
||||
|
||||
from pgadmin.utils.driver import get_driver
|
||||
driver = get_driver(PG_DEFAULT_DRIVER)
|
||||
|
||||
# TODO: Move this JSON generation to a Server method
|
||||
for server in servers:
|
||||
node = generate_browser_node(
|
||||
"%d" % (server.id),
|
||||
"%d" % server_group,
|
||||
server.name,
|
||||
"icon-%s-not-connected" % self.NODE_TYPE,
|
||||
True,
|
||||
self.NODE_TYPE)
|
||||
manager = driver.connection_manager(server.id)
|
||||
conn = manager.connection()
|
||||
module = getattr(manager, "module", None)
|
||||
connected = conn.connected()
|
||||
|
||||
yield node
|
||||
yield self.generate_browser_node(
|
||||
"%d" % (server.id),
|
||||
"%d" % gid,
|
||||
server.name,
|
||||
"icon-server-not-connected" if not connected else
|
||||
"icon-{0}".format(module.NODE_TYPE),
|
||||
True,
|
||||
self.NODE_TYPE,
|
||||
connected=connected,
|
||||
server_type=module.type if module is not None else "PG"
|
||||
)
|
||||
|
||||
@property
|
||||
def jssnippets(self):
|
||||
return []
|
||||
|
||||
@property
|
||||
def csssnippets(self):
|
||||
"""
|
||||
Returns a snippet of css to include in the page
|
||||
"""
|
||||
snippets = [render_template("css/servers.css")]
|
||||
|
||||
for submodule in self.submodules:
|
||||
snippets.extend(submodule.csssnippets)
|
||||
|
||||
return snippets
|
||||
|
||||
|
||||
class ServerMenuItem(MenuItem):
|
||||
def __init__(self, **kwargs):
|
||||
@ -66,8 +91,83 @@ class ServerMenuItem(MenuItem):
|
||||
blueprint = ServerModule(__name__)
|
||||
|
||||
|
||||
class ServerTypeModule(BrowserPluginModule):
|
||||
"""
|
||||
Base class for different server types.
|
||||
"""
|
||||
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
@abstractproperty
|
||||
def type(self):
|
||||
pass
|
||||
|
||||
@abstractproperty
|
||||
def description(self):
|
||||
pass
|
||||
|
||||
@abstractproperty
|
||||
def priority(self):
|
||||
pass
|
||||
|
||||
def get_nodes(self, manager=None, sid=None):
|
||||
assert(sid is not None)
|
||||
|
||||
nodes = []
|
||||
|
||||
for module in self.submodules:
|
||||
if isinstance(module, PGChildModule):
|
||||
if manager and module.BackendSupported(manager):
|
||||
nodes.extend(module.get_nodes(sid=sid, manager=manager))
|
||||
else:
|
||||
nodes.extend(module.get_nodes(sid=sid))
|
||||
|
||||
return nodes
|
||||
|
||||
@abstractmethod
|
||||
def instanceOf(self, version):
|
||||
pass
|
||||
|
||||
def __str__(self):
|
||||
return "Type: {0},Description:{1}".format(self.type, self.description)
|
||||
|
||||
@property
|
||||
def csssnippets(self):
|
||||
"""
|
||||
Returns a snippet of css to include in the page
|
||||
"""
|
||||
snippets = [
|
||||
render_template(
|
||||
"css/node.css",
|
||||
node_type=self.node_type
|
||||
)
|
||||
]
|
||||
|
||||
for submodule in self.submodules:
|
||||
snippets.extend(submodule.csssnippets)
|
||||
|
||||
return snippets
|
||||
|
||||
def get_own_javascripts(self):
|
||||
scripts = []
|
||||
|
||||
for module in self.submodules:
|
||||
scripts.extend(module.get_own_javascripts())
|
||||
|
||||
return scripts
|
||||
|
||||
@property
|
||||
def script_load(self):
|
||||
"""
|
||||
Load the module script for all server types, when a server node is
|
||||
initialized.
|
||||
"""
|
||||
return ServerTypeModule.NODE_TYPE
|
||||
|
||||
|
||||
class ServerNode(NodeView):
|
||||
node_type = ServerModule.NODE_TYPE
|
||||
|
||||
parent_ids = [{'type': 'int', 'id': 'gid'}]
|
||||
ids = [{'type': 'int', 'id': 'sid'}]
|
||||
operations = dict({
|
||||
@ -80,7 +180,9 @@ class ServerNode(NodeView):
|
||||
'stats': [{'get': 'statistics'}],
|
||||
'deps': [{'get': 'dependencies', 'post': 'dependents'}],
|
||||
'module.js': [{}, {}, {'get': 'module_js'}],
|
||||
'connect': [{'get': 'connect_status', 'post': 'connect', 'delete': 'disconnect'}]
|
||||
'connect': [{
|
||||
'get': 'connect_status', 'post': 'connect', 'delete': 'disconnect'
|
||||
}]
|
||||
})
|
||||
|
||||
def list(self, gid):
|
||||
@ -89,20 +191,32 @@ class ServerNode(NodeView):
|
||||
servers = Server.query.filter_by(user_id=current_user.id,
|
||||
servergroup_id=gid)
|
||||
|
||||
from pgadmin.utils.driver import get_driver
|
||||
driver = get_driver(PG_DEFAULT_DRIVER)
|
||||
|
||||
for server in servers:
|
||||
manager = driver.connection_manager(server.id)
|
||||
conn = manager.connection()
|
||||
module = getattr(manager, "module", None)
|
||||
|
||||
connected = conn.connected()
|
||||
res.append(
|
||||
generate_browser_node(
|
||||
"%s" % server.id,
|
||||
"%s" % gid,
|
||||
self.blueprint.generate_browser_node(
|
||||
"%d" % (server.id),
|
||||
"%d" % gid,
|
||||
server.name,
|
||||
"icon-%s-not-connected" % ServerModule.NODE_TYPE,
|
||||
"icon-server-not-connected" if not connected else
|
||||
"icon-{0}".format(module.NODE_TYPE),
|
||||
True,
|
||||
ServerModule.NODE_TYPE)
|
||||
)
|
||||
self.node_type,
|
||||
connected=connected,
|
||||
server_type=module.type if module is not None else 'PG'
|
||||
)
|
||||
)
|
||||
return make_json_response(result=res)
|
||||
|
||||
def delete(self, gid, sid):
|
||||
"""Delete a server node in the settings database"""
|
||||
"""Delete a server node in the settings database."""
|
||||
servers = Server.query.filter_by(user_id=current_user.id, id=sid)
|
||||
|
||||
# TODO:: A server, which is connected, can not be deleted
|
||||
@ -130,7 +244,8 @@ class ServerNode(NodeView):
|
||||
|
||||
def update(self, gid, sid):
|
||||
"""Update the server settings"""
|
||||
server = Server.query.filter_by(user_id=current_user.id, id=sid).first()
|
||||
server = Server.query.filter_by(
|
||||
user_id=current_user.id, id=sid).first()
|
||||
|
||||
if server is None:
|
||||
return make_json_response(
|
||||
@ -138,9 +253,8 @@ class ServerNode(NodeView):
|
||||
errormsg=gettext("Couldn't find the given server.")
|
||||
)
|
||||
|
||||
# TODO::
|
||||
# Not all parameters can be modified, while the server is connected
|
||||
possible_args = {
|
||||
# Not all parameters can be modified, while the server is connected
|
||||
config_param_map = {
|
||||
'name': 'name',
|
||||
'host': 'host',
|
||||
'port': 'port',
|
||||
@ -148,21 +262,49 @@ class ServerNode(NodeView):
|
||||
'username': 'username',
|
||||
'sslmode': 'sslmode',
|
||||
'gid': 'servergroup_id',
|
||||
'comment': 'comment'
|
||||
'comment': 'comment',
|
||||
'role': 'role'
|
||||
}
|
||||
|
||||
disp_lbl = {
|
||||
'name': gettext('name'),
|
||||
'host': gettext('Host name/address'),
|
||||
'port': gettext('Port'),
|
||||
'db': gettext('Maintenance database'),
|
||||
'username': gettext('Username'),
|
||||
'sslmode': gettext('SSL Mode'),
|
||||
'comment': gettext('Comments'),
|
||||
'role': gettext('Role')
|
||||
}
|
||||
|
||||
idx = 0
|
||||
data = request.form if request.form else json.loads(request.data)
|
||||
|
||||
for arg in possible_args:
|
||||
from pgadmin.utils.driver import get_driver
|
||||
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
||||
conn = manager.connection()
|
||||
not_allowed = {}
|
||||
|
||||
if conn.connected():
|
||||
for arg in {
|
||||
'host', 'port', 'db', 'username', 'sslmode', 'role'
|
||||
}:
|
||||
if arg in data:
|
||||
return forbidden(
|
||||
errormsg=gettext(
|
||||
"'{0}' is not allowed to modify, when server is connected."
|
||||
).format(disp_lbl[arg])
|
||||
)
|
||||
|
||||
for arg in config_param_map:
|
||||
if arg in data:
|
||||
setattr(server, possible_args[arg], data[arg])
|
||||
setattr(server, config_param_map[arg], data[arg])
|
||||
idx += 1
|
||||
|
||||
if idx == 0:
|
||||
return make_json_response(
|
||||
success=0,
|
||||
errormsg=gettext('No parameters were chagned!')
|
||||
errormsg=gettext('No parameters were changed!')
|
||||
)
|
||||
|
||||
try:
|
||||
@ -173,11 +315,14 @@ class ServerNode(NodeView):
|
||||
errormsg=e.message
|
||||
)
|
||||
|
||||
manager.update(server)
|
||||
|
||||
return make_json_response(
|
||||
success=1,
|
||||
data={
|
||||
'id': server.id,
|
||||
'gid': server.servergroup_id
|
||||
'gid': server.servergroup_id,
|
||||
'icon': 'icon-server-not-connected'
|
||||
}
|
||||
)
|
||||
|
||||
@ -198,6 +343,14 @@ class ServerNode(NodeView):
|
||||
id=server.servergroup_id
|
||||
).first()
|
||||
|
||||
from pgadmin.utils.driver import get_driver
|
||||
driver = get_driver(PG_DEFAULT_DRIVER)
|
||||
|
||||
manager = driver.connection_manager(sid)
|
||||
conn = manager.connection()
|
||||
connected = conn.connected()
|
||||
module = getattr(manager, 'module', None)
|
||||
|
||||
return ajax_response(
|
||||
response={
|
||||
'id': server.id,
|
||||
@ -209,9 +362,10 @@ class ServerNode(NodeView):
|
||||
'gid': server.servergroup_id,
|
||||
'group-name': sg.name,
|
||||
'comment': server.comment,
|
||||
# TODO:: Make sure - we do have correct values here
|
||||
'connected': True,
|
||||
'version': 'PostgreSQL 9.3 (linux-x64)'
|
||||
'role': server.role,
|
||||
'connected': connected,
|
||||
'version': manager.ver,
|
||||
'server_type': module.type if module is not None else 'PG'
|
||||
}
|
||||
)
|
||||
|
||||
@ -223,7 +377,8 @@ class ServerNode(NodeView):
|
||||
u'port',
|
||||
u'db',
|
||||
u'username',
|
||||
u'sslmode'
|
||||
u'sslmode',
|
||||
u'role'
|
||||
]
|
||||
|
||||
data = request.form if request.form else json.loads(request.data)
|
||||
@ -247,19 +402,25 @@ class ServerNode(NodeView):
|
||||
port=data[u'port'],
|
||||
maintenance_db=data[u'db'],
|
||||
username=data[u'username'],
|
||||
ssl_mode=data['sslmode'],
|
||||
comment=data['comment'] if 'comment' in data else None
|
||||
ssl_mode=data[u'sslmode'],
|
||||
comment=data[u'comment'] if u'comment' in data else None,
|
||||
role=data[u'role'] if u'role' in data else None
|
||||
)
|
||||
db.session.add(server)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify(node=generate_browser_node(
|
||||
'%s' % server.id,
|
||||
'%s' % gid,
|
||||
'%s' % server.name,
|
||||
"icon-{0}-not-connected".format(ServerModule.NODE_TYPE),
|
||||
True,
|
||||
ServerModule.NODE_TYPE))
|
||||
return jsonify(
|
||||
node=self.blueprint.generate_browser_node(
|
||||
"%d" % (server.id),
|
||||
"%d" % gid,
|
||||
server.name,
|
||||
"icon-server-not-connected",
|
||||
True,
|
||||
self.node_type,
|
||||
connected=False,
|
||||
server_type='PG' # Default server type
|
||||
)
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return make_json_response(
|
||||
@ -270,12 +431,27 @@ class ServerNode(NodeView):
|
||||
|
||||
def nodes(self, gid, sid):
|
||||
"""Build a list of treeview nodes from the child nodes."""
|
||||
from pgadmin.utils.driver import get_driver
|
||||
|
||||
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
||||
conn = manager.connection()
|
||||
|
||||
if not conn.connected():
|
||||
return precondition_required(
|
||||
gettext(
|
||||
"Please make a connection to the server first!"
|
||||
)
|
||||
)
|
||||
|
||||
nodes = []
|
||||
# TODO::
|
||||
# We can have nodes for the server object, only when
|
||||
# the server is connected at the moment.
|
||||
for module in blueprint.submodules:
|
||||
nodes.extend(module.get_nodes(server=sid))
|
||||
|
||||
# We will rely on individual server type modules to generate nodes for
|
||||
# them selves.
|
||||
module = getattr(manager, 'module', None)
|
||||
|
||||
if module:
|
||||
nodes.extend(module.get_nodes(sid=sid, manager=manager))
|
||||
|
||||
return make_json_response(data=nodes)
|
||||
|
||||
def sql(self, gid, sid):
|
||||
@ -299,9 +475,180 @@ class ServerNode(NodeView):
|
||||
Override this property for your own logic.
|
||||
"""
|
||||
return make_response(
|
||||
render_template("servers/servers.js"),
|
||||
render_template(
|
||||
"servers/servers.js",
|
||||
server_types=sorted(
|
||||
[
|
||||
m for m in self.blueprint.submodules
|
||||
if isinstance(m, ServerTypeModule)
|
||||
],
|
||||
key=lambda x: x.priority
|
||||
),
|
||||
_=gettext
|
||||
),
|
||||
200, {'Content-Type': 'application/x-javascript'}
|
||||
)
|
||||
|
||||
def connect_status(self, gid, sid):
|
||||
"""Check and return the connection status."""
|
||||
from pgadmin.utils.driver import get_driver
|
||||
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
||||
conn = manager.get_connection()
|
||||
|
||||
return make_json_response(data={'connected': conn.connected()})
|
||||
|
||||
def connect(self, gid, sid):
|
||||
"""
|
||||
Connect the Server and return the connection object.
|
||||
Verification Process before Connection:
|
||||
Verify requested server.
|
||||
|
||||
Check the server password is already been stored in the
|
||||
database or not.
|
||||
If Yes, connect the server and return connection.
|
||||
If No, Raise HTTP error and ask for the password.
|
||||
|
||||
In case of 'Save Password' request from user, excrypted Pasword
|
||||
will be stored in the respected server database and
|
||||
establish the connection OR just connect the server and do not
|
||||
store the password.
|
||||
"""
|
||||
current_app.logger.info(
|
||||
'Connection Request for server#{0}'.format(sid)
|
||||
)
|
||||
|
||||
# Fetch Server Details
|
||||
server = Server.query.filter_by(id=sid).first()
|
||||
if server is None:
|
||||
return bad_request(gettext("Server Not Found."))
|
||||
|
||||
# Fetch User Details.
|
||||
user = User.query.filter_by(id=current_user.id).first()
|
||||
if user is None:
|
||||
return unauthorized(gettext("Unauthorized Request."))
|
||||
|
||||
data = request.form if request.form else json.loads(request.data) if \
|
||||
request.data else {}
|
||||
|
||||
password = None
|
||||
save_password = False
|
||||
|
||||
if 'password' not in data:
|
||||
if server.password is None:
|
||||
# Return the password template in case password is not
|
||||
# provided, or password has not been saved earlier.
|
||||
return make_json_response(
|
||||
success=0,
|
||||
status=428,
|
||||
result=render_template(
|
||||
'servers/password.html',
|
||||
server_label=server.name,
|
||||
username=server.username,
|
||||
_=gettext
|
||||
)
|
||||
)
|
||||
else:
|
||||
password = data['password'] if 'password' in data else None
|
||||
save_password = \
|
||||
data['save_password'] if password and \
|
||||
'save_password' in data else False
|
||||
|
||||
# Encrypt the password before saving with user's login password key.
|
||||
try:
|
||||
password = encrypt(password, user.password) \
|
||||
if password is not None else server.password
|
||||
except Exception as e:
|
||||
return internal_server_error(errormsg=e.message)
|
||||
|
||||
# Connect the Server
|
||||
from pgadmin.utils.driver import get_driver
|
||||
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
||||
conn = manager.connection()
|
||||
|
||||
try:
|
||||
status, errmsg = conn.connect(
|
||||
password=password,
|
||||
modules=[
|
||||
m for m in self.blueprint.submodules
|
||||
if isinstance(m, ServerTypeModule)
|
||||
]
|
||||
)
|
||||
except Exception as e:
|
||||
# TODO::
|
||||
# Ask the password again (if existing password couldn't be
|
||||
# descrypted)
|
||||
return internal_server_error(errormsg=e.message)
|
||||
|
||||
if not status:
|
||||
current_app.logger.error(
|
||||
"Could not connected to server(#{0}) - '{1}'.\nError: {2}".format(
|
||||
server.id, server.name, errmsg
|
||||
)
|
||||
)
|
||||
|
||||
return make_json_response(
|
||||
success=0,
|
||||
status=401,
|
||||
result=render_template(
|
||||
'servers/password.html',
|
||||
server_label=server.name,
|
||||
username=server.username,
|
||||
errmsg=errmsg,
|
||||
_=gettext
|
||||
)
|
||||
)
|
||||
else:
|
||||
if save_password:
|
||||
try:
|
||||
# Save the encrypted password using the user's login
|
||||
# password key.
|
||||
setattr(server, 'password', password)
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
# Release Connection
|
||||
manager.release(database=server.maintenance_db)
|
||||
conn = None
|
||||
|
||||
return internal_server_error(errormsg=e.message)
|
||||
|
||||
current_app.logger.info('Connection Established for server: \
|
||||
%s - %s' % (server.id, server.name))
|
||||
|
||||
return make_json_response(
|
||||
success=1,
|
||||
info=gettext("Server Connected."),
|
||||
data={
|
||||
'icon': 'icon-{0}'.format(
|
||||
manager.module.NODE_TYPE
|
||||
),
|
||||
'connected': True
|
||||
}
|
||||
)
|
||||
|
||||
def disconnect(self, gid, sid):
|
||||
"""Disconnect the Server."""
|
||||
|
||||
server = Server.query.filter_by(id=sid).first()
|
||||
if server is None:
|
||||
return bad_request(gettext("Server Not Found."))
|
||||
|
||||
# Release Connection
|
||||
from pgadmin.utils.driver import get_driver
|
||||
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
||||
|
||||
status = manager.release()
|
||||
|
||||
if not status:
|
||||
return unauthorized(gettext("Server Could Not Disconnect."))
|
||||
else:
|
||||
return make_json_response(
|
||||
success=1,
|
||||
info=gettext("Server Disconnected."),
|
||||
data={
|
||||
'icon': 'icon-server-not-connected',
|
||||
'connected': False
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
ServerNode.register_node_view(blueprint)
|
||||
|
@ -0,0 +1,7 @@
|
||||
.icon-{{node_type}} {
|
||||
background-image: url('{{ url_for('NODE-%s.static' % node_type, filename='img/%s.png' % node_type )}}') !important;
|
||||
background-repeat: no-repeat;
|
||||
align-content: center;
|
||||
vertical-align: middle;
|
||||
height: 1.3em;
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
.icon-server {
|
||||
background-image: url('{{ url_for('NODE-server.static', filename='img/server.png') }}') !important;
|
||||
border-radius: 10px
|
||||
}
|
||||
|
||||
.icon-server-not-connected {
|
||||
background-image: url('{{ url_for('NODE-server.static', filename='img/serverbad.png') }}') !important;
|
||||
border-radius: 10px
|
||||
}
|
||||
|
||||
.icon-server-connecting {
|
||||
background-image: url('{{ url_for('browser.static', filename='css/aciTree/image/load-node.gif')}}') !important;
|
||||
background-repeat: no-repeat;
|
||||
align-content: center;
|
||||
vertical-align: middle;
|
||||
height: 1.3em;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<form name="frmPassword" id="frmPassword" style="height: 100%; width: 100%" onsubmit="return false;">
|
||||
<div>{% if errmsg %}
|
||||
<div class="highlight has-error">
|
||||
<div class='control-label'>{{ errmsg }}</div>
|
||||
</div>{% endif %}
|
||||
<div><b>{{ _('Please enter the password for the user \'{0}\' to connect the server - "{1}"').format(username, server_label) }}</b></div>
|
||||
<div style="padding: 5px; height: 1px;"></div>
|
||||
<div style="width: 100%">
|
||||
<span style="width: 25%;display: inline-table;">Password</span>
|
||||
<span style="width: 73%;display: inline-block;">
|
||||
<input style="width:100%" id="password" class="form-control" name="password" type="password">
|
||||
</span>
|
||||
<span style="margin-left: 25%; padding-top: 15px;width: 45%;display: inline-block;">
|
||||
<input id="save_password" name="save_password" type="checkbox"> Save Password
|
||||
</span>
|
||||
</div>
|
||||
<div style="padding: 5px; height: 1px;"></div>
|
||||
</div>
|
||||
</form>
|
@ -1,6 +1,6 @@
|
||||
define(
|
||||
['jquery', 'underscore', 'pgadmin', 'pgadmin.browser', 'alertify'],
|
||||
function($, _, pgAdmin, pgBrowser, alertify) {
|
||||
['jquery', 'underscore', 'underscore.string', 'pgadmin', 'pgadmin.browser', 'alertify'],
|
||||
function($, _, S, pgAdmin, pgBrowser, alertify) {
|
||||
|
||||
if (!pgBrowser.Nodes['server']) {
|
||||
pgAdmin.Browser.Nodes['server'] = pgAdmin.Browser.Node.extend({
|
||||
@ -29,76 +29,99 @@ function($, _, pgAdmin, pgBrowser, alertify) {
|
||||
name: 'drop_server', node: 'server', module: this,
|
||||
applies: ['object', 'context'], callback: 'delete_obj',
|
||||
category: 'drop', priority: 3, label: '{{ _('Drop Server...') }}',
|
||||
icon: 'fa fa-trash'
|
||||
icon: 'fa fa-trash', enable: 'is_not_connected'
|
||||
},{
|
||||
name: 'connect_server', node: 'server', module: this,
|
||||
applies: ['object', 'context'], callback: 'connect_server',
|
||||
category: 'connect', priority: 4, label: '{{ _('Connect Server...') }}',
|
||||
icon: 'fa fa-link', enable : 'is_not_connected'
|
||||
},
|
||||
{
|
||||
name: 'disconnect_server', node: 'server', module: this,
|
||||
applies: ['object', 'context'], callback: 'disconnect_server',
|
||||
category: 'drop', priority: 5, label: '{{ _('Disconnect Server...') }}',
|
||||
icon: 'fa fa-chain-broken', enable : 'is_connected'
|
||||
}]);
|
||||
},
|
||||
is_not_connected: function(node) {
|
||||
return (node && node.connected != true);
|
||||
},
|
||||
is_connected: function(node) {
|
||||
return (node && node.connected == true);
|
||||
},
|
||||
callbacks: {
|
||||
// Add a server
|
||||
create_server: function (item) {
|
||||
var alert = alertify.prompt(
|
||||
'{{ _('Create a server') }}',
|
||||
'{{ _('Enter a name for the new server') }}',
|
||||
'',
|
||||
function(evt, value) {
|
||||
var d = tree.itemData(item);
|
||||
if (d._type != 'server-group') {
|
||||
d = tree.itemData(tree.parent(item));
|
||||
}
|
||||
$.post(
|
||||
"{{ url_for('browser.index') }}server/obj/" + d.refid + '/' + d.id + '/',
|
||||
{ name: value }
|
||||
)
|
||||
.done(function(data) {
|
||||
if (data.success == 0) {
|
||||
report_error(data.errormsg, data.info);
|
||||
} else {
|
||||
var item = {
|
||||
id: data.data.id,
|
||||
label: data.data.name,
|
||||
inode: true,
|
||||
open: false,
|
||||
icon: 'icon-server-not-connected'
|
||||
}
|
||||
tree.append(null, {
|
||||
itemData: item
|
||||
});
|
||||
/* Connect the server */
|
||||
connect_server: function(args){
|
||||
var input = args || {};
|
||||
obj = this,
|
||||
t = pgBrowser.tree,
|
||||
i = input.item || t.selected(),
|
||||
d = i && i.length == 1 ? t.itemData(i) : undefined;
|
||||
|
||||
if (!d)
|
||||
return false;
|
||||
|
||||
connect_to_server(obj, d, t, i);
|
||||
return false;
|
||||
},
|
||||
/* Disconnect the server */
|
||||
disconnect_server: function(args) {
|
||||
var input = args || {};
|
||||
obj = this,
|
||||
t = pgBrowser.tree,
|
||||
i = input.item || t.selected(),
|
||||
d = i && i.length == 1 ? t.itemData(i) : undefined;
|
||||
|
||||
if (!d)
|
||||
return false;
|
||||
|
||||
alertify.confirm(
|
||||
'{{ _('Disconnect the server') }}',
|
||||
S('{{ _('Are you sure you want to disconnect the server - %%s ?') }}').sprintf(d.label).value(),
|
||||
function(evt) {
|
||||
$.ajax({
|
||||
url: obj.generate_url('connect', d, true),
|
||||
type:'DELETE',
|
||||
success: function(res) {
|
||||
if (res.success == 1) {
|
||||
alertify.success("{{ _('" + res.info + "') }}");
|
||||
t.removeIcon(i);
|
||||
d.connected = false;
|
||||
d.icon = 'icon-server-not-connected';
|
||||
t.addIcon(i, {icon: d.icon});
|
||||
t.unload(i);
|
||||
t.setInode(i);
|
||||
}
|
||||
});
|
||||
},
|
||||
null
|
||||
);
|
||||
alert.show();
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
try {
|
||||
var err = $.parseJSON(xhr.responseText);
|
||||
if (err.success == 0) {
|
||||
msg = S('{{ _(' + err.errormsg + ')}}').value();
|
||||
alertify.error("{{ _('" + err.errormsg + "') }}");
|
||||
}
|
||||
} catch (e) {}
|
||||
t.unload(i);
|
||||
}
|
||||
});
|
||||
},
|
||||
function(evt) {
|
||||
return true;
|
||||
});
|
||||
|
||||
return false;
|
||||
},
|
||||
/* Connect the server (if not connected), before opening this node */
|
||||
beforeopen: function(o) {
|
||||
o.browser.tree.removeIcon(o.item);
|
||||
if (o.data.connected) {
|
||||
o.browser.tree.addIcon(o.item, {icon: 'icon-server-connected'});
|
||||
} else {
|
||||
o.browser.tree.addIcon(o.item, {icon: 'icon-server-not-connected'});
|
||||
}
|
||||
var data = o.data;
|
||||
|
||||
if(!data || data._type != 'server') {
|
||||
return false;
|
||||
}
|
||||
|
||||
o.browser.tree.addIcon(o.item, {icon: data.icon});
|
||||
if (!data.connected) {
|
||||
alertify.confirm(
|
||||
'{{ _('Connect to server') }}',
|
||||
'{{ _('Do you want to connect the server?') }}',
|
||||
function(evt) {
|
||||
$.post(
|
||||
"{{ url_for('browser.index') }}server/connect/" + data.refid + '/'
|
||||
).done(function(data) {
|
||||
if (data.success == 0) {
|
||||
report_error(data.errormsg, data.info);
|
||||
}
|
||||
}).fail(function() {});
|
||||
return true;
|
||||
},
|
||||
function(evt) {
|
||||
return true;
|
||||
}
|
||||
);
|
||||
connect_to_server(this, data, o.browser.tree, o.item);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -107,40 +130,61 @@ function($, _, pgAdmin, pgBrowser, alertify) {
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
defaults: {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
sslmode: 'prefer'
|
||||
name: null,
|
||||
sslmode: 'prefer',
|
||||
host: null,
|
||||
port: 5432,
|
||||
db: null,
|
||||
username: null,
|
||||
role: null
|
||||
},
|
||||
schema: [{
|
||||
id: 'id', label: 'ID', type: 'int', group: null,
|
||||
id: 'id', label: '{{ _('ID') }}', type: 'int', group: null,
|
||||
mode: ['properties']
|
||||
},{
|
||||
id: 'name', label:'Name', type: 'text', group: null,
|
||||
id: 'name', label:'{{ _('Name') }}', type: 'text', group: null,
|
||||
mode: ['properties', 'edit', 'create']
|
||||
},{
|
||||
id: 'connected', label:'Connected', type: 'text', group: null,
|
||||
id: 'connected', label:'{{ _('Connected') }}', type: 'text', group: null,
|
||||
mode: ['properties']
|
||||
},{
|
||||
id: 'version', label:'Version', type: 'text', group: null,
|
||||
id: 'version', label:'{{ _('Version') }}', type: 'text', group: null,
|
||||
mode: ['properties'], show: 'isConnected'
|
||||
},{
|
||||
id: 'comment', label:'Comments:', type: 'multiline', group: null,
|
||||
mode: ['properties', 'edit', 'create'], disable: 'notEditMode'
|
||||
id: 'comment', label:'{{ _('Comments:') }}', type: 'multiline', group: null,
|
||||
mode: ['properties', 'edit', 'create'], disabled: 'notEditMode'
|
||||
},{
|
||||
id: 'host', label:'Host Name/Address', type: 'text', group: "Connection",
|
||||
mode: ['properties', 'edit', 'create']
|
||||
id: 'host', label:'{{ _('Host Name/Address') }}', type: 'text', group: "Connection",
|
||||
mode: ['properties', 'edit', 'create'], disabled: 'isConnected'
|
||||
},{
|
||||
id: 'port', label:'Port', type: 'int', group: "Connection",
|
||||
mode: ['properties', 'edit', 'create']
|
||||
id: 'port', label:'{{ _('Port') }}', type: 'int', group: "Connection",
|
||||
mode: ['properties', 'edit', 'create'], disabled: 'isConnected'
|
||||
},{
|
||||
id: 'db', label:'Maintenance Database', type: 'text', group: "Connection",
|
||||
mode: ['properties', 'edit', 'create']
|
||||
id: 'db', label:'{{ _('Maintenance Database') }}', type: 'text', group: "Connection",
|
||||
mode: ['properties', 'edit', 'create'], disabled: 'isConnected'
|
||||
},{
|
||||
id: 'username', label:'User Name', type: 'text', group: "Connection",
|
||||
mode: ['properties', 'edit', 'create']
|
||||
id: 'username', label:'{{ _('User Name') }}', type: 'text', group: "Connection",
|
||||
mode: ['properties', 'edit', 'create'], disabled: 'isConnected'
|
||||
},{
|
||||
id: 'sslmode', label:'SSL Mode', type: 'options', group: "Connection",
|
||||
mode: ['properties', 'edit', 'create'],
|
||||
'options': [{label:'Allow', value:'allow'}, {label: 'Prefer', value:'prefer'}, {label: 'Require', value: 'require'}, {label: 'Disable', value:'disable'}, {label:'Verify-CA', value: 'verify-ca'}, {label:'Verify-Full', value:'verify-full'}]
|
||||
id: 'role', label:'{{ _('Role') }}', type: 'text', group: "Connection",
|
||||
mode: ['properties', 'edit', 'create'], disabled: 'isConnected'
|
||||
},{
|
||||
id: 'sslmode', label:'{{ _('SSL Mode') }}', type: 'options', group: "Connection",
|
||||
mode: ['properties', 'edit', 'create'], disabled: 'isConnected',
|
||||
'options': [
|
||||
{label: 'Allow', value: 'allow'},
|
||||
{label: 'Prefer', value: 'prefer'},
|
||||
{label: 'Require', value: 'require'},
|
||||
{label: 'Disable', value: 'disable'},
|
||||
{label: 'Verify-CA', value: 'verify-ca'},
|
||||
{label: 'Verify-Full', value: 'verify-full'}
|
||||
]
|
||||
},{
|
||||
id: 'server_type', label: '{{ _('Server Type') }}', type: 'options',
|
||||
mode: ['properties'], show: 'isConnected',
|
||||
'options': [{% set cnt = 1 %}{% for server_type in server_types %}{% if cnt != 1 %},{% endif %}
|
||||
{label: '{{ server_type.description }}', value: '{{ server_type.type}}'}{% set cnt = cnt + 1 %}{% endfor %}
|
||||
]
|
||||
}],
|
||||
validate: function(attrs, options) {
|
||||
if (!this.isNew() && 'id' in this.changed) {
|
||||
@ -151,13 +195,145 @@ function($, _, pgAdmin, pgBrowser, alertify) {
|
||||
}
|
||||
return null;
|
||||
},
|
||||
isConnected: function(mode) {
|
||||
return mode == 'properties' && this.get('connected');
|
||||
isConnected: function(model) {
|
||||
return model.get('connected');
|
||||
}
|
||||
})
|
||||
});
|
||||
function connect_to_server(obj, data, tree, item) {
|
||||
var onFailure = function(xhr, status, error, _model, _data, _tree, _item) {
|
||||
|
||||
tree.setInode(_item);
|
||||
tree.addIcon(_item, {icon: 'icon-server-not-connected'});
|
||||
|
||||
alertify.pgNotifier('error', xhr, error, function(msg) {
|
||||
setTimeout(function() {
|
||||
alertify.dlgServerPass(
|
||||
'{{ _('Connect to Server') }}',
|
||||
msg, _model, _data, _tree, _item
|
||||
).resizeTo();
|
||||
}, 100);
|
||||
});
|
||||
},
|
||||
onSuccess = function(res, model, data, tree, item) {
|
||||
tree.deselect(item);
|
||||
tree.setInode(item);
|
||||
|
||||
if (res && res.data) {
|
||||
if(typeof res.data.connected == 'boolean') {
|
||||
data.connected = res.data.connected;
|
||||
}
|
||||
if (typeof res.data.icon == 'string') {
|
||||
tree.removeIcon(item);
|
||||
data.icon = res.data.icon;
|
||||
tree.addIcon(item, {icon: data.icon});
|
||||
}
|
||||
|
||||
alertify.success(res.info);
|
||||
setTimeout(function() {tree.select(item);}, 10);
|
||||
setTimeout(function() {tree.open(item);}, 100);
|
||||
}
|
||||
};
|
||||
|
||||
// Ask Password and send it back to the connect server
|
||||
if (!alertify.dlgServerPass) {
|
||||
alertify.dialog('dlgServerPass', function factory() {
|
||||
return {
|
||||
main: function(title, message, model, data, tree, item) {
|
||||
this.set('title', title);
|
||||
this.message = message;
|
||||
this.tree = tree;
|
||||
this.nodeData = data;
|
||||
this.nodeItem = item;
|
||||
this.nodeModel = model;
|
||||
},
|
||||
setup:function() {
|
||||
return {
|
||||
buttons:[
|
||||
{
|
||||
text: "{{ _('OK') }}", key: 13, className: "btn btn-primary"
|
||||
},
|
||||
{
|
||||
text: "{{ _('Cancel') }}", className: "btn btn-danger"
|
||||
}
|
||||
],
|
||||
focus: { element: '#password', select: true },
|
||||
options: {
|
||||
modal: 0, resizable: false, maximizable: false, pinnable: false
|
||||
}
|
||||
};
|
||||
},
|
||||
build:function() {},
|
||||
prepare:function() {
|
||||
this.setContent(this.message);
|
||||
},
|
||||
callback: function(closeEvent) {
|
||||
var _sdata = this.nodeData,
|
||||
_tree = this.tree,
|
||||
_item = this.nodeItem,
|
||||
_model = this.nodeModel;
|
||||
|
||||
if (closeEvent.button.text == "{{ _('OK') }}") {
|
||||
|
||||
var _url = _model.generate_url('connect', _sdata, true);
|
||||
|
||||
_tree.setLeaf(_item);
|
||||
_tree.removeIcon(_item);
|
||||
_tree.addIcon(_item, {icon: 'icon-server-connecting'});
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
timeout: 30000,
|
||||
url: _url,
|
||||
data: $('#frmPassword').serialize(),
|
||||
success: function(res) {
|
||||
return onSuccess(
|
||||
res, _model, _sdata, _tree, _item
|
||||
);
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
return onFailure(
|
||||
xhr, status, error, _model, _sdata, _tree, _item
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
_tree.setInode(_item);
|
||||
_tree.removeIcon(_item);
|
||||
_tree.addIcon(_item, {icon: 'icon-server-not-connected'});
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
alertify.confirm(
|
||||
'{{ _('Connect to server') }}',
|
||||
'{{ _('Do you want to connect the server?') }}',
|
||||
function(evt) {
|
||||
url = obj.generate_url("connect", data, true);
|
||||
$.post(url)
|
||||
.done(
|
||||
function(res) {
|
||||
if (res.success == 1) {
|
||||
return onSuccess(res, obj, data, tree, item);
|
||||
}
|
||||
})
|
||||
.fail(
|
||||
function(xhr, status, error) {
|
||||
return onFailure(xhr, status, error, obj, data, tree, item);
|
||||
});
|
||||
},
|
||||
notEditMode: function(mode) {
|
||||
return mode != 'edit';
|
||||
}})
|
||||
});
|
||||
function() {});
|
||||
}
|
||||
/* Send PING to indicate that session is alive */
|
||||
function server_status(server_id)
|
||||
{
|
||||
url = "/ping";
|
||||
$.post(url)
|
||||
.done(function(data) { return true})
|
||||
.fail(function(xhr, status, error) { return false})
|
||||
}
|
||||
}
|
||||
|
||||
return pgBrowser.Nodes['server'];
|
||||
|
@ -29,7 +29,7 @@ function($, _, pgAdmin, Backbone) {
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
defaults: {
|
||||
id: undefined,
|
||||
name: undefined
|
||||
name: null
|
||||
},
|
||||
schema: [
|
||||
{id: 'id', label: 'ID', type: 'int', group: null, mode: ['properties']},
|
||||
|
@ -119,7 +119,6 @@ OWNER TO helpdesk;\n';
|
||||
width: 500,
|
||||
isCloseable: false,
|
||||
isPrivate: true,
|
||||
// TODO:: Revove demoSql later
|
||||
content: '<textarea id="sql-textarea" name="sql-textarea"></textarea>',
|
||||
events: sqlPanelEvents
|
||||
}),
|
||||
@ -163,7 +162,7 @@ OWNER TO helpdesk;\n';
|
||||
width: 500,
|
||||
isCloseable: false,
|
||||
isPrivate: true,
|
||||
url: 'http://www.pgadmin.org'
|
||||
url: 'about:blank' // http://www.pgadmin.org'
|
||||
})/* Add hooked-in frames by extensions */{% for panel_item in current_app.panels %}{% if panel_item.isIframe %},
|
||||
'{{ panel_item.name }}' : new pgAdmin.Browser.Frame({
|
||||
name: '{{ panel_item.name }}',
|
||||
@ -280,7 +279,6 @@ OWNER TO helpdesk;\n';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
init: function() {
|
||||
var obj=this;
|
||||
@ -343,6 +341,7 @@ OWNER TO helpdesk;\n';
|
||||
mode: "text/x-sql",
|
||||
readOnly: true
|
||||
});
|
||||
// TODO:: Revove demoSql later
|
||||
$('#sql-textarea').val(demoSql);
|
||||
|
||||
// Initialise the treeview
|
||||
@ -378,7 +377,7 @@ OWNER TO helpdesk;\n';
|
||||
var o = undefined;
|
||||
|
||||
_.each(menus, function(m) {
|
||||
if (name == (m.module.type + '_' + m.callback)) {
|
||||
if (name == (m.module.type + '_' + m.name)) {
|
||||
o = m;
|
||||
}
|
||||
});
|
||||
@ -406,20 +405,22 @@ OWNER TO helpdesk;\n';
|
||||
_.each(
|
||||
_.sortBy(menus, function(m) { return m.priority; }),
|
||||
function(m) {
|
||||
if (m.category == 'create' && !m.disabled(d))
|
||||
createMenu[m.module.type + '_' + m.callback] = { name: m.label };
|
||||
if (m.category == 'create' && !m.disabled(d)) {
|
||||
createMenu[m.module.type + '_' + m.name] = { name: m.label, icon: m.module.type };
|
||||
}
|
||||
});
|
||||
|
||||
if (_.size(createMenu)) {
|
||||
menu["create"] = { "name": "{{ _('Create') }}" };
|
||||
menu["create"] = { name: "{{ _('Create') }}", icon: 'fa fa-magic' };
|
||||
menu["create"]["items"] = createMenu;
|
||||
}
|
||||
|
||||
_.each(
|
||||
_.sortBy(menus, function(m) { return m.priority; }),
|
||||
function(m) {
|
||||
if (m.category != 'create' && !m.disabled(d))
|
||||
menu[m.module.type + '_' + m.callback] = { name: m.label };
|
||||
if (m.category != 'create' && !m.disabled(d)) {
|
||||
menu[m.module.type + '_' + m.name] = { name: m.label, icon: m.icon };
|
||||
}
|
||||
});
|
||||
|
||||
return _.size(menu) ? {
|
||||
@ -516,6 +517,16 @@ OWNER TO helpdesk;\n';
|
||||
}
|
||||
};
|
||||
geneate_menus();
|
||||
|
||||
// Ping the server every 5 minutes
|
||||
setInterval(function() {
|
||||
$.ajax({
|
||||
url: '{{ url_for('misc.ping') }}',
|
||||
type:'POST',
|
||||
success: function() {},
|
||||
error: function() {}
|
||||
});
|
||||
}, 300000);
|
||||
},
|
||||
// load the module right now
|
||||
load_module: function(name, path, c) {
|
||||
@ -532,7 +543,6 @@ OWNER TO helpdesk;\n';
|
||||
if (c)
|
||||
c.loaded += 1;
|
||||
}, function() {
|
||||
/* TODO:: Show proper error */
|
||||
obj.report_error(
|
||||
'{{ _('Error loading script - ') }}' + path);
|
||||
});
|
||||
@ -557,7 +567,7 @@ OWNER TO helpdesk;\n';
|
||||
}
|
||||
|
||||
if (_.has(menus, m.name)) {
|
||||
console.log(m.name +
|
||||
console && console.log && console.log(m.name +
|
||||
' has been ignored!\nIt is already exist in the ' +
|
||||
a +
|
||||
' list of menus!');
|
||||
@ -605,6 +615,10 @@ OWNER TO helpdesk;\n';
|
||||
navbar.children('#mnu_obj').removeClass('hide');
|
||||
});
|
||||
obj.enable_disable_menus();
|
||||
},
|
||||
messages: {
|
||||
'server_lost': '{{ _('Connection to the server has been lost!') }}',
|
||||
'click_for_detailed_msg': '{{ _('%s<br><br>click here for details!') }}'
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -15,7 +15,7 @@ function(_, alertify, pgAdmin) {
|
||||
</h4>\
|
||||
</div>\
|
||||
<div id="collapseOne" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="headingOne">\
|
||||
<div class="panel-body" style="overflow: scroll;">' + message + '</div>\
|
||||
<div class="panel-body" style="overflow: auto;">' + unescape(message) + '</div>\
|
||||
</div>\
|
||||
</div>';
|
||||
|
||||
@ -28,7 +28,7 @@ function(_, alertify, pgAdmin) {
|
||||
</h4>\
|
||||
</div>\
|
||||
<div id="collapseTwo" class="panel-collapse collapse" role="tabpanel" aria-labelledby="headingTwo">\
|
||||
<div class="panel-body" style="overflow: scroll;">' + unescape(info) + '</div>\
|
||||
<div class="panel-body" style="overflow: auto;">' + unescape(info) + '</div>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>'
|
||||
|
@ -1,6 +1,7 @@
|
||||
define(
|
||||
['jquery', 'underscore', 'underscore.string', 'pgadmin', 'pgadmin.browser.menu',
|
||||
'backbone', 'alertify', 'backform', 'pgadmin.backform', 'wcdocker'],
|
||||
'backbone', 'alertify', 'backform', 'pgadmin.backform', 'wcdocker',
|
||||
'pgadmin.alertifyjs'],
|
||||
function($, _, S, pgAdmin, Menu, Backbone, Alertify, Backform) {
|
||||
|
||||
var pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
|
||||
@ -54,7 +55,7 @@ function($, _, S, pgAdmin, Menu, Backbone, Alertify, Backform) {
|
||||
'numeric': 'uneditable-input',
|
||||
'date': 'date',
|
||||
'boolean': 'bool-text',
|
||||
'options': 'uneditable-input',
|
||||
'options': Backform.ReadonlyOptionControl,
|
||||
'multiline': 'textarea'
|
||||
},
|
||||
'edit': {
|
||||
@ -102,6 +103,11 @@ function($, _, S, pgAdmin, Menu, Backbone, Alertify, Backform) {
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
priority: 3, label: '{{ _("Properties...") }}',
|
||||
data: {'action': 'edit'}, icon: 'fa fa-pencil-square-o'
|
||||
}, {
|
||||
name: 'refresh', node: this.type, module: this,
|
||||
applies: ['object', 'context'], callback: 'refresh_node',
|
||||
priority: 2, label: '{{ _("Refresh...") }}',
|
||||
icon: 'fa fa-refresh'
|
||||
}]);
|
||||
},
|
||||
///////
|
||||
@ -161,9 +167,9 @@ function($, _, S, pgAdmin, Menu, Backbone, Alertify, Backform) {
|
||||
name: f.id, label: f.label,
|
||||
control: controlType[type][f.type],
|
||||
// Do we need to show this control in this mode?
|
||||
show: f.show && newModel[f.show] &&
|
||||
visible: f.show && newModel[f.show] &&
|
||||
typeof newModel[f.show] == "function" ?
|
||||
newModel[f.show] : undefined,
|
||||
newModel[f.show] : f.show,
|
||||
// This can be disabled in some cases (if not hidden)
|
||||
disabled: (type == 'properties' ? true : (
|
||||
f.disabled && newModel[f.disabled] &&
|
||||
@ -217,8 +223,15 @@ function($, _, S, pgAdmin, Menu, Backbone, Alertify, Backform) {
|
||||
}
|
||||
}
|
||||
})
|
||||
.error(function() {
|
||||
// TODO:: Handle the error message properly.
|
||||
.error(function(m, jqxhr) {
|
||||
// TODO:: We may not want to continue from here
|
||||
console.log(arguments);
|
||||
Alertify.pgNotifier(
|
||||
"error", jqxhr,
|
||||
S(
|
||||
"{{ _("Error fetching the properties - %%s!") }}"
|
||||
).sprintf(jqxhr.statusText).value()
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// Yay - render the view now!
|
||||
@ -440,6 +453,7 @@ function($, _, S, pgAdmin, Menu, Backbone, Alertify, Backform) {
|
||||
t.select(n);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
error: function(jqx) {
|
||||
var msg = jqx.responseText;
|
||||
@ -462,9 +476,10 @@ function($, _, S, pgAdmin, Menu, Backbone, Alertify, Backform) {
|
||||
},
|
||||
// Callback called - when a node is selected in browser tree.
|
||||
selected: function(o) {
|
||||
// Show (One of these, whose panel is open)
|
||||
// Show the information about the selected node in the below panels,
|
||||
// which are visible at this time:
|
||||
// + Properties
|
||||
// + Query
|
||||
// + Query (if applicable, otherwise empty)
|
||||
// + Dependents
|
||||
// + Dependencies
|
||||
// + Statistics
|
||||
@ -482,32 +497,39 @@ function($, _, S, pgAdmin, Menu, Backbone, Alertify, Backform) {
|
||||
// is active).
|
||||
this.showProperties(o.item, o.data,
|
||||
pgBrowser.panels['properties'].panel);
|
||||
} else if ('sql' in br.panels &&
|
||||
}
|
||||
if ('sql' in br.panels &&
|
||||
br.panels['sql'] &&
|
||||
br.panels['sql'].panel &&
|
||||
br.panels['sql'].panel.isVisible()) {
|
||||
// Show reverse engineered query for this object (when
|
||||
// the 'sql' tab is active.)
|
||||
} else if ('statistics' in br.panels &&
|
||||
// TODO:: Show reverse engineered query for this object (when 'sql'
|
||||
// tab is active.)
|
||||
}
|
||||
if ('statistics' in br.panels &&
|
||||
br.panels['statistics'] &&
|
||||
br.panels['statistics'].panel &&
|
||||
br.panels['statistics'].panel.isVisible()) {
|
||||
// Show statistics for this object (when the
|
||||
// 'statistics' tab is active.)
|
||||
} else if ('dependencies' in br.panels &&
|
||||
// TODO:: Show statistics for this object (when the 'statistics'
|
||||
// tab is active.)
|
||||
}
|
||||
if ('dependencies' in br.panels &&
|
||||
br.panels['dependencies'] &&
|
||||
br.panels['dependencies'].panel &&
|
||||
br.panels['dependencies'].panel.isVisible()) {
|
||||
// Show dependencies for this object (when the
|
||||
// TODO:: Show dependencies for this object (when the
|
||||
// 'dependencies' tab is active.)
|
||||
} else if ('dependents' in br.panels &&
|
||||
}
|
||||
if ('dependents' in br.panels &&
|
||||
br.panels['dependents'] &&
|
||||
br.panels['dependents'].panel &&
|
||||
br.panels['dependents'].panel.isVisible()) {
|
||||
// Show dependents for this object (when the
|
||||
// 'dependents' tab is active.)
|
||||
// TODO:: Show dependents for this object (when the 'dependents'
|
||||
// tab is active.)
|
||||
}
|
||||
}
|
||||
},
|
||||
refresh_node: function(args) {
|
||||
this.callbacks.selected();
|
||||
}
|
||||
},
|
||||
/**********************************************************************
|
||||
@ -644,19 +666,17 @@ function($, _, S, pgAdmin, Menu, Backbone, Alertify, Backform) {
|
||||
|
||||
if (c && !_.isEmpty(c)) {
|
||||
m.save({} ,{
|
||||
attrs: (m.isNew() ?
|
||||
m.attributes :
|
||||
m.changedAttributes()),
|
||||
attrs: m.attributes,
|
||||
success: function() {
|
||||
onSaveFunc.call();
|
||||
},
|
||||
error: function() {
|
||||
/* Reset the changed attributes on failure */
|
||||
m.changed = c;
|
||||
|
||||
/* TODO:: Alert for the user on error */
|
||||
console.log('ERROR:');
|
||||
console.log(arguments);
|
||||
error: function(m, jqxhr) {
|
||||
Alertify.pgNotifier(
|
||||
"error", jqxhr,
|
||||
S(
|
||||
"{{ _("Error during saving properties - %%s!") }}"
|
||||
).sprintf(jqxhr.statusText).value()
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -707,14 +727,8 @@ function($, _, S, pgAdmin, Menu, Backbone, Alertify, Backform) {
|
||||
if (view.model.tnode) {
|
||||
var d = _.extend({}, view.model.tnode),
|
||||
func = function(i) {
|
||||
/* Register this panel for this node */
|
||||
pgBrowser.Node.panels =
|
||||
pgBrowser.Node.panels || {};
|
||||
pgBrowser.Node.panels[d.id] = panel;
|
||||
panel.title(that.title(d));
|
||||
setTimeout(function() {
|
||||
that.showProperties(i, d, panel,
|
||||
'properties');
|
||||
closePanel();
|
||||
}, 0);
|
||||
tree.setVisible(i);
|
||||
tree.select(i);
|
||||
@ -818,7 +832,7 @@ function($, _, S, pgAdmin, Menu, Backbone, Alertify, Backform) {
|
||||
opURL = {
|
||||
'create': 'obj', 'drop': 'obj', 'edit': 'obj',
|
||||
'properties': 'obj', 'depends': 'deps',
|
||||
'statistics': 'stats'
|
||||
'statistics': 'stats', 'collections': 'nodes'
|
||||
};
|
||||
|
||||
if (d._type == this.type) {
|
||||
@ -856,5 +870,105 @@ function($, _, S, pgAdmin, Menu, Backbone, Alertify, Backform) {
|
||||
})
|
||||
});
|
||||
|
||||
pgBrowser.Collection = _.extend(_.clone(pgAdmin.Browser.Node), {
|
||||
///////
|
||||
// Initialization function
|
||||
// Generally - used to register the menus for this type of node.
|
||||
//
|
||||
// Also, look at pgAdmin.Browser.add_menus(...) function.
|
||||
//
|
||||
// Collection will not have 'Properties' menu.
|
||||
//
|
||||
// NOTE: Override this for each node for initialization purpose
|
||||
Init: function() {
|
||||
if (this.node_initialized)
|
||||
return;
|
||||
this.node_initialized = true;
|
||||
|
||||
pgAdmin.Browser.add_menus([{
|
||||
name: 'refresh', node: this.type, module: this,
|
||||
applies: ['object', 'context'], callback: 'refresh_collection',
|
||||
priority: 2, label: '{{ _("Refresh...") }}',
|
||||
icon: 'fa fa-refresh'
|
||||
}]);
|
||||
},
|
||||
callbacks: {
|
||||
refresh_collection: function() {
|
||||
// TODO:: Refresh the collection node
|
||||
console.log(arguments);
|
||||
},
|
||||
selected: function(o) {
|
||||
// Show (Node information on these panels, which one is visible.)
|
||||
// + Properties (list down the children nodes in pages)
|
||||
// + Query (Remove existing SQL)
|
||||
// + Dependents (Remove dependents)
|
||||
// + Dependencies (Remove dependencies)
|
||||
// + Statistics (TODO:: Check the current implementation in pgAdmin 3)
|
||||
|
||||
// Update the menu items
|
||||
pgAdmin.Browser.enable_disable_menus.apply(o.browser, [o.item]);
|
||||
|
||||
if (o && o.data && o.browser) {
|
||||
var br = o.browser;
|
||||
if ('properties' in br.panels &&
|
||||
br.panels['properties'] &&
|
||||
br.panels['properties'].panel &&
|
||||
br.panels['properties'].panel.isVisible()) {
|
||||
// Show object properties (only when the 'properties' tab
|
||||
// is active).
|
||||
this.showProperties(o.item, o.data,
|
||||
pgBrowser.panels['properties'].panel);
|
||||
}
|
||||
if ('sql' in br.panels &&
|
||||
br.panels['sql'] &&
|
||||
br.panels['sql'].panel &&
|
||||
br.panels['sql'].panel.isVisible()) {
|
||||
// TODO::
|
||||
// Remove the information from the sql pane
|
||||
}
|
||||
if ('statistics' in br.panels &&
|
||||
br.panels['statistics'] &&
|
||||
br.panels['statistics'].panel &&
|
||||
br.panels['statistics'].panel.isVisible()) {
|
||||
// TODO::
|
||||
// Remove information from the statistics pane
|
||||
}
|
||||
if ('dependencies' in br.panels &&
|
||||
br.panels['dependencies'] &&
|
||||
br.panels['dependencies'].panel &&
|
||||
br.panels['dependencies'].panel.isVisible()) {
|
||||
// TODO::
|
||||
// Remove information from the dependencies pane
|
||||
}
|
||||
if ('dependents' in br.panels &&
|
||||
br.panels['dependents'] &&
|
||||
br.panels['dependents'].panel &&
|
||||
br.panels['dependents'].panel.isVisible()) {
|
||||
// TODO::
|
||||
// Remove information from the dependents pane
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
/**********************************************************************
|
||||
* A hook (not a callback) to show object properties in given HTML
|
||||
* element.
|
||||
*
|
||||
* This has been used for the showing, editing properties of the node.
|
||||
* This has also been used for creating a node.
|
||||
**/
|
||||
showProperties: function(item, data, panel) {
|
||||
var that = this,
|
||||
tree = pgAdmin.Browser.tree,
|
||||
j = panel.$container.find('.obj_properties').first(),
|
||||
view = j.data('obj-view'),
|
||||
content = $('<div></div>')
|
||||
.addClass('pg-prop-content col-xs-12');
|
||||
|
||||
// TODO:: Show list of children in paging mode
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return pgAdmin.Browser.Node;
|
||||
});
|
||||
|
@ -9,24 +9,105 @@
|
||||
|
||||
"""Browser helper utilities"""
|
||||
|
||||
from abc import ABCMeta, abstractmethod, abstractproperty
|
||||
from collections import OrderedDict
|
||||
import flask
|
||||
from flask.views import View, MethodViewType, with_metaclass
|
||||
from flask.ext.babel import gettext
|
||||
|
||||
from config import PG_DEFAULT_DRIVER
|
||||
from pgadmin.browser import PgAdminModule
|
||||
from pgadmin.utils.ajax import make_json_response
|
||||
import gettext
|
||||
|
||||
|
||||
def generate_browser_node(node_id, parent_id, label, icon, inode, node_type):
|
||||
obj = {
|
||||
"id": "%s/%s" % (node_type, node_id),
|
||||
"label": label,
|
||||
"icon": icon,
|
||||
"inode": inode,
|
||||
"_type": node_type,
|
||||
"_id": node_id,
|
||||
"refid": parent_id,
|
||||
"module": 'pgadmin.node.%s' % node_type
|
||||
}
|
||||
return obj
|
||||
class NodeAttr(object):
|
||||
"""
|
||||
"""
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
@abstractmethod
|
||||
def validate(self, mode, value):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def schema(self):
|
||||
pass
|
||||
|
||||
|
||||
class PGChildModule(object):
|
||||
"""
|
||||
class PGChildModule(ServerChildModule)
|
||||
|
||||
This is a base class for children/grand-children of PostgreSQL, and
|
||||
all Postgres Plus version (i.e. Postgres Plus Advanced Server, Green Plum,
|
||||
etc).
|
||||
|
||||
Method:
|
||||
------
|
||||
* BackendSupported(manager)
|
||||
- Return True when it supports certain version.
|
||||
Uses the psycopg2 server connection manager as input for checking the
|
||||
compatibility of the current module.
|
||||
|
||||
* AddAttr(attr)
|
||||
- This adds the attribute supported for specific version only. It takes
|
||||
NodeAttr as input, and update max_ver, min_ver variables for this module.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.min_ver = 1000000000
|
||||
self.max_ver = 0
|
||||
self.attributes = {}
|
||||
|
||||
super(PGChildModule, self).__init__(*args, **kwargs)
|
||||
|
||||
def BackendSupported(self, mangaer):
|
||||
sversion = getattr(mangaer, 'sversion', None)
|
||||
|
||||
if (sversion is None or not isinstance(sversion, int)):
|
||||
return False
|
||||
|
||||
if (self.min_ver is None and self.max_ver is None):
|
||||
return True
|
||||
|
||||
assert(self.max_ver is None or isinstance(self.max_ver, int))
|
||||
assert(self.min_ver is None or isinstance(self.min_ver, int))
|
||||
|
||||
if self.min_ver is None or self.min_ver <= sversion:
|
||||
if self.max_ver is None or self.max_ver >= sversion:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@abstractmethod
|
||||
def get_nodes(self, sid=None, **kwargs):
|
||||
pass
|
||||
|
||||
def AddAttr(self, attr):
|
||||
assert(isinstance(attr, PGNodeAttr))
|
||||
|
||||
name = getattr(attr, 'name', None)
|
||||
assert(name is not None and isinstance(name, str))
|
||||
assert(name not in self.attributes)
|
||||
# TODO:: Check for naming convention
|
||||
|
||||
min_ver = getattr(attr, 'min_ver', None)
|
||||
assert(min_ver is None or isinstance(min_ver, int))
|
||||
|
||||
max_ver = getattr(attr, 'max_ver', None)
|
||||
assert(max_ver is None or isinstance(max_ver, int))
|
||||
|
||||
self.attributes[name] = attr
|
||||
|
||||
if max_ver is None:
|
||||
self.max_ver = None
|
||||
elif self.max_var is not None and self.max_ver < max_ver:
|
||||
self.max_ver < max_ver
|
||||
|
||||
if min_ver is None:
|
||||
self.min_ver = None
|
||||
elif self.min_ver is not None and self.min_ver > min_ver:
|
||||
self.min_ver = min_ver
|
||||
|
||||
|
||||
class NodeView(with_metaclass(MethodViewType, View)):
|
||||
@ -94,7 +175,10 @@ class NodeView(with_metaclass(MethodViewType, View)):
|
||||
for meth in ops:
|
||||
meths.append(meth.upper())
|
||||
if len(meths) > 0:
|
||||
cmds.append({'cmd': op, 'req': idx == 0, 'with_id': idx != 2, 'methods': meths})
|
||||
cmds.append({
|
||||
'cmd': op, 'req': (idx == 0),
|
||||
'with_id': (idx != 2), 'methods': meths
|
||||
})
|
||||
idx += 1
|
||||
|
||||
return cmds
|
||||
@ -148,28 +232,28 @@ class NodeView(with_metaclass(MethodViewType, View)):
|
||||
|
||||
assert self.cmd in self.operations, \
|
||||
"Unimplemented Command ({0}) for {1}".format(
|
||||
self.cmd,
|
||||
str(self.__class__.__name__)
|
||||
)
|
||||
self.cmd,
|
||||
str(self.__class__.__name__)
|
||||
)
|
||||
|
||||
has_args, has_id = self.check_args(**kwargs)
|
||||
|
||||
assert self.cmd in self.operations and \
|
||||
(has_id and len(self.operations[self.cmd]) > 0 and \
|
||||
meth in self.operations[self.cmd][0]) or \
|
||||
(not has_id and len(self.operations[self.cmd]) > 1 and \
|
||||
meth in self.operations[self.cmd][1]) or \
|
||||
(len(self.operations[self.cmd]) > 2 and \
|
||||
meth in self.operations[self.cmd][2]), \
|
||||
assert (self.cmd in self.operations and
|
||||
(has_id and len(self.operations[self.cmd]) > 0 and
|
||||
meth in self.operations[self.cmd][0]) or
|
||||
(not has_id and len(self.operations[self.cmd]) > 1 and
|
||||
meth in self.operations[self.cmd][1]) or
|
||||
(len(self.operations[self.cmd]) > 2 and
|
||||
meth in self.operations[self.cmd][2])), \
|
||||
"Unimplemented method ({0}) for command ({1}), which {2} an id".format(
|
||||
meth, self.cmd,
|
||||
'requires' if has_id else 'does not require'
|
||||
)
|
||||
meth, self.cmd,
|
||||
'requires' if has_id else 'does not require'
|
||||
)
|
||||
|
||||
meth = self.operations[self.cmd][0][meth] if has_id else \
|
||||
self.operations[self.cmd][1][meth] if has_args and \
|
||||
meth in self.operations[self.cmd][1] else \
|
||||
self.operations[self.cmd][2][meth]
|
||||
self.operations[self.cmd][1][meth] if has_args and \
|
||||
meth in self.operations[self.cmd][1] else \
|
||||
self.operations[self.cmd][2][meth]
|
||||
|
||||
method = getattr(self, meth, None)
|
||||
|
||||
@ -177,7 +261,7 @@ class NodeView(with_metaclass(MethodViewType, View)):
|
||||
return make_json_response(
|
||||
status=406,
|
||||
success=0,
|
||||
errormsg=gettext.gettext(
|
||||
errormsg=gettext(
|
||||
"Unimplemented method ({0}) for this url ({1})".format(
|
||||
meth, flask.request.path)
|
||||
)
|
||||
@ -187,6 +271,7 @@ class NodeView(with_metaclass(MethodViewType, View)):
|
||||
|
||||
@classmethod
|
||||
def register_node_view(cls, blueprint):
|
||||
cls.blueprint = blueprint
|
||||
id_url, url = cls.get_node_urls()
|
||||
|
||||
commands = cls.generate_ops()
|
||||
@ -221,7 +306,36 @@ class NodeView(with_metaclass(MethodViewType, View)):
|
||||
"""
|
||||
return flask.make_response(
|
||||
flask.render_template(
|
||||
"{0}/{1}.js".format(self.node_type)
|
||||
"{0}/{0}.js".format(self.node_type)
|
||||
),
|
||||
200, {'Content-Type': 'application/x-javascript'}
|
||||
)
|
||||
|
||||
def nodes(self, *args, **kwargs):
|
||||
"""Build a list of treeview nodes from the child nodes."""
|
||||
nodes = []
|
||||
|
||||
for module in self.blueprint.submodules:
|
||||
nodes.extend(module.get_nodes(*args, **kwargs))
|
||||
|
||||
return make_json_response(data=nodes)
|
||||
|
||||
|
||||
class PGChildNodeView(NodeView):
|
||||
|
||||
def nodes(self, sid=None, **kwargs):
|
||||
"""Build a list of treeview nodes from the child nodes."""
|
||||
|
||||
from pgadmin.utils.driver import get_driver
|
||||
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
||||
|
||||
nodes = []
|
||||
for module in self.blueprint.submodules:
|
||||
if isinstance(module, ServerChildModule):
|
||||
if sid is not None and manager is not None and \
|
||||
module.BackendSupported(manager):
|
||||
nodes.extend(module.get_nodes(*args, **kwargs))
|
||||
else:
|
||||
nodes.extend(module.get_nodes(*args, **kwargs))
|
||||
|
||||
return make_json_response(data=nodes)
|
||||
|
@ -8,9 +8,13 @@
|
||||
##########################################################################
|
||||
|
||||
"""A blueprint module providing utility functions for the application."""
|
||||
MODULE_NAME = 'misc'
|
||||
|
||||
import datetime
|
||||
from flask import session, current_app
|
||||
from pgadmin.utils import PgAdminModule
|
||||
import pgadmin.utils.driver as driver
|
||||
|
||||
MODULE_NAME = 'misc'
|
||||
|
||||
# Initialise the module
|
||||
blueprint = PgAdminModule(MODULE_NAME, __name__,
|
||||
@ -19,7 +23,11 @@ blueprint = PgAdminModule(MODULE_NAME, __name__,
|
||||
##########################################################################
|
||||
# A special URL used to "ping" the server
|
||||
##########################################################################
|
||||
@blueprint.route("/ping")
|
||||
|
||||
|
||||
@blueprint.route("/ping", methods=('get', 'post'))
|
||||
def ping():
|
||||
"""Generate a "PING" response to indicate that the server is alive."""
|
||||
driver.ping()
|
||||
|
||||
return "PING"
|
||||
|
@ -14,8 +14,8 @@ things:
|
||||
|
||||
1) Increment SETTINGS_SCHEMA_VERSION in config.py
|
||||
|
||||
2) Modify setup.py to ensure that the appropriate changes are made to the config
|
||||
database to upgrade it to the new version.
|
||||
2) Modify setup.py to ensure that the appropriate changes are made to the
|
||||
config database to upgrade it to the new version.
|
||||
"""
|
||||
|
||||
from flask.ext.sqlalchemy import SQLAlchemy
|
||||
@ -24,9 +24,11 @@ from flask.ext.security import UserMixin, RoleMixin
|
||||
db = SQLAlchemy()
|
||||
|
||||
# Define models
|
||||
roles_users = db.Table('roles_users',
|
||||
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
|
||||
db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))
|
||||
roles_users = db.Table(
|
||||
'roles_users',
|
||||
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
|
||||
db.Column('role_id', db.Integer(), db.ForeignKey('role.id'))
|
||||
)
|
||||
|
||||
|
||||
class Version(db.Model):
|
||||
@ -35,6 +37,7 @@ class Version(db.Model):
|
||||
name = db.Column(db.String(32), primary_key=True)
|
||||
value = db.Column(db.Integer(), nullable=False)
|
||||
|
||||
|
||||
class Role(db.Model, RoleMixin):
|
||||
"""Define a security role"""
|
||||
__tablename__ = 'role'
|
||||
@ -42,6 +45,7 @@ class Role(db.Model, RoleMixin):
|
||||
name = db.Column(db.String(128), unique=True, nullable=False)
|
||||
description = db.Column(db.String(256), nullable=False)
|
||||
|
||||
|
||||
class User(db.Model, UserMixin):
|
||||
"""Define a user object"""
|
||||
__tablename__ = 'user'
|
||||
@ -53,6 +57,7 @@ class User(db.Model, UserMixin):
|
||||
roles = db.relationship('Role', secondary=roles_users,
|
||||
backref=db.backref('users', lazy='dynamic'))
|
||||
|
||||
|
||||
class Setting(db.Model):
|
||||
"""Define a setting object"""
|
||||
__tablename__ = 'setting'
|
||||
@ -60,6 +65,7 @@ class Setting(db.Model):
|
||||
setting = db.Column(db.String(256), primary_key=True)
|
||||
value = db.Column(db.String(1024))
|
||||
|
||||
|
||||
class ServerGroup(db.Model):
|
||||
"""Define a server group for the treeview"""
|
||||
__tablename__ = 'servergroup'
|
||||
@ -92,6 +98,8 @@ class Server(db.Model):
|
||||
nullable=False)
|
||||
maintenance_db = db.Column(db.String(64), nullable=False)
|
||||
username = db.Column(db.String(64), nullable=False)
|
||||
password = db.Column(db.String(64), nullable=True)
|
||||
role = db.Column(db.String(64), nullable=True)
|
||||
ssl_mode = db.Column(
|
||||
db.String(16),
|
||||
db.CheckConstraint(
|
||||
|
@ -416,16 +416,6 @@ iframe {
|
||||
-o-text-overow: ellipsis;
|
||||
}
|
||||
|
||||
.icon-server-connected {
|
||||
background-image: url('/browser/server/static/img/server.png') !important;
|
||||
border-radius: 10px
|
||||
}
|
||||
|
||||
.icon-server-not-connected {
|
||||
background-image: url('/browser/server/static/img/serverbad.png') !important;
|
||||
border-radius: 10px
|
||||
}
|
||||
|
||||
.form-control {
|
||||
color: #333333;
|
||||
}
|
||||
@ -435,3 +425,26 @@ iframe {
|
||||
fieldset[disabled] .form-control {
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
padding: 5px;
|
||||
margin-bottom: 5px;
|
||||
background-color: #F7F7F9;
|
||||
border: 1px solid #E1E1E8;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.highlight.has-error {
|
||||
border-color: #A94442;
|
||||
}
|
||||
|
||||
.icon-fa::before {
|
||||
font: normal normal normal 14px/1 FontAwesome;
|
||||
text-rendering: auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
transform: translate(0, 0);
|
||||
display: inline-block;
|
||||
margin-left: -18px;
|
||||
margin-right: 9px;
|
||||
}
|
||||
|
@ -5,4 +5,118 @@ function(alertify) {
|
||||
alertify.defaults.theme.ok = "btn btn-primary";
|
||||
alertify.defaults.theme.cancel = "btn btn-danger";
|
||||
alertify.defaults.theme.input = "form-control";
|
||||
|
||||
alertify.pgIframeDialog || alertify.dialog('pgIframeDialog', function() {
|
||||
var iframe;
|
||||
return {
|
||||
// dialog constructor function, this will be called when the user calls
|
||||
// alertify.pgIframeDialog(message)
|
||||
main:function(message){
|
||||
//set the videoId setting and return current instance for chaining.
|
||||
return this.set({
|
||||
'pg_msg': message
|
||||
});
|
||||
},
|
||||
// we only want to override two options (padding and overflow).
|
||||
setup: function(){
|
||||
return {
|
||||
options:{
|
||||
//disable both padding and overflow control.
|
||||
padding : !1,
|
||||
overflow: !1,
|
||||
}
|
||||
};
|
||||
},
|
||||
// This will be called once the DOM is ready and will never be invoked
|
||||
// again. Here we create the iframe to embed the video.
|
||||
build:function() {
|
||||
// create the iframe element
|
||||
iframe = document.createElement('iframe');
|
||||
|
||||
iframe.src = "";
|
||||
iframe.frameBorder = "no";
|
||||
iframe.width = "100%";
|
||||
iframe.height = "100%";
|
||||
|
||||
// add it to the dialog
|
||||
this.elements.content.appendChild(iframe);
|
||||
|
||||
//give the dialog initial height (half the screen height).
|
||||
this.elements.body.style.minHeight = screen.height * .5 + 'px';
|
||||
},
|
||||
// dialog custom settings
|
||||
settings:{
|
||||
pg_msg: undefined
|
||||
},
|
||||
// listen and respond to changes in dialog settings.
|
||||
settingUpdated: function(key, oldValue, newValue){
|
||||
switch(key){
|
||||
case 'pg_msg':
|
||||
var doc = iframe.contentWindow || iframe.contentDocument;
|
||||
if (doc.document) {
|
||||
doc = doc.document;
|
||||
}
|
||||
|
||||
doc.open();
|
||||
doc.write(newValue);
|
||||
doc.close();
|
||||
|
||||
break;
|
||||
}
|
||||
},
|
||||
// listen to internal dialog events.
|
||||
hooks: {
|
||||
// triggered when a dialog option gets update.
|
||||
// warning! this will not be triggered for settings updates.
|
||||
onupdate: function(option,oldValue, newValue){
|
||||
switch(option){
|
||||
case 'resizable':
|
||||
if(newValue){
|
||||
this.elements.content.removeAttribute('style');
|
||||
iframe && iframe.removeAttribute('style');
|
||||
}else{
|
||||
this.elements.content.style.minHeight = 'inherit';
|
||||
iframe && (iframe.style.minHeight = 'inherit');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
alertify.pgNotifier = function(type, xhr, promptmsg, onJSONResult) {
|
||||
var msg = xhr.responseText,
|
||||
contentType = xhr.getResponseHeader('Content-Type');
|
||||
|
||||
if (xhr.status == 0) {
|
||||
msg = window.pgAdmin.Browser.messages.server_lost;
|
||||
}
|
||||
|
||||
if (contentType) {
|
||||
try {
|
||||
if (contentType.indexOf('text/json') == 0) {
|
||||
resp = $.parseJSON(msg);
|
||||
|
||||
if (resp.result != null && (!resp.errormsg || resp.errormsg == '') &&
|
||||
onJSONResult && typeof(onJSONResult) == 'function') {
|
||||
return onJSONResult(resp.result);
|
||||
}
|
||||
msg = resp.result || resp.errormsg || "Unknown error";
|
||||
}
|
||||
} catch (exc) {
|
||||
}
|
||||
|
||||
if (contentType.indexOf('text/html') == 0) {
|
||||
alertify.notify(
|
||||
S(
|
||||
window.pgAdmin.Browser.messages.click_for_detailed_msg
|
||||
).sprintf(promptmsg).value(), type, 0, function() {
|
||||
alertify.pgIframeDialog().show().set({ frameless: false }).set('pg_msg', msg);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
alertify.alert().show().set('message', msg).set('title', promptmsg);
|
||||
};
|
||||
});
|
||||
|
@ -66,6 +66,25 @@
|
||||
'</div>'
|
||||
].join("\n"));
|
||||
|
||||
var ReadonlyOptionControl = Backform.ReadonlyOptionControl = Backform.SelectControl.extend({
|
||||
template: _.template([
|
||||
'<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
|
||||
'<div class="<%=Backform.controlsClassName%>">',
|
||||
'<% for (var i=0; i < options.length; i++) { %>',
|
||||
' <% var option = options[i]; %>',
|
||||
' <% if (option.value === rawValue) { %>',
|
||||
' <span class="<%=Backform.controlClassName%> uneditable-input" disabled><%-option.label%></span>',
|
||||
' <% } %>',
|
||||
'<% } %>',
|
||||
'</div>'
|
||||
].join("\n")),
|
||||
events: {},
|
||||
getValueFromDOM: function() {
|
||||
return this.formatter.toRaw(this.$el.find("span").text(), this.model);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Backform Dialog view (in bootstrap tabbular form)
|
||||
// A collection of field models.
|
||||
var Dialog = Backform.Dialog = Backform.Form.extend({
|
||||
|
@ -35,6 +35,7 @@
|
||||
<script type="text/javascript">
|
||||
require.config({
|
||||
baseUrl: '',
|
||||
waitSeconds: 0,
|
||||
shim: {
|
||||
"backbone": {
|
||||
"deps": ['underscore', 'jquery'],
|
||||
|
@ -12,7 +12,9 @@
|
||||
from flask import Response
|
||||
import json
|
||||
|
||||
def make_json_response(success=1, errormsg='', info='', result=None, data=None, status=200):
|
||||
|
||||
def make_json_response(success=1, errormsg='', info='', result=None,
|
||||
data=None, status=200):
|
||||
"""Create a HTML response document describing the results of a request and
|
||||
containing the data."""
|
||||
doc = dict()
|
||||
@ -22,12 +24,71 @@ def make_json_response(success=1, errormsg='', info='', result=None, data=None,
|
||||
doc['result'] = result
|
||||
doc['data'] = data
|
||||
|
||||
return Response(response=json.dumps(doc),
|
||||
status=status,
|
||||
mimetype="text/json")
|
||||
return Response(
|
||||
response=json.dumps(doc),
|
||||
status=status,
|
||||
mimetype="text/json"
|
||||
)
|
||||
|
||||
|
||||
def make_response(response=None, status=200):
|
||||
"""Create a JSON response handled by the backbone models."""
|
||||
return Response(response=json.dumps(response),
|
||||
status=status,
|
||||
mimetype="text/json")
|
||||
return Response(
|
||||
response=json.dumps(response),
|
||||
status=status,
|
||||
mimetype="text/json"
|
||||
)
|
||||
|
||||
|
||||
def internal_server_error(errormsg=''):
|
||||
"""Create a response with HTTP status code 500 - Internal Server Error."""
|
||||
return make_json_response(
|
||||
status=500,
|
||||
success=0,
|
||||
errormsg=errormsg
|
||||
)
|
||||
|
||||
|
||||
def forbidden(errmsg=''):
|
||||
"""Create a response with HTTP status code 403 - Forbidden."""
|
||||
return make_json_response(
|
||||
status=403,
|
||||
success=0,
|
||||
errormsg=errmsg
|
||||
)
|
||||
|
||||
|
||||
def unauthorized(errormsg=''):
|
||||
"""Create a response with HTTP status code 401 - Unauthorized."""
|
||||
return make_json_response(
|
||||
status=401,
|
||||
success=0,
|
||||
errormsg=errormsg
|
||||
)
|
||||
|
||||
|
||||
def bad_request(errormsg=''):
|
||||
"""Create a response with HTTP status code 400 - Bad Request."""
|
||||
return make_json_response(
|
||||
status=400,
|
||||
success=0,
|
||||
errormsg=errormsg
|
||||
)
|
||||
|
||||
|
||||
def precondition_required(errormsg=''):
|
||||
"""Create a response with HTTP status code 428 - Precondition Required."""
|
||||
return make_json_response(
|
||||
status=428,
|
||||
success=0,
|
||||
errormsg=errormsg
|
||||
)
|
||||
|
||||
|
||||
def success_return(message=''):
|
||||
"""Create a response with HTTP status code 200 - OK."""
|
||||
return make_json_response(
|
||||
status=200,
|
||||
success=1,
|
||||
info=message
|
||||
)
|
||||
|
69
web/pgadmin/utils/crypto.py
Normal file
69
web/pgadmin/utils/crypto.py
Normal file
@ -0,0 +1,69 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2015, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
#########################################################################
|
||||
"""This File Provides Cryptography."""
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto import Random
|
||||
import base64
|
||||
|
||||
padding_string = '}'
|
||||
|
||||
|
||||
def encrypt(plaintext, key):
|
||||
"""
|
||||
Encrypt the plaintext with AES method.
|
||||
|
||||
Parameters:
|
||||
plaintext -- String to be encrypted.
|
||||
key -- Key for encryption.
|
||||
"""
|
||||
|
||||
iv = Random.new().read(AES.block_size)
|
||||
cipher = AES.new(pad(key), AES.MODE_CFB, iv)
|
||||
excrypted = base64.b64encode(iv + cipher.encrypt(plaintext))
|
||||
|
||||
return excrypted
|
||||
|
||||
|
||||
def decrypt(ciphertext, key):
|
||||
"""
|
||||
Decrypt the AES encrypted string.
|
||||
|
||||
Parameters:
|
||||
ciphertext -- Encrypted string with AES method.
|
||||
key -- key to decrypt the encrypted string.
|
||||
"""
|
||||
|
||||
global padding_string
|
||||
|
||||
ciphertext = base64.b64decode(ciphertext)
|
||||
iv = ciphertext[:AES.block_size]
|
||||
cipher = AES.new(pad(key), AES.MODE_CFB, iv)
|
||||
decrypted = cipher.decrypt(ciphertext[AES.block_size:])
|
||||
l = decrypted.count(padding_string)
|
||||
|
||||
return decrypted[:len(decrypted)-l]
|
||||
|
||||
|
||||
def pad(str):
|
||||
"""Add padding to the key."""
|
||||
|
||||
global padding_string
|
||||
str_len = len(str)
|
||||
|
||||
# Key must be maximum 32 bytes long, so take first 32 bytes
|
||||
if str_len > 32:
|
||||
return str[:32]
|
||||
|
||||
# If key size id 16, 24 or 32 bytes then padding not require
|
||||
if str_len == 16 or str_len == 24 or str_len == 32:
|
||||
return str
|
||||
|
||||
# Add padding to make key 32 bytes long
|
||||
return str + ((32 - len(str) % 32) * padding_string)
|
33
web/pgadmin/utils/driver/__init__.py
Normal file
33
web/pgadmin/utils/driver/__init__.py
Normal file
@ -0,0 +1,33 @@
|
||||
from flask import current_app
|
||||
from .registry import DriverRegistry
|
||||
|
||||
|
||||
def get_driver(type):
|
||||
drivers = getattr(current_app, '_pgadmin_server_drivers', None)
|
||||
|
||||
if drivers is None or not isinstance(drivers, dict):
|
||||
drivers = dict()
|
||||
|
||||
if type in drivers:
|
||||
return drivers[type]
|
||||
|
||||
driver = DriverRegistry.create(type)
|
||||
|
||||
if driver is not None:
|
||||
drivers[type] = driver
|
||||
setattr(current_app, '_pgadmin_server_drivers', drivers)
|
||||
|
||||
return driver
|
||||
|
||||
def init_app(app):
|
||||
drivers = dict()
|
||||
|
||||
setattr(app, '_pgadmin_server_drivers', drivers)
|
||||
DriverRegistry.load_drivers()
|
||||
|
||||
|
||||
def ping():
|
||||
drivers = getattr(current_app, '_pgadmin_server_drivers', None)
|
||||
|
||||
for type in drivers:
|
||||
drivers[type].gc()
|
152
web/pgadmin/utils/driver/abstract.py
Normal file
152
web/pgadmin/utils/driver/abstract.py
Normal file
@ -0,0 +1,152 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2015, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
from abc import ABCMeta, abstractmethod, abstractproperty
|
||||
from flask import session
|
||||
from .registry import DriverRegistry
|
||||
|
||||
|
||||
class BaseDriver(object):
|
||||
"""
|
||||
class BaseDriver(object):
|
||||
|
||||
This is a base class for different server types.
|
||||
Inherit this class to implement different type of database driver
|
||||
implementation.
|
||||
|
||||
(For PostgreSQL/Postgres Plus Advanced Server, we will be using psycopg2)
|
||||
|
||||
Abstract Properties:
|
||||
-------- ----------
|
||||
* Version (string):
|
||||
Current version string for the database server
|
||||
|
||||
Abstract Methods:
|
||||
-------- -------
|
||||
* get_connection(*args, **kwargs)
|
||||
- It should return a Connection class object, which may/may not be
|
||||
connected to the database server.
|
||||
|
||||
* release_connection(*args, **kwargs)
|
||||
- Implement the connection release logic
|
||||
|
||||
* gc()
|
||||
- Implement this function to release the connections assigned in the
|
||||
session, which has not been pinged from more than the idle timeout
|
||||
configuration.
|
||||
"""
|
||||
__metaclass__ = DriverRegistry
|
||||
|
||||
@abstractproperty
|
||||
def Version(cls):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_connection(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def release_connection(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def gc(self):
|
||||
pass
|
||||
|
||||
|
||||
class BaseConnection(object):
|
||||
"""
|
||||
class BaseConnection(object)
|
||||
|
||||
It is a base class for database connection. A different connection
|
||||
drive must implement this to expose abstract methods for this server.
|
||||
|
||||
General idea is to create a wrapper around the actaul driver
|
||||
implementation. It will be instantiated by the driver factory
|
||||
basically. And, they should not be instantiated directly.
|
||||
|
||||
|
||||
Abstract Methods:
|
||||
-------- -------
|
||||
* connect(**kwargs)
|
||||
- Define this method to connect the server using that particular driver
|
||||
implementation.
|
||||
|
||||
* execute_scalar(query, params)
|
||||
- Implement this method to execute the given query and returns single
|
||||
datum result.
|
||||
|
||||
* execute_2darray(query, params)
|
||||
- Implement this method to execute the given query and returns the result
|
||||
as a 2 dimentional array.
|
||||
|
||||
* execute_dict(query, params)
|
||||
- Implement this method to execute the given query and returns the result
|
||||
as an array of dict (column name -> value) format.
|
||||
|
||||
* connected()
|
||||
- Implement this method to get the status of the connection. It should
|
||||
return True for connected, otherwise False
|
||||
|
||||
* reset()
|
||||
- Implement this method to reconnect the database server (if possible)
|
||||
|
||||
* transaction_status()
|
||||
- Implement this method to get the transaction status for this
|
||||
connection. Range of return values different for each driver type.
|
||||
|
||||
* ping()
|
||||
- Implement this method to ping the server. There are times, a connection
|
||||
has been lost, but - the connection driver does not know about it. This
|
||||
can be helpful to figure out the actual reason for query failure.
|
||||
|
||||
* _release()
|
||||
- Implement this method to release the connection object. This should not
|
||||
be directly called using the connection object itself.
|
||||
|
||||
NOTE: Please use BaseDriver.release_connection(...) for releasing the
|
||||
connection object for better memory management, and connection pool
|
||||
management.
|
||||
"""
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
@abstractmethod
|
||||
def connect(self, **kwargs):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def execute_scalar(self, query, params=None):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def execute_2darray(self, query, params=None):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def execute_dict(self, query, params=None):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def connected(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def reset(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def transaction_status(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def ping(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _release(self):
|
||||
pass
|
601
web/pgadmin/utils/driver/psycopg2/__init__.py
Normal file
601
web/pgadmin/utils/driver/psycopg2/__init__.py
Normal file
@ -0,0 +1,601 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2015, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
from datetime import datetime
|
||||
|
||||
import psycopg2
|
||||
import psycopg2.extras
|
||||
|
||||
from flask import g, current_app, session
|
||||
from flask.ext.babel import gettext
|
||||
from flask.ext.security import current_user
|
||||
|
||||
from ..abstract import BaseDriver, BaseConnection
|
||||
from pgadmin.settings.settings_model import Server, User
|
||||
from pgadmin.utils.crypto import encrypt, decrypt
|
||||
|
||||
|
||||
_ = gettext
|
||||
|
||||
|
||||
class Connection(BaseConnection):
|
||||
"""
|
||||
class Connection(object)
|
||||
|
||||
A wrapper class, which wraps the psycopg2 connection object, and
|
||||
delegate the execution to the actual connection object, when required.
|
||||
|
||||
Methods:
|
||||
-------
|
||||
* connect(**kwargs)
|
||||
- Connect the PostgreSQL/Postgres Plus servers using the psycopg2 driver
|
||||
|
||||
* execute_scalar(query, params)
|
||||
- Execute the given query and returns single datum result
|
||||
|
||||
* execute_2darray(query, params)
|
||||
- Execute the given query and returns the result as a 2 dimentional
|
||||
array.
|
||||
|
||||
* execute_dict(query, params)
|
||||
- Execute the given query and returns the result as an array of dict
|
||||
(column name -> value) format.
|
||||
|
||||
* connected()
|
||||
- Get the status of the connection.
|
||||
Returns True if connected, otherwise False.
|
||||
|
||||
* reset()
|
||||
- Reconnect the database server (if possible)
|
||||
|
||||
* transaction_status()
|
||||
- Trasaction Status
|
||||
|
||||
* ping()
|
||||
- Ping the server.
|
||||
|
||||
* _release()
|
||||
- Release the connection object of psycopg2
|
||||
"""
|
||||
def __init__(self, manager, conn_id, db, auto_reconnect=True):
|
||||
assert(manager is not None)
|
||||
assert(conn_id is not None)
|
||||
|
||||
self.conn_id = conn_id
|
||||
self.manager = manager
|
||||
self.db = db if db is not None else manager.db
|
||||
self.conn = None
|
||||
self.auto_reconnect = auto_reconnect
|
||||
|
||||
super(Connection, self).__init__()
|
||||
|
||||
def connect(self, **kwargs):
|
||||
if self.conn:
|
||||
if self.conn.closed:
|
||||
self.conn = None
|
||||
else:
|
||||
return True, None
|
||||
|
||||
pg_conn = None
|
||||
password = None
|
||||
mgr = self.manager
|
||||
|
||||
if 'password' in kwargs:
|
||||
encpass = kwargs['password']
|
||||
else:
|
||||
encpass = getattr(mgr, 'password', None)
|
||||
|
||||
if encpass:
|
||||
# Fetch Logged in User Details.
|
||||
user = User.query.filter_by(id=current_user.id).first()
|
||||
|
||||
if user is None:
|
||||
return unauthorized(gettext("Unauthorized Request."))
|
||||
|
||||
password = decrypt(encpass, user.password)
|
||||
|
||||
try:
|
||||
import os
|
||||
os.environ['PGAPPNAME'] = 'pgAdmin IV - {0}'.format(self.conn_id)
|
||||
pg_conn = psycopg2.connect(
|
||||
host=mgr.host,
|
||||
port=mgr.port,
|
||||
database=self.db,
|
||||
user=mgr.user,
|
||||
password=password
|
||||
)
|
||||
|
||||
except psycopg2.Error as e:
|
||||
msg = e.pgerror if e.pgerror else e.message \
|
||||
if e.message else e.diag.message_detail \
|
||||
if e.diag.message_detail else str(e)
|
||||
|
||||
return False, msg
|
||||
|
||||
pg_conn.autocommit = True
|
||||
self.conn = pg_conn
|
||||
|
||||
self.conn.set_client_encoding("UNICODE")
|
||||
status, res = self.execute_scalar("""
|
||||
SET DateStyle=ISO;
|
||||
SET client_min_messages=notice;
|
||||
SET bytea_output=escape;
|
||||
""")
|
||||
if not status:
|
||||
self.conn.close()
|
||||
self.conn = None
|
||||
|
||||
return False, res
|
||||
|
||||
if mgr.role:
|
||||
status, res = self.execute_scalar("SET ROLE TO %s", [mgr.role])
|
||||
|
||||
if not status:
|
||||
self.conn.close()
|
||||
self.conn = None
|
||||
|
||||
return False, \
|
||||
_("Failed to setup the role with error message:\n{0}").format(
|
||||
res
|
||||
)
|
||||
|
||||
if mgr.ver is None:
|
||||
status, res = self.execute_scalar("SELECT version()")
|
||||
|
||||
if status:
|
||||
mgr.ver = res
|
||||
mgr.sversion = int(pg_conn.server_version)
|
||||
else:
|
||||
self.conn.close()
|
||||
self.conn = None
|
||||
|
||||
return False, res
|
||||
|
||||
if 'password' in kwargs:
|
||||
mgr.password = kwargs['password']
|
||||
|
||||
if 'modules' in kwargs and isinstance(kwargs['modules'], list):
|
||||
for m in sorted(
|
||||
kwargs['modules'], key=lambda module: module.priority
|
||||
):
|
||||
if m.instanceOf(mgr.ver):
|
||||
mgr.server_type = m.type
|
||||
mgr.module = m
|
||||
break
|
||||
|
||||
return True, None
|
||||
|
||||
def __cursor(self):
|
||||
cur = getattr(g, self.conn_id, None)
|
||||
|
||||
if self.connected() and cur and not cur.closed:
|
||||
return True, cur
|
||||
|
||||
if not self.connected():
|
||||
status = False
|
||||
errmsg = ""
|
||||
|
||||
if self.auto_reconnect:
|
||||
status, errmsg = self.connect()
|
||||
errmsg = gettext(
|
||||
"""
|
||||
Attempt to reconnect it failed with the below error:
|
||||
{0}
|
||||
""").format(errmsg)
|
||||
|
||||
if not status:
|
||||
msg = gettext("Connection was lost!\n{0}").format(errmsg)
|
||||
current_app.logger.error(msg)
|
||||
|
||||
return False, msg
|
||||
|
||||
try:
|
||||
cur = self.conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
|
||||
except psycopg2.Error as pe:
|
||||
errmsg = gettext("""
|
||||
Failed to create cursor for psycopg2 connection with error message for the \
|
||||
server#{1}:{2}:
|
||||
{0}""").format(str(pe), self.manager.sid, self.db)
|
||||
current_app.logger.error(errmsg)
|
||||
self.conn.close()
|
||||
self.conn = None
|
||||
|
||||
if self.auto_reconnect:
|
||||
status, errmsg = self.connect()
|
||||
if not status:
|
||||
msg = gettext(
|
||||
"""
|
||||
Connection for server#{0} with database "{1}" was lost.
|
||||
Attempt to reconnect it failed with the below error:
|
||||
{2}"""
|
||||
).format(self.driver.server_id, self.database, errmsg)
|
||||
current_app.logger.error(msg)
|
||||
|
||||
return False, errmsg
|
||||
else:
|
||||
return False, errmsg
|
||||
|
||||
setattr(g, self.conn_id, cur)
|
||||
|
||||
return True, cur
|
||||
|
||||
def execute_scalar(self, query, params=None):
|
||||
status, cur = self.__cursor()
|
||||
|
||||
if not status:
|
||||
return False, str(cur)
|
||||
|
||||
try:
|
||||
cur.execute(query, params)
|
||||
except psycopg2.Error as pe:
|
||||
cur.close()
|
||||
return False, str(pe)
|
||||
|
||||
if cur.rowcount > 0:
|
||||
res = cur.fetchone()
|
||||
if len(res) > 0:
|
||||
return True, res[0]
|
||||
|
||||
return True, None
|
||||
|
||||
def execute_2darray(self, query, params=None):
|
||||
status, cur = self.__cursor()
|
||||
|
||||
if not status:
|
||||
return False, str(cur)
|
||||
|
||||
try:
|
||||
cur.execute(query, params)
|
||||
except psycopg2.Error as pe:
|
||||
cur.close()
|
||||
return False, str(pe)
|
||||
|
||||
import copy
|
||||
# Get Resultset Column Name, Type and size
|
||||
columns = [copy.deepcopy(desc.__dict__) for desc in cur.description]
|
||||
|
||||
rows = []
|
||||
for row in cur:
|
||||
rows.append(row)
|
||||
|
||||
return True, {'columns': columns, 'rows': rows}
|
||||
|
||||
def execute_dict(self, query, params=None):
|
||||
status, cur = self.__cursor()
|
||||
|
||||
if not status:
|
||||
return False, str(cur)
|
||||
|
||||
try:
|
||||
cur.execute(query, params)
|
||||
except psycopg2.Error as pe:
|
||||
cur.close()
|
||||
return False, str(pe)
|
||||
|
||||
import copy
|
||||
# Get Resultset Column Name, Type and size
|
||||
columns = [copy.deepcopy(desc.__dict__) for desc in cur.description]
|
||||
|
||||
rows = []
|
||||
for row in cur:
|
||||
rows.append(dict(row))
|
||||
|
||||
return True, {'columns': columns, 'rows': rows}
|
||||
|
||||
def connected(self):
|
||||
if self.conn:
|
||||
if not self.conn.closed:
|
||||
return True
|
||||
self.conn = None
|
||||
return False
|
||||
|
||||
def reset(self):
|
||||
if self.conn:
|
||||
if self.conn.closed:
|
||||
self.conn = None
|
||||
pg_conn = None
|
||||
mgr = self.manager
|
||||
|
||||
password = getattr(mgr, 'password', None)
|
||||
|
||||
if password:
|
||||
# Fetch Logged in User Details.
|
||||
user = User.query.filter_by(id=current_user.id).first()
|
||||
|
||||
if user is None:
|
||||
return unauthorized(gettext("Unauthorized Request."))
|
||||
|
||||
password = decrypt(password, user.password)
|
||||
|
||||
try:
|
||||
pg_conn = psycopg2.connect(
|
||||
host=mgr.host,
|
||||
port=mgr.port,
|
||||
database=db,
|
||||
user=mgr.user,
|
||||
password=password
|
||||
)
|
||||
|
||||
except psycopg2.Error as e:
|
||||
msg = e.pgerror if e.pgerror else e.message \
|
||||
if e.message else e.diag.message_detail \
|
||||
if e.diag.message_detail else str(e)
|
||||
|
||||
current_app.logger.error(
|
||||
gettext(
|
||||
"""
|
||||
Failed to reset the connection of the server due to following error:
|
||||
{0}"""
|
||||
).Format(msg)
|
||||
)
|
||||
return False, msg
|
||||
|
||||
self.conn = pg_conn
|
||||
|
||||
return True, None
|
||||
|
||||
def transaction_status(self):
|
||||
if self.conn:
|
||||
return self.conn.get_transaction_status()
|
||||
return None
|
||||
|
||||
def ping(self):
|
||||
return self.execute_scalar('SELECT 1')
|
||||
|
||||
def _release(self):
|
||||
if self.conn:
|
||||
self.conn.close()
|
||||
self.conn = None
|
||||
|
||||
|
||||
class ServerManager(object):
|
||||
"""
|
||||
class ServerManager
|
||||
|
||||
This class contains the information about the given server.
|
||||
And, acts as connection manager for that particular session.
|
||||
"""
|
||||
def __init__(self, server):
|
||||
self.module = None
|
||||
self.ver = None
|
||||
self.sversion = None
|
||||
self.connections = dict()
|
||||
|
||||
self.update(server)
|
||||
|
||||
def update(self, server):
|
||||
assert(server is not None)
|
||||
assert(isinstance(server, Server))
|
||||
|
||||
self.module = None
|
||||
self.ver = None
|
||||
self.sversion = None
|
||||
self.password = None
|
||||
|
||||
self.sid = server.id
|
||||
self.stype = None
|
||||
self.host = server.host
|
||||
self.port = server.port
|
||||
self.db = server.maintenance_db
|
||||
self.user = server.username
|
||||
self.password = server.password
|
||||
self.role = server.role
|
||||
self.ssl_mode = server.ssl_mode
|
||||
self.pinged = datetime.now()
|
||||
|
||||
for con in self.connections:
|
||||
self.connections[con]._release()
|
||||
|
||||
self.connections = dict()
|
||||
|
||||
def ServerVersion(self):
|
||||
return self.ver
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
return self.sversion
|
||||
|
||||
def MajorVersion(self):
|
||||
if self.sversion is not None:
|
||||
return int(self.sversion / 10000)
|
||||
raise Exception("Information is not available!")
|
||||
|
||||
def MinorVersion(self):
|
||||
if self.sversion:
|
||||
return int(int(self.sversion / 100) % 100)
|
||||
raise Exception("Information is not available!")
|
||||
|
||||
def PatchVersion(self):
|
||||
if self.sversion:
|
||||
return int(int(self.sversion / 100) / 100)
|
||||
raise Exception("Information is not available!")
|
||||
|
||||
def connection(self, database=None, conn_id=None, auto_reconnect=True):
|
||||
|
||||
my_id = ('CONN:' + str(conn_id)) if conn_id is not None else \
|
||||
('DB:' + str(database))
|
||||
|
||||
self.pinged = datetime.now()
|
||||
|
||||
if my_id in self.connections:
|
||||
return self.connections[my_id]
|
||||
else:
|
||||
self.connections[my_id] = Connection(
|
||||
self, my_id, database, auto_reconnect
|
||||
)
|
||||
|
||||
return self.connections[my_id]
|
||||
|
||||
def release(self, database=None, conn_id=None):
|
||||
|
||||
my_id = ('CONN:' + str(conn_id)) if conn_id is not None else \
|
||||
('DB:' + str(database)) if database is not None else None
|
||||
|
||||
if my_id is not None:
|
||||
if my_id in self.connections:
|
||||
self.connections[my_id]._release()
|
||||
del self.connections[my_id]
|
||||
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
for con in self.connections:
|
||||
self.connections[con]._release()
|
||||
|
||||
self.connections = dict()
|
||||
self.password = None
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class Driver(BaseDriver):
|
||||
"""
|
||||
class Driver(BaseDriver):
|
||||
|
||||
This driver acts as a wrapper around psycopg2 connection driver
|
||||
implementation. We will be using psycopg2 for makeing connection with
|
||||
the PostgreSQL/Postgres Plus Advanced Server (EnterpriseDB).
|
||||
|
||||
Properties:
|
||||
----------
|
||||
|
||||
* Version (string):
|
||||
Version of psycopg2 driver
|
||||
|
||||
Methods:
|
||||
-------
|
||||
* get_connection(sid, database, conn_id, auto_reconnect)
|
||||
- It returns a Connection class object, which may/may not be connected
|
||||
to the database server for this sesssion
|
||||
|
||||
* release_connection(seid, database, conn_id)
|
||||
- It releases the connection object for the given conn_id/database for this
|
||||
session.
|
||||
|
||||
* connection_manager(sid, reset)
|
||||
- It returns the server connection manager for this session.
|
||||
"""
|
||||
def __init__(self, **kwargs):
|
||||
self.managers = dict()
|
||||
|
||||
super(Driver, self).__init__()
|
||||
|
||||
def connection_manager(self, sid=None):
|
||||
"""
|
||||
connection_manager(...)
|
||||
|
||||
Returns the ServerManager object for the current session. It will
|
||||
create new ServerManager object (if necessary).
|
||||
|
||||
Parameters:
|
||||
sid
|
||||
- Server ID
|
||||
"""
|
||||
assert (sid is not None and isinstance(sid, int))
|
||||
managers = None
|
||||
|
||||
import datetime
|
||||
if session['_id'] not in self.managers:
|
||||
self.managers[session['_id']] = managers = dict()
|
||||
else:
|
||||
managers = self.managers[session['_id']]
|
||||
|
||||
managers['pinged'] = datetime.datetime.now()
|
||||
if sid not in managers:
|
||||
from pgadmin.settings.settings_model import Server
|
||||
s = Server.query.filter_by(id=sid).first()
|
||||
|
||||
managers[str(sid)] = ServerManager(s)
|
||||
|
||||
return managers[str(sid)]
|
||||
|
||||
return managers[str(sid)]
|
||||
|
||||
def Version(cls):
|
||||
"""
|
||||
Version(...)
|
||||
|
||||
Returns the current version of psycopg2 driver
|
||||
"""
|
||||
version = getattr(psycopg2, '__version__', None)
|
||||
|
||||
if version:
|
||||
return version
|
||||
|
||||
raise Exception(
|
||||
"Driver Version information for psycopg2 is not available!"
|
||||
)
|
||||
|
||||
def get_connection(
|
||||
self, sid, database=None, conn_id=None, auto_reconnect=True
|
||||
):
|
||||
"""
|
||||
get_connection(...)
|
||||
|
||||
Returns the connection object for the certain connection-id/database
|
||||
for the specific server, identified by sid. Create a new Connection
|
||||
object (if require).
|
||||
|
||||
Parameters:
|
||||
sid
|
||||
- Server ID
|
||||
database
|
||||
- Database, on which the connection needs to be made
|
||||
If provided none, maintenance_db for the server will be used,
|
||||
while generating new Connection object.
|
||||
conn_id
|
||||
- Identification String for the Connection This will be used by
|
||||
certain tools, which will require a dedicated connection for it.
|
||||
i.e. Debugger, Query Tool, etc.
|
||||
auto_reconnect
|
||||
- This parameters define, if we should attempt to reconnect the
|
||||
database server automatically, when connection has been lost for
|
||||
any reason. Certain tools like Debugger will require a permenant
|
||||
connection, and it stops working on disconnection.
|
||||
|
||||
"""
|
||||
manager = connection_manager(sid)
|
||||
|
||||
return manager.connection(database, conn_id, connect, **kwargs)
|
||||
|
||||
def release_connection(self, sid, database=None, conn_id=None):
|
||||
"""
|
||||
Release the connection for the given connection-id/database in this
|
||||
session.
|
||||
"""
|
||||
return connection_manager(sid).release(database, conn_id)
|
||||
|
||||
def gc(self):
|
||||
"""
|
||||
Release the connections for the sessions, which have not pinged the
|
||||
server for more than config.MAX_SESSION_IDLE_TIME.
|
||||
"""
|
||||
import datetime
|
||||
from config import MAX_SESSION_IDLE_TIME
|
||||
|
||||
assert(MAX_SESSION_IDLE_TIME is not None and
|
||||
isinstance(MAX_SESSION_IDLE_TIME, int))
|
||||
|
||||
# Mininum session idle is 20 minutes
|
||||
max_idle_time = max(MAX_SESSION_IDLE_TIME, 20)
|
||||
session_idle_timeout = datetime.timedelta(minutes=max_idle_time)
|
||||
|
||||
curr_time = datetime.datetime.now()
|
||||
|
||||
for sess in self.managers:
|
||||
sess_mgr = self.managers[sess]
|
||||
|
||||
if sess == session['_id']:
|
||||
sess_mgr['pinged'] = curr_time
|
||||
continue
|
||||
|
||||
if (curr_time - sess_mgr['pinged'] >= session_idle_timeout):
|
||||
for mgr in [m for m in sess_mgr if isinstance(m,
|
||||
ServerManager)]:
|
||||
mgr.release()
|
84
web/pgadmin/utils/driver/registry.py
Normal file
84
web/pgadmin/utils/driver/registry.py
Normal file
@ -0,0 +1,84 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2015, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
from abc import ABCMeta
|
||||
from flask.ext.babel import gettext
|
||||
|
||||
|
||||
def _decorate_cls_name(module_name):
|
||||
l = len(__package__) + 1
|
||||
|
||||
if len(module_name) > l and module_name.startswith(__package__):
|
||||
return module_name[l:]
|
||||
|
||||
return module_name
|
||||
|
||||
|
||||
class DriverRegistry(ABCMeta):
|
||||
"""
|
||||
class DriverRegistry(object)
|
||||
Every Driver will be registered automatically by its module name.
|
||||
|
||||
This uses factory pattern to genreate driver object based on its name
|
||||
automatically.
|
||||
|
||||
Class-level Methods:
|
||||
----------- -------
|
||||
* __init__(...)
|
||||
- It will be used to register type of drivers. You don't need to call
|
||||
this function explicitly. This will be automatically executed, whenever
|
||||
we create class and inherit from BaseDriver, it will register it as
|
||||
available driver in DriverRegistry. Because - the __metaclass__ for
|
||||
BaseDriver is set it to DriverRegistry, and it will create new instance
|
||||
of this DriverRegistry per class.
|
||||
|
||||
* create(type, *args, **kwargs)
|
||||
- Create type of driver object for this server, from the available
|
||||
driver list (if available, or raise exception).
|
||||
|
||||
* load_drivers():
|
||||
- Use this function from init_app(...) to load all available drivers in
|
||||
the registry.
|
||||
"""
|
||||
registry = dict()
|
||||
drivers = dict()
|
||||
|
||||
def __init__(cls, name, bases, d):
|
||||
|
||||
# Register this type of driver, based on the module name
|
||||
# Avoid registering the BaseDriver itself
|
||||
|
||||
if name != 'BaseDriver':
|
||||
DriverRegistry.registry[_decorate_cls_name(d['__module__'])] = cls
|
||||
|
||||
ABCMeta.__init__(cls, name, bases, d)
|
||||
|
||||
@classmethod
|
||||
def create(cls, name, **kwargs):
|
||||
|
||||
if name in DriverRegistry.drivers:
|
||||
return DriverRegistry.drivers[name]
|
||||
|
||||
if name in DriverRegistry.registry:
|
||||
DriverRegistry.drivers[name] = \
|
||||
(DriverRegistry.registry[name])(**kwargs)
|
||||
return DriverRegistry.drivers[name]
|
||||
|
||||
raise NotImplementedError(
|
||||
gettext("Driver - '{0}' has not been implemented!").format(name)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def load_drivers(cls):
|
||||
DriverRegistry.registry = dict()
|
||||
|
||||
from importlib import import_module
|
||||
from werkzeug.utils import find_modules
|
||||
|
||||
for module_name in find_modules(__package__, True):
|
||||
module = import_module(module_name)
|
167
web/setup.py
167
web/setup.py
@ -10,34 +10,47 @@
|
||||
"""Perform the initial setup of the application, by creating the auth
|
||||
and settings database."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import getpass
|
||||
import random
|
||||
import string
|
||||
|
||||
from flask import Flask
|
||||
from flask.ext.sqlalchemy import SQLAlchemy
|
||||
from flask.ext.security import Security, SQLAlchemyUserDatastore
|
||||
from flask.ext.security.utils import encrypt_password
|
||||
from pgadmin.settings.settings_model import db, Role, User, Server, ServerGroup, Version
|
||||
|
||||
import getpass, os, random, sys, string
|
||||
from pgadmin.settings.settings_model import db, Role, User, Server, \
|
||||
ServerGroup, Version
|
||||
|
||||
# Configuration settings
|
||||
import config
|
||||
|
||||
|
||||
def do_setup(app):
|
||||
|
||||
"""Create a new settings database from scratch"""
|
||||
if config.SERVER_MODE is False:
|
||||
print("NOTE: Configuring authentication for DESKTOP mode.")
|
||||
email = config.DESKTOP_USER
|
||||
p1 = ''.join([random.choice(string.ascii_letters + string.digits) for n in xrange(32)])
|
||||
p1 = ''.join([
|
||||
random.choice(string.ascii_letters + string.digits)
|
||||
for n in xrange(32)
|
||||
])
|
||||
|
||||
else:
|
||||
print("NOTE: Configuring authentication for SERVER mode.\n")
|
||||
|
||||
# Prompt the user for their default username and password.
|
||||
print("Enter the email address and password to use for the initial pgAdmin user account:\n")
|
||||
print("""
|
||||
Enter the email address and password to use for the initial pgAdmin user \
|
||||
account:\n""")
|
||||
email = ''
|
||||
while email == '':
|
||||
email = raw_input("Email address: ")
|
||||
|
||||
pprompt = lambda: (getpass.getpass(), getpass.getpass('Retype password: '))
|
||||
def pprompt():
|
||||
return getpass.getpass(), getpass.getpass('Retype password:')
|
||||
|
||||
p1, p2 = pprompt()
|
||||
while p1 != p2:
|
||||
@ -52,7 +65,10 @@ def do_setup(app):
|
||||
password = encrypt_password(p1)
|
||||
|
||||
db.create_all()
|
||||
user_datastore.create_role(name='Administrators', description='pgAdmin Administrators Role')
|
||||
user_datastore.create_role(
|
||||
name='Administrators',
|
||||
description='pgAdmin Administrators Role'
|
||||
)
|
||||
user_datastore.create_user(email=email, password=password)
|
||||
db.session.flush()
|
||||
user_datastore.add_role_to_user(email, 'Administrators')
|
||||
@ -63,14 +79,20 @@ def do_setup(app):
|
||||
db.session.merge(server_group)
|
||||
|
||||
# Set the schema version
|
||||
version = Version(name='ConfigDB', value=config.SETTINGS_SCHEMA_VERSION)
|
||||
version = Version(
|
||||
name='ConfigDB', value=config.SETTINGS_SCHEMA_VERSION
|
||||
)
|
||||
db.session.merge(version)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
# Done!
|
||||
print("")
|
||||
print("The configuration database has been created at %s" % config.SQLITE_PATH)
|
||||
print(
|
||||
"The configuration database has been created at {0}".format(
|
||||
config.SQLITE_PATH
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def do_upgrade(app, datastore, security, version):
|
||||
@ -80,12 +102,80 @@ def do_upgrade(app, datastore, security, version):
|
||||
# version.
|
||||
#######################################################################
|
||||
|
||||
# Changes introduced in schema version 2
|
||||
if int(version.value) < 2:
|
||||
# Create the 'server' table
|
||||
db.metadata.create_all(db.engine, tables=[Server.__table__])
|
||||
elif int(version.value) < 3:
|
||||
db.engine.execute('ALTER TABLE server ADD COLUMN comment TEXT(1024)');
|
||||
with app.app_context():
|
||||
version = Version.query.filter_by(name='ConfigDB').first()
|
||||
|
||||
# Pre-flight checks
|
||||
if int(version.value) > int(config.SETTINGS_SCHEMA_VERSION):
|
||||
print("""
|
||||
The database schema version is {0}, whilst the version required by the \
|
||||
software is {1}.
|
||||
Exiting...""".format(version.value, config.SETTINGS_SCHEMA_VERSION))
|
||||
sys.exit(1)
|
||||
elif int(version.value) == int(config.SETTINGS_SCHEMA_VERSION):
|
||||
print("""
|
||||
The database schema version is {0} as required.
|
||||
Exiting...""".format(version.value))
|
||||
sys.exit(1)
|
||||
|
||||
app.logger.info(
|
||||
"NOTE: Upgrading database schema from version %d to %d." %
|
||||
(version.value, config.SETTINGS_SCHEMA_VERSION)
|
||||
)
|
||||
|
||||
#######################################################################
|
||||
# Run whatever is required to update the database schema to the current
|
||||
# version. Always use "< REQUIRED_VERSION" as the test for readability
|
||||
#######################################################################
|
||||
|
||||
# Changes introduced in schema version 2
|
||||
if int(version.value) < 2:
|
||||
# Create the 'server' table
|
||||
db.metadata.create_all(db.engine, tables=[Server.__table__])
|
||||
if int(version.value) < 3:
|
||||
db.engine.execute(
|
||||
'ALTER TABLE server ADD COLUMN comment TEXT(1024)'
|
||||
)
|
||||
if int(version.value) < 4:
|
||||
db.engine.execute(
|
||||
'ALTER TABLE server ADD COLUMN password TEXT(64)'
|
||||
)
|
||||
if int(version.value) < 5:
|
||||
db.engine.execute('ALTER TABLE server ADD COLUMN role text(64)')
|
||||
if int(version.value) == 6:
|
||||
db.engine.execute("ALTER TABLE server RENAME TO server_old")
|
||||
db.engine.execute("""
|
||||
CREATE TABLE server (
|
||||
id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
servergroup_id INTEGER NOT NULL,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
host VARCHAR(128) NOT NULL,
|
||||
port INTEGER NOT NULL CHECK (port >= 1024 AND port <= 65534),
|
||||
maintenance_db VARCHAR(64) NOT NULL,
|
||||
username VARCHAR(64) NOT NULL,
|
||||
ssl_mode VARCHAR(16) NOT NULL CHECK (
|
||||
ssl_mode IN (
|
||||
'allow', 'prefer', 'require', 'disable', 'verify-ca', 'verify-full'
|
||||
)),
|
||||
comment VARCHAR(1024), password TEXT(64), role text(64),
|
||||
PRIMARY KEY (id),
|
||||
FOREIGN KEY(user_id) REFERENCES user (id),
|
||||
FOREIGN KEY(servergroup_id) REFERENCES servergroup (id)
|
||||
)""")
|
||||
db.engine.execute("""
|
||||
INSERT INTO server (
|
||||
id, user_id, servergroup_id, name, host, port, maintenance_db, username,
|
||||
ssl_mode, comment, password, role
|
||||
) SELECT
|
||||
id, user_id, servergroup_id, name, host, port, maintenance_db, username,
|
||||
ssl_mode, comment, password, role
|
||||
FROM server_old""")
|
||||
db.engine.execute("DROP TABLE server_old")
|
||||
|
||||
# Finally, update the schema version
|
||||
version.value = config.SETTINGS_SCHEMA_VERSION
|
||||
db.session.merge(version)
|
||||
|
||||
# Finally, update the schema version
|
||||
version.value = config.SETTINGS_SCHEMA_VERSION
|
||||
@ -94,8 +184,10 @@ def do_upgrade(app, datastore, security, version):
|
||||
db.session.commit()
|
||||
|
||||
# Done!
|
||||
print("")
|
||||
print("The configuration database %s has been upgraded to version %d" % (config.SQLITE_PATH, config.SETTINGS_SCHEMA_VERSION))
|
||||
app.logger.info(
|
||||
"The configuration database %s has been upgraded to version %d" %
|
||||
(config.SQLITE_PATH, config.SETTINGS_SCHEMA_VERSION)
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
# Do stuff!
|
||||
@ -103,24 +195,31 @@ def do_upgrade(app, datastore, security, version):
|
||||
if __name__ == '__main__':
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(config)
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + config.SQLITE_PATH.replace('\\', '/')
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = \
|
||||
'sqlite:///' + config.SQLITE_PATH.replace('\\', '/')
|
||||
db.init_app(app)
|
||||
|
||||
print("pgAdmin 4 - Application Initialisation")
|
||||
print("======================================\n")
|
||||
|
||||
local_config = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'config_local.py')
|
||||
local_config = os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)),
|
||||
'config_local.py'
|
||||
)
|
||||
if not os.path.isfile(local_config):
|
||||
print("The configuration file %s does not exist.\n" % local_config)
|
||||
print("Before running this application, ensure that config_local.py has been created")
|
||||
print("and sets values for SECRET_KEY, SECURITY_PASSWORD_SALT and CSRF_SESSION_KEY")
|
||||
print("at bare minimum. See config.py for more information and a complete list of")
|
||||
print("settings. Exiting...")
|
||||
print("""
|
||||
The configuration file - {0} does not exist.
|
||||
Before running this application, ensure that config_local.py has been created
|
||||
and sets values for SECRET_KEY, SECURITY_PASSWORD_SALT and CSRF_SESSION_KEY
|
||||
at bare minimum. See config.py for more information and a complete list of
|
||||
settings. Exiting...""".format(local_config))
|
||||
sys.exit(1)
|
||||
|
||||
# Check if the database exists. If it does, tell the user and exit.
|
||||
if os.path.isfile(config.SQLITE_PATH):
|
||||
print("The configuration database %s already exists.\nEntering upgrade mode...\n" % config.SQLITE_PATH)
|
||||
print("""
|
||||
The configuration database %s already exists.
|
||||
Entering upgrade mode...""".format(config.SQLITE_PATH))
|
||||
|
||||
# Setup Flask-Security
|
||||
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
|
||||
@ -132,15 +231,23 @@ if __name__ == '__main__':
|
||||
|
||||
# Pre-flight checks
|
||||
if int(version.value) > int(config.SETTINGS_SCHEMA_VERSION):
|
||||
print("The database schema version is %d, whilst the version required by the software is %d.\nExiting..."
|
||||
% (version.value, config.SETTINGS_SCHEMA_VERSION))
|
||||
print("""
|
||||
The database schema version is %d, whilst the version required by the \
|
||||
software is %d.
|
||||
Exiting...""".format(version.value, config.SETTINGS_SCHEMA_VERSION))
|
||||
sys.exit(1)
|
||||
elif int(version.value) == int(config.SETTINGS_SCHEMA_VERSION):
|
||||
print("The database schema version is %d as required.\nExiting..." % (version.value))
|
||||
print("""
|
||||
The database schema version is %d as required.
|
||||
Exiting...""".format(version.value))
|
||||
sys.exit(1)
|
||||
|
||||
print("NOTE: Upgrading database schema from version %d to %d." % (version.value, config.SETTINGS_SCHEMA_VERSION))
|
||||
print("NOTE: Upgrading database schema from version %d to %d." % (
|
||||
version.value, config.SETTINGS_SCHEMA_VERSION
|
||||
))
|
||||
do_upgrade(app, user_datastore, security, version)
|
||||
else:
|
||||
print("The configuration database %s does not exist.\nEntering initial setup mode...\n" % config.SQLITE_PATH)
|
||||
print("""
|
||||
The configuration database - {0} does not exist.
|
||||
Entering initial setup mode...""".format(config.SQLITE_PATH))
|
||||
do_setup(app)
|
||||
|
Loading…
Reference in New Issue
Block a user