mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Add support for extensions.
This commit is contained in:
committed by
Dave Page
parent
f466e0169a
commit
c950683fa1
@@ -0,0 +1,480 @@
|
|||||||
|
##########################################################################
|
||||||
|
#
|
||||||
|
# pgAdmin 4 - PostgreSQL Tools
|
||||||
|
#
|
||||||
|
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
|
||||||
|
# This software is released under the PostgreSQL Licence
|
||||||
|
#
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
""" Implements Extension Node """
|
||||||
|
|
||||||
|
import json
|
||||||
|
from flask import render_template, make_response, request, jsonify
|
||||||
|
from flask.ext.babel import gettext
|
||||||
|
from pgadmin.utils.ajax import make_json_response, \
|
||||||
|
make_response as ajax_response, internal_server_error
|
||||||
|
from pgadmin.browser.utils import PGChildNodeView
|
||||||
|
from pgadmin.browser.collection import CollectionNodeModule
|
||||||
|
import pgadmin.browser.server_groups.servers.databases as databases
|
||||||
|
from pgadmin.utils.driver import get_driver
|
||||||
|
from config import PG_DEFAULT_DRIVER
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
# As unicode type is not available in python3
|
||||||
|
# If we check a variable is "isinstance(variable, str)
|
||||||
|
# it breaks in python 3 as variable type is not string its unicode.
|
||||||
|
# We assign basestring as str type if it is python3, unicode
|
||||||
|
# if it is python2.
|
||||||
|
|
||||||
|
try:
|
||||||
|
unicode = unicode
|
||||||
|
except NameError:
|
||||||
|
# 'unicode' is undefined, must be Python 3
|
||||||
|
str = str
|
||||||
|
unicode = str
|
||||||
|
bytes = bytes
|
||||||
|
basestring = (str, bytes)
|
||||||
|
else:
|
||||||
|
# 'unicode' exists, must be Python 2
|
||||||
|
str = str
|
||||||
|
unicode = unicode
|
||||||
|
bytes = str
|
||||||
|
basestring = basestring
|
||||||
|
|
||||||
|
|
||||||
|
class ExtensionModule(CollectionNodeModule):
|
||||||
|
"""
|
||||||
|
class ExtensionModule(Object):
|
||||||
|
|
||||||
|
A collection Node which inherits CollectionNodeModule
|
||||||
|
class and define methods to get child nodes, to load its own
|
||||||
|
javascript file.
|
||||||
|
"""
|
||||||
|
NODE_TYPE = "extension"
|
||||||
|
COLLECTION_LABEL = gettext("Extensions")
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Initialising the base class
|
||||||
|
"""
|
||||||
|
super(ExtensionModule, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def get_nodes(self, gid, sid, did):
|
||||||
|
"""
|
||||||
|
Generate the collection node
|
||||||
|
"""
|
||||||
|
yield self.generate_browser_collection_node(did)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def node_inode(self):
|
||||||
|
"""
|
||||||
|
If a node have child return True otherwise False
|
||||||
|
"""
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def script_load(self):
|
||||||
|
"""
|
||||||
|
Load the module script for extension, when any of the database nodes are
|
||||||
|
initialized.
|
||||||
|
"""
|
||||||
|
return databases.DatabaseModule.NODE_TYPE
|
||||||
|
|
||||||
|
|
||||||
|
# Create blueprint of extension module
|
||||||
|
blueprint = ExtensionModule(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ExtensionView(PGChildNodeView):
|
||||||
|
"""
|
||||||
|
This is a class for extension nodes which inherits the
|
||||||
|
properties and methods from NodeView class and define
|
||||||
|
various methods to list, create, update and delete extension.
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
---------
|
||||||
|
* node_type - tells which type of node it is
|
||||||
|
* parent_ids - id with its type and name of parent nodes
|
||||||
|
* ids - id with type and name of extension module being used.
|
||||||
|
* operations - function routes mappings defined.
|
||||||
|
"""
|
||||||
|
node_type = blueprint.node_type
|
||||||
|
|
||||||
|
parent_ids = [
|
||||||
|
{'type': 'int', 'id': 'gid'},
|
||||||
|
{'type': 'int', 'id': 'sid'},
|
||||||
|
{'type': 'int', 'id': 'did'}
|
||||||
|
]
|
||||||
|
ids = [
|
||||||
|
{'type': 'int', 'id': 'eid'}
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = dict({
|
||||||
|
'obj': [
|
||||||
|
{'get': 'properties', 'delete': 'delete', 'put': 'update'},
|
||||||
|
{'get': 'list', 'post': 'create'}
|
||||||
|
],
|
||||||
|
'delete': [{'delete': 'delete'}],
|
||||||
|
'nodes': [{'get': 'node'}, {'get': 'nodes'}],
|
||||||
|
'sql': [{'get': 'sql'}],
|
||||||
|
'msql': [{'get': 'msql'}, {'get': 'msql'}],
|
||||||
|
'stats': [{'get': 'statistics'}],
|
||||||
|
'dependency': [{'get': 'dependencies'}],
|
||||||
|
'dependent': [{'get': 'dependents'}],
|
||||||
|
'module.js': [{}, {}, {'get': 'module_js'}],
|
||||||
|
'avails': [{}, {'get': 'avails'}],
|
||||||
|
'schemas': [{}, {'get': 'schemas'}],
|
||||||
|
'children': [{'get': 'children'}]
|
||||||
|
})
|
||||||
|
|
||||||
|
def check_precondition(f):
|
||||||
|
"""
|
||||||
|
This function will behave as a decorator which will checks
|
||||||
|
database connection before running view, it will also attaches
|
||||||
|
manager,conn & template_path properties to self
|
||||||
|
"""
|
||||||
|
@wraps(f)
|
||||||
|
def wrap(*args, **kwargs):
|
||||||
|
# Here args[0] will hold self & kwargs will hold gid,sid,did
|
||||||
|
self = args[0]
|
||||||
|
self.manager = get_driver(
|
||||||
|
PG_DEFAULT_DRIVER
|
||||||
|
).connection_manager(kwargs['sid'])
|
||||||
|
self.conn = self.manager.connection(did=kwargs['did'])
|
||||||
|
self.template_path = 'extensions/sql'
|
||||||
|
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
return wrap
|
||||||
|
|
||||||
|
@check_precondition
|
||||||
|
def list(self, gid, sid, did):
|
||||||
|
"""
|
||||||
|
Fetches all extensions properties and render into properties tab
|
||||||
|
"""
|
||||||
|
SQL = render_template("/".join([self.template_path, 'properties.sql']))
|
||||||
|
status, res = self.conn.execute_dict(SQL)
|
||||||
|
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=res)
|
||||||
|
return ajax_response(
|
||||||
|
response=res['rows'],
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
|
||||||
|
@check_precondition
|
||||||
|
def nodes(self, gid, sid, did):
|
||||||
|
"""
|
||||||
|
Lists all extensions under the Extensions Collection node
|
||||||
|
"""
|
||||||
|
res = []
|
||||||
|
SQL = render_template("/".join([self.template_path, 'properties.sql']))
|
||||||
|
status, rset = self.conn.execute_2darray(SQL)
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=rset)
|
||||||
|
|
||||||
|
for row in rset['rows']:
|
||||||
|
res.append(
|
||||||
|
self.blueprint.generate_browser_node(
|
||||||
|
row['eid'],
|
||||||
|
did,
|
||||||
|
row['name'],
|
||||||
|
'icon-extension'
|
||||||
|
))
|
||||||
|
|
||||||
|
return make_json_response(
|
||||||
|
data=res,
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
|
||||||
|
@check_precondition
|
||||||
|
def properties(self, gid, sid, did, eid):
|
||||||
|
"""
|
||||||
|
Fetch the properties of a single extension and render in properties tab
|
||||||
|
"""
|
||||||
|
SQL = render_template("/".join(
|
||||||
|
[self.template_path, 'properties.sql']), eid=eid)
|
||||||
|
status, res = self.conn.execute_dict(SQL)
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=res)
|
||||||
|
|
||||||
|
return ajax_response(
|
||||||
|
response=res['rows'][0],
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
|
||||||
|
@check_precondition
|
||||||
|
def create(self, gid, sid, did):
|
||||||
|
"""
|
||||||
|
Create a new extension object
|
||||||
|
"""
|
||||||
|
required_args = [
|
||||||
|
'name'
|
||||||
|
]
|
||||||
|
|
||||||
|
data = request.form if request.form else \
|
||||||
|
json.loads(request.data.decode())
|
||||||
|
for arg in required_args:
|
||||||
|
if arg not in data:
|
||||||
|
return make_json_response(
|
||||||
|
status=410,
|
||||||
|
success=0,
|
||||||
|
errormsg=gettext(
|
||||||
|
"Couldn't find the required parameter (%s)." % arg
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
status, res = self.conn.execute_dict(
|
||||||
|
render_template(
|
||||||
|
"/".join([self.template_path, 'create.sql']),
|
||||||
|
data=data
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=res)
|
||||||
|
|
||||||
|
status, rset = self.conn.execute_dict(
|
||||||
|
render_template(
|
||||||
|
"/".join([self.template_path, 'properties.sql']),
|
||||||
|
ename=data['name']
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=rset)
|
||||||
|
|
||||||
|
for row in rset['rows']:
|
||||||
|
return jsonify(
|
||||||
|
node=self.blueprint.generate_browser_node(
|
||||||
|
row['eid'],
|
||||||
|
did,
|
||||||
|
row['name'],
|
||||||
|
'icon-extension'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@check_precondition
|
||||||
|
def update(self, gid, sid, did, eid):
|
||||||
|
"""
|
||||||
|
This function will update an extension object
|
||||||
|
"""
|
||||||
|
data = request.form if request.form else \
|
||||||
|
json.loads(request.data.decode())
|
||||||
|
SQL = self.getSQL(gid, sid, data, did, eid)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if SQL and isinstance(SQL, basestring) and \
|
||||||
|
SQL.strip('\n') and SQL.strip(' '):
|
||||||
|
status, res = self.conn.execute_dict(SQL)
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=res)
|
||||||
|
|
||||||
|
return make_json_response(
|
||||||
|
success=1,
|
||||||
|
info="Extension updated",
|
||||||
|
data={
|
||||||
|
'id': eid,
|
||||||
|
'sid': sid,
|
||||||
|
'gid': gid
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return make_json_response(
|
||||||
|
success=1,
|
||||||
|
info="Nothing to update",
|
||||||
|
data={
|
||||||
|
'id': did,
|
||||||
|
'sid': sid,
|
||||||
|
'gid': gid
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return internal_server_error(errormsg=str(e))
|
||||||
|
|
||||||
|
@check_precondition
|
||||||
|
def delete(self, gid, sid, did, eid):
|
||||||
|
"""
|
||||||
|
This function will drop/drop cascade a extension object
|
||||||
|
"""
|
||||||
|
cascade = True if self.cmd == 'delete' else False
|
||||||
|
try:
|
||||||
|
# check if extension with eid exists
|
||||||
|
SQL = render_template("/".join(
|
||||||
|
[self.template_path, 'delete.sql']), eid=eid)
|
||||||
|
status, name = self.conn.execute_scalar(SQL)
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=name)
|
||||||
|
# drop extension
|
||||||
|
SQL = render_template("/".join(
|
||||||
|
[self.template_path, 'delete.sql']
|
||||||
|
), name=name, cascade=cascade)
|
||||||
|
status, res = self.conn.execute_scalar(SQL)
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=res)
|
||||||
|
|
||||||
|
return make_json_response(
|
||||||
|
success=1,
|
||||||
|
info=gettext("Extension dropped"),
|
||||||
|
data={
|
||||||
|
'id': did,
|
||||||
|
'sid': sid,
|
||||||
|
'gid': gid,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return internal_server_error(errormsg=str(e))
|
||||||
|
|
||||||
|
@check_precondition
|
||||||
|
def msql(self, gid, sid, did, eid=None):
|
||||||
|
"""
|
||||||
|
This function returns modified SQL
|
||||||
|
"""
|
||||||
|
data = request.args.copy()
|
||||||
|
SQL = self.getSQL(gid, sid, data, did, eid)
|
||||||
|
if SQL and isinstance(SQL, basestring) and SQL.strip('\n') \
|
||||||
|
and SQL.strip(' '):
|
||||||
|
return make_json_response(
|
||||||
|
data=SQL,
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return make_json_response(
|
||||||
|
data=gettext('-- Modified SQL --'),
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
|
||||||
|
def getSQL(self, gid, sid, data, did, eid=None):
|
||||||
|
"""
|
||||||
|
This function will generate sql from model data
|
||||||
|
"""
|
||||||
|
required_args = [
|
||||||
|
'name'
|
||||||
|
]
|
||||||
|
try:
|
||||||
|
if eid is not None:
|
||||||
|
SQL = render_template("/".join(
|
||||||
|
[self.template_path, 'properties.sql']
|
||||||
|
), eid=eid)
|
||||||
|
status, res = self.conn.execute_dict(SQL)
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=res)
|
||||||
|
old_data = res['rows'][0]
|
||||||
|
for arg in required_args:
|
||||||
|
if arg not in data:
|
||||||
|
data[arg] = old_data[arg]
|
||||||
|
SQL = render_template("/".join(
|
||||||
|
[self.template_path, 'update.sql']
|
||||||
|
), data=data, o_data=old_data)
|
||||||
|
else:
|
||||||
|
SQL = render_template("/".join(
|
||||||
|
[self.template_path, 'create.sql']
|
||||||
|
), data=data)
|
||||||
|
return SQL
|
||||||
|
except Exception as e:
|
||||||
|
return internal_server_error(errormsg=str(e))
|
||||||
|
|
||||||
|
@check_precondition
|
||||||
|
def avails(self, gid, sid, did):
|
||||||
|
"""
|
||||||
|
This function with fetch all the available extensions
|
||||||
|
"""
|
||||||
|
SQL = render_template("/".join([self.template_path, 'extensions.sql']))
|
||||||
|
status, rset = self.conn.execute_dict(SQL)
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=rset)
|
||||||
|
return make_json_response(
|
||||||
|
data=rset['rows'],
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
|
||||||
|
@check_precondition
|
||||||
|
def schemas(self, gid, sid, did):
|
||||||
|
"""
|
||||||
|
This function with fetch all the schemas
|
||||||
|
"""
|
||||||
|
SQL = render_template("/".join([self.template_path, 'schemas.sql']))
|
||||||
|
status, rset = self.conn.execute_dict(SQL)
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=rset)
|
||||||
|
return make_json_response(
|
||||||
|
data=rset['rows'],
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
|
||||||
|
def module_js(self):
|
||||||
|
"""
|
||||||
|
This property defines whether javascript exists for this node.
|
||||||
|
"""
|
||||||
|
return make_response(
|
||||||
|
render_template(
|
||||||
|
"extensions/js/extensions.js",
|
||||||
|
_=gettext
|
||||||
|
),
|
||||||
|
200, {'Content-Type': 'application/x-javascript'}
|
||||||
|
)
|
||||||
|
|
||||||
|
@check_precondition
|
||||||
|
def sql(self, gid, sid, did, eid):
|
||||||
|
"""
|
||||||
|
This function will generate sql for the sql panel
|
||||||
|
"""
|
||||||
|
SQL = render_template("/".join(
|
||||||
|
[self.template_path, 'properties.sql']
|
||||||
|
), eid=eid)
|
||||||
|
status, res = self.conn.execute_dict(SQL)
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=res)
|
||||||
|
|
||||||
|
result = res['rows'][0]
|
||||||
|
|
||||||
|
SQL = render_template("/".join(
|
||||||
|
[self.template_path, 'create.sql']
|
||||||
|
),
|
||||||
|
data=result,
|
||||||
|
conn=self.conn,
|
||||||
|
display_comments=True
|
||||||
|
)
|
||||||
|
|
||||||
|
return ajax_response(response=SQL)
|
||||||
|
|
||||||
|
@check_precondition
|
||||||
|
def dependents(self, gid, sid, did, eid):
|
||||||
|
"""
|
||||||
|
This function gets the dependents and returns an ajax response
|
||||||
|
for the extension node.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
gid: Server Group ID
|
||||||
|
sid: Server ID
|
||||||
|
did: Database ID
|
||||||
|
eid: Extension ID
|
||||||
|
"""
|
||||||
|
dependents_result = self.get_dependents(self.conn, eid)
|
||||||
|
return ajax_response(
|
||||||
|
response=dependents_result,
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
|
||||||
|
@check_precondition
|
||||||
|
def dependencies(self, gid, sid, did, eid):
|
||||||
|
"""
|
||||||
|
This function gets the dependencies and returns an ajax response
|
||||||
|
for the extension node.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
gid: Server Group ID
|
||||||
|
sid: Server ID
|
||||||
|
did: Database ID
|
||||||
|
lid: Extension ID
|
||||||
|
"""
|
||||||
|
dependencies_result = self.get_dependencies(self.conn, eid)
|
||||||
|
return ajax_response(
|
||||||
|
response=dependencies_result,
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
|
||||||
|
# Register and add ExtensionView as blueprint
|
||||||
|
ExtensionView.register_node_view(blueprint)
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1017 B |
Binary file not shown.
|
After Width: | Height: | Size: 996 B |
@@ -0,0 +1,254 @@
|
|||||||
|
define(
|
||||||
|
['jquery', 'underscore', 'underscore.string', 'pgadmin',
|
||||||
|
'pgadmin.browser', 'pgadmin.browser.collection'],
|
||||||
|
function($, _, S, pgAdmin, pgBrowser) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create and Add an Extension Collection into nodes
|
||||||
|
* Params:
|
||||||
|
* label - Label for Node
|
||||||
|
* type - Type of Node
|
||||||
|
* columns - List of columns to show under under properties.
|
||||||
|
*/
|
||||||
|
if (!pgBrowser.Nodes['coll-extension']) {
|
||||||
|
var extensions = pgAdmin.Browser.Nodes['coll-extension'] =
|
||||||
|
pgAdmin.Browser.Collection.extend({
|
||||||
|
node: 'extension',
|
||||||
|
label: '{{ _('Extension') }}',
|
||||||
|
type: 'coll-extension',
|
||||||
|
columns: ['name', 'owner', 'comment']
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create and Add an Extension Node into nodes
|
||||||
|
* Params:
|
||||||
|
* parent_type - Name of parent Node
|
||||||
|
* type - Type of Node
|
||||||
|
* hasSQL - True if we need to show SQL query Tab control, otherwise False
|
||||||
|
* canDrop - True to show "Drop Extension" link under Context menu,
|
||||||
|
* otherwise False
|
||||||
|
* canDropCascade - True to show "Drop Cascade" link under Context menu,
|
||||||
|
* otherwise False
|
||||||
|
* columns - List of columns to show under under properties tab.
|
||||||
|
* label - Label for Node
|
||||||
|
*/
|
||||||
|
if (!pgBrowser.Nodes['extension']) {
|
||||||
|
pgAdmin.Browser.Nodes['extension'] =
|
||||||
|
pgAdmin.Browser.Node.extend({
|
||||||
|
parent_type: 'database',
|
||||||
|
type: 'extension',
|
||||||
|
hasSQL: true,
|
||||||
|
hasDepends: true,
|
||||||
|
canDrop: true,
|
||||||
|
canDropCascade: true,
|
||||||
|
label: '{{ _('Extension') }}',
|
||||||
|
|
||||||
|
Init: function() {
|
||||||
|
if(this.initialized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.initialized = true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add "create extension" menu item into context and object menu
|
||||||
|
* for the following nodes:
|
||||||
|
* coll-extension, extension and database.
|
||||||
|
*/
|
||||||
|
pgBrowser.add_menus([{
|
||||||
|
name: 'create_extension_on_coll', node: 'coll-extension', module: this,
|
||||||
|
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||||
|
category: 'create', priority: 4, label: '{{ _('Extension...') }}',
|
||||||
|
icon: 'wcTabIcon icon-extension', data: {action: 'create'}
|
||||||
|
},{
|
||||||
|
name: 'create_extension', node: 'extension', module: this,
|
||||||
|
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||||
|
category: 'create', priority: 4, label: '{{ _('Extension...') }}',
|
||||||
|
icon: 'wcTabIcon icon-extension', data: {action: 'create'}
|
||||||
|
},{
|
||||||
|
name: 'create_extension', node: 'database', module: this,
|
||||||
|
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||||
|
category: 'create', priority: 4, label: '{{ _('Extension...') }}',
|
||||||
|
icon: 'wcTabIcon icon-extension', data: {action: 'create'}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Define model for the Node and specify the properties
|
||||||
|
* of the model in schema.
|
||||||
|
*/
|
||||||
|
model: pgAdmin.Browser.Node.Model.extend({
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
id: 'name', label: '{{ _('Name')}}', first_empty: true,
|
||||||
|
type: 'text', mode: ['properties', 'create', 'edit'],
|
||||||
|
visible: true, url:'avails', disabled: function(m) {
|
||||||
|
return !m.isNew();
|
||||||
|
},
|
||||||
|
transform: function(data) {
|
||||||
|
var res = [];
|
||||||
|
var label = this.model.get('name');
|
||||||
|
if (!this.model.isNew()) {
|
||||||
|
res.push({label: label, value: label});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (data && _.isArray(data)) {
|
||||||
|
_.each(data, function(d) {
|
||||||
|
if (d.installed_version === null)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* d contains json data and sets into
|
||||||
|
* select's option control
|
||||||
|
*
|
||||||
|
* We need to stringify data because formatter will
|
||||||
|
* convert Array Object as [Object] string
|
||||||
|
*/
|
||||||
|
res.push({label: d.name, value: JSON.stringify(d)});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* extends NodeAjaxOptionsControl to override the properties
|
||||||
|
* getValueFromDOM which takes stringified data from option of
|
||||||
|
* select control and parse it. And `onChange` takes the stringified
|
||||||
|
* data from select's option, thus convert it to json format and set the
|
||||||
|
* data into Model which is used to enable/disable the schema field.
|
||||||
|
*/
|
||||||
|
control: Backform.NodeAjaxOptionsControl.extend({
|
||||||
|
getValueFromDOM: function() {
|
||||||
|
var data = this.formatter.toRaw(
|
||||||
|
_.unescape(this.$el.find("select").val()), this.model);
|
||||||
|
/*
|
||||||
|
* return null if data is empty to prevent it from
|
||||||
|
* throwing parsing error. Adds check as name can be empty
|
||||||
|
*/
|
||||||
|
if (data === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else if (typeof(data) === 'string') {
|
||||||
|
data=JSON.parse(data);
|
||||||
|
}
|
||||||
|
return data.name;
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When name is changed, extract value from its select option and
|
||||||
|
* set attributes values into the model
|
||||||
|
*/
|
||||||
|
onChange: function() {
|
||||||
|
Backform.NodeAjaxOptionsControl.prototype.onChange.apply(this, arguments);
|
||||||
|
var selectedValue = this.$el.find("select").val();
|
||||||
|
if (selectedValue.trim() != "") {
|
||||||
|
var d = this.formatter.toRaw(selectedValue, this.model);
|
||||||
|
if(typeof(d) === 'string')
|
||||||
|
d=JSON.parse(d);
|
||||||
|
var changes = {
|
||||||
|
'version' : '',
|
||||||
|
'relocatable': (
|
||||||
|
(!_.isNull(d.relocatable[0]) && !_.isUndefined(d.relocatable[0])) ?
|
||||||
|
d.relocatable[0]: ''),
|
||||||
|
'schema': ((!_.isNull(d.schema[0]) &&
|
||||||
|
!_.isUndefined(d.schema[0])) ? d.schema[0]: '')
|
||||||
|
};
|
||||||
|
this.model.set(changes);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var changes = {'version': '', 'relocatable': true, 'schema': ''};
|
||||||
|
this.model.set(changes);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'eid', label: '{{ _('Oid')}}', cell: 'string',
|
||||||
|
type: 'text', disabled: true, mode: ['properties', 'edit', 'create']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'owner', label:'{{ _('Owner') }}', control: 'node-list-by-name',
|
||||||
|
mode: ['properties'], node: 'role', cell: 'string'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'schema', label: '{{ _('Schema')}}', type: 'text', control: 'node-ajax-options',
|
||||||
|
mode: ['properties', 'create', 'edit'], group: 'Definition', deps: ['relocatable'],
|
||||||
|
url: 'schemas', first_empty: true, disabled: function(m) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* enable or disable schema field if model's relocatable
|
||||||
|
* attribute is True or False
|
||||||
|
*/
|
||||||
|
return (m.has('relocatable') ? !m.get('relocatable') : false);
|
||||||
|
},
|
||||||
|
transform: function(data) {
|
||||||
|
var res = [];
|
||||||
|
if (data && _.isArray(data)) {
|
||||||
|
_.each(data, function(d) {
|
||||||
|
res.push({label: d.schema, value: d.schema});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'relocatable', label: '{{ _('Relocatable?')}}', cell: 'switch',
|
||||||
|
type: 'switch', mode: ['properties'], 'options': {
|
||||||
|
'onText': 'Yes', 'offText': 'No', 'onColor': 'success',
|
||||||
|
'offColor': 'default', 'size': 'small'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'version', label: '{{ _('Version')}}', cell: 'string',
|
||||||
|
mode: ['properties', 'create', 'edit'], group: 'Definition',
|
||||||
|
control: 'node-ajax-options', url:'avails', first_empty: true,
|
||||||
|
|
||||||
|
// Transform the data into version for the selected extension.
|
||||||
|
transform: function(data) {
|
||||||
|
res = [];
|
||||||
|
var extension = this.model.get('name');
|
||||||
|
_.each(data, function(dt) {
|
||||||
|
if(dt.name == extension) {
|
||||||
|
if(dt.version && _.isArray(dt.version)) {
|
||||||
|
_.each(dt.version, function(v) {
|
||||||
|
res.push({ label: v, value: v });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'comment', label: '{{ _('Comment')}}', cell: 'string',
|
||||||
|
type: 'multiline', disabled: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
validate: function() {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Triggers error messages for name
|
||||||
|
* if it is empty/undefined/null
|
||||||
|
*/
|
||||||
|
var err = {},
|
||||||
|
errmsg,
|
||||||
|
name = this.get('name');
|
||||||
|
if (_.isUndefined(name) || _.isNull(name) ||
|
||||||
|
String(name).replace(/^\s+|\s+$/g, '') == '') {
|
||||||
|
err['name'] = '{{ _('Name can not be empty!') }}';
|
||||||
|
errmsg = errmsg || err['name'];
|
||||||
|
this.errorModel.set('name', errmsg);
|
||||||
|
return errmsg;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.errorModel.unset('name');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
return pgBrowser.Nodes['coll-extension'];
|
||||||
|
});
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{#=========================Create new extension======================#}
|
||||||
|
{#===Generates comments and code for SQL tab===#}
|
||||||
|
{% if display_comments %}
|
||||||
|
-- Extension: {{ conn|qtIdent(data.name) }}
|
||||||
|
|
||||||
|
-- DROP EXTENSION {{ conn|qtIdent(data.name) }};
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
{% if data.name %}
|
||||||
|
CREATE EXTENSION {{ conn|qtIdent(data.name) }}{% if data.schema == '' and data.version == '' %};{% endif %}
|
||||||
|
{% if data.schema %}
|
||||||
|
|
||||||
|
SCHEMA {{ conn|qtIdent(data.schema) }}{% if data.version == '' %};{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if data.version %}
|
||||||
|
|
||||||
|
VERSION {{ conn|qtIdent(data.version) }};
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{#============================Drop/Cascade Extension by name=========================#}
|
||||||
|
{% if eid %}
|
||||||
|
SELECT x.extname from pg_extension x
|
||||||
|
WHERE x.oid = {{ eid }}::int
|
||||||
|
{% endif %}
|
||||||
|
{% if name %}
|
||||||
|
DROP EXTENSION {{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %}
|
||||||
|
{% endif %}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{# ======================Fetch extensions names=====================#}
|
||||||
|
SELECT
|
||||||
|
a.name, a.installed_version,
|
||||||
|
array_agg(av.version) as version,
|
||||||
|
array_agg(av.schema) as schema,
|
||||||
|
array_agg(av.superuser) as superuser,
|
||||||
|
array_agg(av.relocatable) as relocatable
|
||||||
|
FROM
|
||||||
|
pg_available_extensions a
|
||||||
|
LEFT JOIN pg_available_extension_versions av ON (a.name = av.name)
|
||||||
|
GROUP BY a.name, a.installed_version
|
||||||
|
ORDER BY a.name
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{#===================Fetch properties of each extension by name or oid===================#}
|
||||||
|
SELECT
|
||||||
|
x.oid AS eid, pg_get_userbyid(extowner) AS owner,
|
||||||
|
x.extname AS name, n.nspname AS schema,
|
||||||
|
x.extrelocatable AS relocatable, x.extversion AS version,
|
||||||
|
e.comment
|
||||||
|
FROM
|
||||||
|
pg_extension x
|
||||||
|
LEFT JOIN pg_namespace n ON x.extnamespace=n.oid
|
||||||
|
JOIN pg_available_extensions() e(name, default_version, comment) ON x.extname=e.name
|
||||||
|
{%- if eid %}
|
||||||
|
WHERE x.oid = {{eid}}::int
|
||||||
|
{% elif ename %}
|
||||||
|
WHERE x.extname = {{ename|qtLiteral}}::text
|
||||||
|
{% else %}
|
||||||
|
ORDER BY x.extname
|
||||||
|
{% endif %}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{#===================fetch all schemas==========================#}
|
||||||
|
SELECT nspname As schema FROM pg_namespace
|
||||||
|
ORDER BY nspname
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{# =============Update extension schema============= #}
|
||||||
|
{% if data.schema and data.schema != o_data.schema %}
|
||||||
|
ALTER EXTENSION {{ conn|qtIdent(o_data.name) }}
|
||||||
|
SET SCHEMA {{ conn|qtIdent(data.schema) }};
|
||||||
|
{% endif %}
|
||||||
|
{# =============Update extension version============= #}
|
||||||
|
{% if data.version and data.version != o_data.version %}
|
||||||
|
ALTER EXTENSION {{ conn|qtIdent(o_data.name) }}
|
||||||
|
UPDATE TO {{ conn|qtIdent(data.version) }};
|
||||||
|
{% endif %}
|
||||||
Reference in New Issue
Block a user