public inbox for [email protected]
help / color / mirror / Atom feedFrom: Surinder Kumar <[email protected]>
To: Dave Page <[email protected]>
Cc: pgadmin-hackers <[email protected]>
Subject: Re: [pgAdmin4] [Patch]: Grant Wizard
Date: Tue, 8 Mar 2016 10:44:58 +0530
Message-ID: <CAM5-9D_D3TGq9-DbcR+07=p2cgT=yEtmEJnHU9rtmov_nby9+g@mail.gmail.com> (raw)
In-Reply-To: <CA+OCxoyGKAorswp0fwUojT4gB0Ex+dcRxzKbLkfG48qeCmSCrQ@mail.gmail.com>
References: <CAM5-9D8oYyEaL89coD9FozPDziGhYutYAat9dMP9dAwhOp5OKQ@mail.gmail.com>
<CA+OCxozy4aH9=2T0Sp_WworU_3UQaaHBcQXTDhEUKHAkZJT8ow@mail.gmail.com>
<CA+OCxoyGKAorswp0fwUojT4gB0Ex+dcRxzKbLkfG48qeCmSCrQ@mail.gmail.com>
List-Unsubscribe: <mailto:[email protected]?body=unsub%20pgadmin-hackers>
Hi
PFA patch with following changes:
1. Extends SqlTabControl to write a new function 'onWizardChange',
instead of writing it in backform.pgadmin.js file.
Also, validations in privilegeControl are not working properly, when
validations gets fixed, I will send an updated patch.
This is complete patch, as Khusboo's patch also merged in attached patch and
patches of "Sequence" and "Functions" macros are already committed.
On Fri, Mar 4, 2016 at 8:06 PM, Dave Page <[email protected]> wrote:
> A couple of corrections below <sigh>
>
> On Fri, Mar 4, 2016 at 1:39 PM, Dave Page <[email protected]> wrote:
> > Hi
> >
> > On Thu, Mar 3, 2016 at 12:49 PM, Surinder Kumar
> > <[email protected]> wrote:
> >> Hi,
> >>
> >> PFA patch for Grant Wizard.
> >>
> >> Before applying grant wizard patch:
> >>
> >> 1. Apply patch for "wizard JS file" which Khushboo had shared
> with
> >> Ashesh personally.
> >> I am using that patch with few changes in that. Ashesh will
> >> review
> >> and commit that patch.
> >>
> >> 2. Apply patches of "Sequence" and "Functions" macros.
> >>
> >>
> >> Please review the patch and Let me know for any comments.
> >
> > Initial feedback:
> >
> > - Why does this add specific knowledge of the Grant Wizard to the
> > Browser module? It should be a plugin that loads itself at runtime.
> > Any changes to the browser to support that should be entirely generic
> > and usable by any module.
>
Fixed.
> >
> > - The comment above also applies to the core templates. CSS should be
> > advertised by the plugin, and the browser can add it into the rendered
> > output when the module is dynamically loaded.
>
Fixed.
> >
> > - +""" Implements Grant Wizard""" - why the leading space? Please
> > review all comments and code for such small details.
>
Done
> >
> > - The Python code to detect and alias various Python 2/3 classes
> > should be centralised, as I've seen it in at least one other module.
> >
>
Removed it, as far as it is not required.
> > - In other module names, we've separated multiple words with a hypen.
> > e.g. server-groups. s/grantwizard/grant-wizard/g
>
> That should be an underscore, not a hyphen:
>
> s/grantwizard/grant_wizard/g
> >
> > - The published routes for this module are all under
> >
> > - "GET /browser/static/js/wizard.js HTTP/1.1" 404 -
> >
> > - For consistency, when naming CSS/JS files that are core to a module,
> > please use the module name, e.g.
> > /web/pgadmin/tools/grant-wizard/static/css/grant-wizard.css
>
> /web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css
>
Done
>
> --
> Dave Page
> Blog: http://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EnterpriseDB UK: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>
--
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers
Attachments:
[application/octet-stream] grant_wizard_v2.patch (61.5K, 3-grant_wizard_v2.patch)
download | inline diff:
diff --git a/web/pgadmin/browser/static/css/wizard.css b/web/pgadmin/browser/static/css/wizard.css
new file mode 100644
index 0000000..9934aac
--- /dev/null
+++ b/web/pgadmin/browser/static/css/wizard.css
@@ -0,0 +1,89 @@
+
+/** CSS for Wizard **/
+.wizard_dlg {
+ height: 300px;
+ padding: 0 10px;
+}
+
+.ajs-content {
+ padding-top: 0px !important;
+}
+
+/* Wizard Header CSS */
+.wizard-header {
+ background: #428bca;
+ padding-left: 15px;
+ padding-bottom: 7px;
+ color: #fff;
+ font-size: 18px;
+ height: 76px;
+ line-height: 76px;
+}
+
+.wizard-header h3 {
+ font-size: 18px;
+ display: inline-block;
+}
+
+.wizard-content {
+ position: relative;
+ overflow-y: auto;
+ float: left;
+ padding: 0;
+ min-height: 390px;
+ max-height: 390px;
+}
+
+/* Wizard Footer CSS */
+.footer {
+ position: absolute;
+ border-top: 1px solid #ccc;
+ bottom: 0px;
+ height: 71px;
+ right: 0px;
+ padding-top: 22px;
+}
+
+/* Wizard Button CSS */
+.wizard-buttons {
+ float: right;
+}
+
+.wizard-buttons button {
+ float: left;
+ padding: 7px 15.2px;
+ font-size: 14px;
+ margin-right: 5px;
+}
+
+.wizard-finish {
+ margin-right: 0 !important;
+}
+
+/* Wizard Status bar CSS */
+.wizard-description {
+ height: auto;
+ padding: 20px 0px;
+}
+
+/* Error message css */
+.error_msg_div {
+ display: block;
+ bottom: 20px;
+}
+
+.error_msg_div p {
+ background: #fff;
+ color: #b92c28;
+}
+
+/* In wizard select2 dropdown doesn't
+ * popup because z-index of alertify
+ * wizard is greater than the z-index
+ * of select2 dropdown. To make select2
+ * visible, set z-index of select2
+ * higher value than wizard's
+ */
+.select2-container--open {
+ z-index: 10000;
+}
diff --git a/web/pgadmin/browser/static/js/wizard.js b/web/pgadmin/browser/static/js/wizard.js
new file mode 100644
index 0000000..78458e4
--- /dev/null
+++ b/web/pgadmin/browser/static/js/wizard.js
@@ -0,0 +1,222 @@
+define(
+ ['underscore', 'backbone', 'pgadmin', 'pgadmin.browser'],
+function(_, Backbone, pgAdmin, pgBrowser) {
+
+ pgBrowser = pgBrowser || pgAdmin.Browser || {};
+
+ /* Wizard individual Page Model */
+ var WizardPage = pgBrowser.WizardPage = Backbone.Model.extend({
+ defaults: {
+ id: undefined, /* Id */
+ page_title: undefined, /* Page Title */
+ view: undefined, /* A Backbone View */
+ html: undefined, /* HTML tags to be rendered */
+ image: undefined, /* Left hand side image */
+ disable_prev: false, /* Previous Button Flag */
+ disable_next: false, /* Next Button Flag */
+ disable_cancel: false, /* Cancel Button Flag */
+ show_progress_bar: '',
+ /* Callback for OnLoad */
+ onLoad: function() {
+ return true;
+ },
+ /* Callback for before Next */
+ beforeNext: function() {
+ return true;
+ },
+ onNext: function(){},
+ onBefore: function() {},
+ /* Callback for before Previous */
+ beforePrev: function() {
+ return true;
+ }
+ }
+ });
+
+ var Wizard = pgBrowser.Wizard = Backbone.View.extend({
+ options: {
+ title: 'Wizard', /* Main Wizard Title */
+ image: 'left_panel.png', /* TODO:: We can use default image here */
+ curr_page: 0, /* Current Page to Load */
+ disable_next: false,
+ disable_prev: false,
+ disable_finish: false,
+ disable_cancel: false,
+ height: 400,
+ width: 650,
+ show_left_panel: true
+ },
+ tmpl: _.template(
+ " <div class='pgadmin-wizard' style='height: <%= this.options.height %>px; width: <%= this.options.width %>px'>"
+ + " <div class='wizard-header wizard-badge'>"
+ + " <div class='row'>"
+ + " <div class='col-sm-9'>"
+ + " <h3><span id='main-title'><%= this.options.title %></span> - <span id='step-title'><%= page_title %></span></h3>"
+ + " </div>"
+ + " </div>"
+ + " </div>"
+ + " <div class='wizard-content col-sm-12'>"
+ + " <% if(this.options.show_left_panel) { %>"
+ + " <div class='col-sm-3 wizard-left-panel'>"
+ + " <img src='<%= this.options.image %>'></div>"
+ + " <% } %>"
+ + " <div class='col-sm-<% if(this.options.show_left_panel){ %>6<% } else { %>12<% } %> wizard-right-panel'>"
+ + " <% if( typeof show_description != 'undefined'){ %>"
+ + " <div class='wizard-description'>"
+ + " <%= show_description %>"
+ + " </div>"
+ + " <% } %>"
+ + " <div class='wizard-right-panel_content'>"
+ + " </div>"
+ + " </div>"
+ + " </div>"
+ + " <div class='col-sm-12 error_msg_div'>"
+ + " <p></p>"
+ + " </div>"
+ + " <div class='footer col-sm-12'>"
+ + " <div class='row'>"
+ + " <div class='col-sm-4 wizard-progress-bar'>"
+ + " <p><% if(show_progress_bar){ %><%= show_progress_bar %><% } %></p>"
+ + " </div>"
+ + " <div class='col-sm-8'>"
+ + " <div class='wizard-buttons'>"
+ + " <button class='btn-sm btn-primary wizard-back' <%=this.options.disable_prev ? 'disabled' : ''%>><i class='fa fa-backward'></i> Back</button>"
+ + " <button class='btn-sm btn-primary wizard-next' <%=this.options.disable_next ? 'disabled' : ''%>>Next <i class='fa fa-forward'></i></button>"
+ + " <button class='btn-sm btn-danger wizard-cancel' <%=this.options.disable_cancel ? 'disabled' : ''%>><i class='fa fa-lg fa-close'></i>Cancel</button>"
+ + " <button class='btn-sm btn-primary wizard-finish' <%=this.options.disable_finish ? 'disabled' : ''%>>Finish</button>"
+ + " </div>"
+ + " </div>"
+ + " </div>"
+ + " </div>"
+ + " </div>"),
+ events: {
+ "click button.wizard-next" : "nextPage",
+ "click button.wizard-back" : "prevPage",
+ "click button.wizard-cancel" : "onCancel",
+ "click button.wizard-finish" : "finishWizard",
+ },
+ initialize: function(options) {
+ this.options = _.extend({}, this.options, options.options);
+ this.currPage = this.collection.at(this.options.curr_page).toJSON();
+ },
+ render: function() {
+ var data = this.currPage;
+
+ /* Check Status of the buttons */
+ this.options.disable_next = (this.options.disable_next ? true : this.evalASFunc(this.currPage.disable_next));
+ this.options.disable_prev = (this.options.disable_prev ? true : this.evalASFunc(this.currPage.disable_prev));
+ this.options.disable_cancel = (this.currPage.canCancel ? true : this.evalASFunc(this.currPage.disable_cancel));
+
+ that = this;
+
+ /* HTML Content */
+ if (data.html) { data.content = data.html; }
+ /* Backbone View */
+ else if (data.view) { data.content = data.view.render().el;}
+
+ $(this.el).html(this.tmpl(data));
+ $(this.el).find(".wizard-right-panel_content").html(data.content);
+
+ /* OnLoad Callback */
+ this.onLoad();
+
+ return this;
+ },
+ nextPage: function() {
+ this.options.curr_page.el = this.$el;
+ if (!this.beforeNext()) { return false; }
+
+ page_id = this.onNext();
+
+ if (page_id ) {
+ this.currPage = this.collection.get(page_id).toJSON();
+ this.options.curr_page = this.collection.indexOf(this.collection.get(page_id));
+ }
+ else if (this.options.curr_page < (this.collection.length-1)) {
+ this.options.curr_page = this.options.curr_page + 1;
+ this.currPage = this.collection.at(this.options.curr_page).toJSON();
+ }
+
+ this.enableDisableNext();
+ this.enableDisablePrev();
+
+ return this.render();
+ },
+ prevPage: function() {
+ if (!this.beforePrev()) { return false; }
+
+ page_id = this.onPrev();
+
+ if (page_id){
+ this.currPage = this.collection.get(page_id).toJSON();
+ this.options.curr_page = this.collection.indexOf(this.collection.get(page_id));
+ }
+ else if (this.options.curr_page > 0) {
+ this.options.curr_page = this.options.curr_page - 1;
+ this.currPage = this.collection.at(this.options.curr_page).toJSON();
+ }
+
+ this.enableDisableNext();
+ this.enableDisablePrev();
+
+ return this.render();
+ },
+ finishWizard: function() {
+ this.onFinish();
+ return true;
+ },
+ enableDisableNext: function(disable) {
+ if (typeof(disable) != 'undefined') {
+ this.options.disable_next = disable;
+ }
+ else if (this.options.curr_page >= (this.collection.length-1)) {
+ this.options.disable_next = true;
+ }
+ else {
+ this.options.disable_next = false;
+ }
+ },
+ enableDisablePrev: function(disable) {
+ if (typeof(disable) != 'undefined') {
+ this.options.disable_prev = disable;
+ }
+ else if (this.options.curr_page <= 0) {
+ this.options.disable_prev = true;
+ }
+ else {
+ this.options.disable_prev = false;
+ }
+ },
+ beforeNext: function(){
+ return this.evalASFunc(this.currPage.beforeNext);
+ },
+ beforePrev: function(){
+ return this.evalASFunc(this.currPage.beforePrev);
+ },
+ onPrev: function(){
+ return this.evalASFunc(this.currPage.onPrev);
+ },
+ onNext: function(){
+ return this.evalASFunc(this.currPage.onNext);
+ },
+ onLoad: function() {
+ return this.evalASFunc(this.currPage.onLoad);
+ },
+ onFinish: function() {
+ return true;
+ },
+ onCancel: function() {
+ this.$el.remove();
+ return true;
+ },
+ evalASFunc: function(func, ctx) {
+ var self = this;
+ ctx = ctx || self.currPage;
+
+ return (_.isFunction(func) ? func.apply(ctx, [self]) : func);
+ }
+ });
+
+ return pgBrowser;
+
+});
diff --git a/web/pgadmin/tools/grant_wizard/__init__.py b/web/pgadmin/tools/grant_wizard/__init__.py
new file mode 100644
index 0000000..24c097a
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/__init__.py
@@ -0,0 +1,291 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Implements Grant Wizard"""
+
+import json
+from flask import render_template, request, current_app
+from flask.ext.babel import gettext
+from pgadmin.utils.ajax import make_response as ajax_response, \
+ make_json_response, internal_server_error
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+from pgadmin.browser.server_groups.servers.utils import parse_priv_to_db
+from pgadmin.utils import PgAdminModule
+from flask import Response, url_for
+from flask.ext.security import login_required
+from urllib import unquote
+
+# set template path for sql scripts
+template_path = 'grant_wizard/sql'
+MODULE_NAME = 'grant_wizard'
+
+
+class GrantWizardModule(PgAdminModule):
+ """
+ class GrantWizardModule(Object):
+
+ It is a wizard which inherits PgAdminModule
+ class and define methods to load its own
+ javascript file.
+ """
+ def get_own_stylesheets(self):
+ """
+ Returns:
+ list: the stylesheets used by this module.
+ """
+ stylesheets = []
+ for (endpoint, filename) in [
+ ('browser.static', 'css/wizard.css'),
+ ('grant_wizard.static', 'css/grant_wizard.css'),
+ ]:
+ stylesheets.append(url_for(endpoint, filename=filename))
+ return stylesheets
+
+ def get_own_javascripts(self):
+ """"
+ Returns:
+ list: js files used by this module
+ """
+ scripts = []
+ scripts.append({
+ 'name': 'pgadmin.tools.grant_wizard',
+ 'path': url_for('grant_wizard.index') + 'grant_wizard',
+ 'when': None
+ })
+ scripts.append({
+ 'name': 'pgadmin.browser.wizard',
+ 'path': url_for('browser.static', filename='js/wizard'),
+ 'when': None
+ })
+ return scripts
+
+# Create blueprint for GrantWizardModule class
+blueprint = GrantWizardModule(
+ MODULE_NAME, __name__, static_url_path='',
+ url_prefix='/wizard')
+
+
[email protected]("/")
+@login_required
+def index():
+ pass
+
+
[email protected]("/grant_wizard.js")
+@login_required
+def script():
+ """render own javascript"""
+ return Response(response=render_template(
+ "grant_wizard/js/grant_wizard.js", _=gettext),
+ status=200,
+ mimetype="application/javascript")
+
+
[email protected](
+ '/grant_wizard/properties/<int:gid>/<int:sid>/<int:did>'
+ '/<int:node_id>/<node_name>/<node_type>/<nspname>/',
+ methods=('GET', 'POST'))
+@login_required
+def properties(gid, sid, did, node_id, node_name, node_type, nspname):
+ """It fetches the properties of object types
+ and render into selection page of wizard
+ """
+
+ # unquote encoded url parameter
+ node_type = unquote(node_type)
+ nspname = unquote(nspname)
+
+ res_data = []
+ manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+ conn = manager.connection(did=did)
+
+ # Fetch functions against schema
+ if node_type in ['schema']:
+ SQL = render_template("/".join(
+ [template_path, 'properties.sql']),
+ node_id=node_id, node_name=node_name,
+ type=node_type, nspname=nspname)
+
+ status, res = conn.execute_dict(SQL)
+
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ res_data.extend(res['rows'])
+
+ # Fetch trigger functions
+ if node_type in ['schema', 'trigger_function']:
+ SQL = render_template("/".join(
+ [template_path, 'properties.sql']),
+ node_id=node_id, node_name=node_name,
+ type='trigger_function', nspname=nspname)
+ status, res = conn.execute_dict(SQL)
+
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ res_data.extend(res['rows'])
+
+ # Fetch Sequences against schema
+ if node_type in ['schema', 'sequence']:
+ SQL = render_template("/".join(
+ [template_path, 'properties.sql']),
+ node_id=node_id, node_name=node_name,
+ type='sequence', nspname=nspname)
+
+ status, res = conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+ res_data.extend(res['rows'])
+
+ # Fetch Tables against schema
+ if node_type in ['schema', 'table']:
+ SQL = render_template("/".join(
+ [template_path, 'properties.sql']),
+ node_id=node_id, node_name=node_name,
+ type='table', nspname=nspname)
+
+ status, res = conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ res_data.extend(res['rows'])
+
+ # Fetch Views against schema
+ if node_type in ['schema', 'coll-view']:
+ SQL = render_template("/".join(
+ [template_path, 'properties.sql']),
+ node_id=node_id, node_name=node_name,
+ type='coll-view', nspname=nspname)
+
+ status, res = conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ res_data.extend(res['rows'])
+
+ return ajax_response(
+ response=res_data,
+ status=200
+ )
+
+
[email protected](
+ '/grant_wizard/msql/<int:gid>/<int:sid>/<int:did>/<node_type>/<nspname>/',
+ methods=('GET', 'POST'))
+def msql(gid, sid, did, node_type, nspname):
+ """
+ This function will return modified SQL
+ """
+
+ # unquote encoded url parameter
+ node_type = unquote(node_type)
+ nspname = unquote(nspname)
+
+ data = {}
+ for k, v in request.args.items():
+ try:
+ data[k] = json.loads(v)
+ except ValueError:
+ data[k] = v
+
+ # Form db connection
+ manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+ conn = manager.connection(did=did)
+
+ acls = []
+ try:
+ acls = render_template(
+ "/".join([template_path, 'allowed_acl.json'])
+ )
+ acls = json.loads(acls)
+ except Exception as e:
+ current_app.logger.exception(e)
+
+ try:
+
+ # Parse privileges
+ if 'acl' in data:
+ data['acl'] = parse_priv_to_db(data['acl'], acls[node_type]['acl'])
+
+ # Pass database objects and get SQL for privileges
+ SQL = render_template("/".join(
+ [template_path, 'grant.sql']),
+ data=data, nspname=nspname, conn=conn)
+
+ res = {'data': SQL}
+
+ return ajax_response(
+ response=res,
+ status=200
+ )
+
+ except Exception as e:
+ return make_json_response(
+ status=410,
+ success=0,
+ errormsg=e.message
+ )
+
+
[email protected](
+ '/grant_wizard/save/<int:gid>/<int:sid>/<int:did>/'
+ '<node_type>/<nspname>/',
+ methods=('GET', 'POST'))
+def save(gid, sid, did, node_type, nspname):
+ """
+ This function will apply the privileges to the selected
+ Database Objects
+ """
+
+ # unquote encoded url parameter
+ node_type = unquote(node_type)
+ nspname = unquote(nspname)
+
+ data = request.form if request.form else json.loads(request.data.decode())
+
+ # Form db connection and we use conn to execute sql
+ manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+ conn = manager.connection(did=did)
+
+ acls = []
+ try:
+ acls = render_template(
+ "/".join([template_path, 'allowed_acl.json'])
+ )
+ acls = json.loads(acls)
+ except Exception as e:
+ current_app.logger.exception(e)
+
+ try:
+
+ # Parse privileges
+ if 'acl' in data:
+ data['acl'] = parse_priv_to_db(
+ data['acl'],
+ acls[node_type]['acl']
+ )
+
+ # Pass database objects and get SQL for privileges
+ SQL = render_template("/".join(
+ [template_path, 'grant.sql']),
+ data=data, nspname=nspname, conn=conn)
+
+ status, res = conn.execute_dict(SQL)
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ return make_json_response(
+ success=1,
+ info="Privileges Applied"
+ )
+
+ except Exception as e:
+ return internal_server_error(errormsg=e.message)
diff --git a/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css b/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css
new file mode 100644
index 0000000..4403c68
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css
@@ -0,0 +1,66 @@
+/** Grant Wizard CSS **/
+
+/**
+ CSS to make db object type table
+ fixed so that tbody content may
+ scroll
+ */
+.object_type_table thead tr {
+ position: relative;
+ display: block;
+}
+
+.object_type_table tbody {
+ display: block;
+ overflow-y: scroll;
+ height: 180px;
+}
+
+.object_type_table tbody tr td:nth-child(1),
+.object_type_table thead tr th:nth-child(1) {
+ width: 28px;
+ min-width: 28px;
+}
+
+.object_type_table tbody tr td:nth-child(2),
+.object_type_table thead tr th:nth-child(2) {
+ width: 185px;
+ min-width: 185px;
+}
+
+.object_type_table tbody tr td:nth-child(3),
+.object_type_table thead tr th:nth-child(3) {
+ width: 110px;
+ min-width: 110px;
+}
+
+.object_type_table tbody tr td:nth-child(4),
+.object_type_table thead tr th:nth-child(4) {
+ width: 244px;
+}
+
+/** Override Backgrid filter CSS **/
+.backgrid-filter.form-search {
+ float: left;
+ margin: 0px 5px 5px 0;
+}
+
+/** Custom styling for Codemirror field **/
+.wizard-right-panel_content {
+ border: 1px solide #ccc;
+}
+
+.wizard-right-panel_content .CodeMirror {
+ border: 1px solid #ccc;
+ height: 285px !important;
+ min-height: 285px !important;
+}
+
+.wizard-right-panel_content .CodeMirror-linenumber {
+ background: #F7F7F7;
+ border-right: 1px solid #DDDDDD;
+}
+
+.wizard-right-panel_content .CodeMirror-gutters {
+ min-height: 285px !important;
+}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
new file mode 100644
index 0000000..90287b2
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
@@ -0,0 +1,967 @@
+define([
+ 'jquery', 'underscore', 'underscore.string', 'alertify',
+ 'pgadmin.browser', 'backbone', 'backgrid', 'pgadmin.browser.node',
+ 'backgrid.select.all', 'backgrid.filter', 'backbone.paginator',
+ 'backgrid.paginator', 'pgadmin.browser.server.privilege',
+ 'pgadmin.browser.wizard',
+ ],
+
+function($, _, S, alertify, pgBrowser, Backbone, Backgrid, pgNode) {
+
+ if (pgBrowser.GrantWizard) {
+ return pgBrowser.GrantWizard;
+ }
+
+ /**
+ It is sub model for field "Objects". It has fields
+ for database object types such as Schemas, Views and
+ Sequence etc.
+ */
+ var DatabaseObjectModel = pgNode.Model.extend({
+ defaults: {
+ selected: false,
+ icon: 'icon-unknown',
+ name: undefined,
+ nspname: undefined,
+ proargs: undefined,
+ object_type: undefined,
+ object_id: undefined
+ },
+ idAttribute: 'object_id',
+ parse: function(res) {
+
+ // Create unique object id
+ res.object_id = res.object_type + res.nspname + '"' + res.name;
+
+ return res;
+ },
+
+ validate: function() {
+
+ /*
+ * Triggers error messages for object types "selected"
+ * if it is empty/undefined/null
+ */
+ var err = {},
+ errmsg,
+ node = this.get('objects').toJSON();
+ if (_.isEmpty(node)) {
+ err['selected'] = '{{ _("Please select any database object type") }}';
+ errmsg = errmsg || err['selected'];
+ this.errorModel.set('selected', errmsg);
+ return errmsg;
+ } else {
+ this.errorModel.unset('selected');
+ }
+ return null;
+ }
+ });
+
+ // Define privileges for various types of nodes
+ var privDict = {
+ 'schema': ['a', 'r', 'w', 'd', 'D', 'x', 't', 'U', 'X'],
+ 'coll-function': ['X'],
+ 'coll-sequence': ['a', 'w', 'U'],
+ 'coll-table':['a', 'w', 'U', 'd', 'D', 'x', 't'],
+ 'coll-view': ['a', 'r', 'w', 'd', 'D', 'x', 't'],
+ };
+
+ // Define columns for the Db Object Types Grid
+ var columns = [{
+ name: "selected",
+
+ /*
+ Override render method of Backgrid.Extension.SelectRowCell
+ class. It has an issue: It doesn't mark rows checked if we move to next
+ page and then go back to previous page. but it must show.
+ so we handle this case by overriding the render method.
+ */
+ cell: Backgrid.Extension.SelectRowCell.extend({
+ render: function() {
+
+ // Use the Backform Control's render function
+ Backgrid.Extension.SelectRowCell.prototype.render.apply(this, arguments);
+
+ var col = this.column.get('name');
+ if (this.model && this.model.has(col)) {
+ if (this.model.get(col)) {
+ this.checkbox().prop("checked", true);
+ this.$el.parent().toggleClass("selected", true);
+ this.model.trigger("backgrid:selected", this.model, true);
+ }
+ }
+ return this;
+ }
+ }),
+
+ headerCell: "select-all",
+
+ },{
+ name: "object_type",
+ label: "Object Type",
+ editable: false,
+ cell: Backgrid.Cell.extend({
+ render: function() {
+
+ // Override render to add icon to Db Object column
+ Backgrid.Cell.prototype.render.apply(this, arguments);
+ this.$el.addClass(this.model.get('icon')).css({"padding-left": "22px"});
+
+ return this;
+ }
+ })
+ },{
+ name: "nspname",
+ label: "Schema",
+ cell: "string",
+ editable: false
+ },{
+ name: "name",
+ label: "Name",
+ cell: "string",
+ editable: false
+ }];
+
+ // Create an Object GrantWizard of pgBrowser class
+ pgBrowser.GrantWizard = {
+ init: function() {
+ if (this.initialized) {
+ return;
+ }
+
+ this.initialized = true;
+
+ // Define list of nodes on which grant wizard option appears
+ var supported_nodes = [
+ 'schema', 'coll-function',
+ 'coll-sequcene', 'coll-table',
+ 'coll-view'
+ ],
+
+ /**
+ Enable/disable grantwizard menu in tools based
+ on node selected
+ if selected node is present in supported_nodes,
+ menu will be enabled otherwise disabled.
+ */
+ menu_enabled = function(obj) {
+ if(!_.isUndefined(obj) && !_.isNull(obj))
+ return (_.indexOf(supported_nodes, obj._type) !== -1 ? true: false);
+ else
+ return false;
+ };
+
+ // Define the nodes on which the menus to be appear
+ var menus = [{
+ name: 'grant_wizard_schema', module: this,
+ applies: ['tools'], callback: 'start_grant_wizard',
+ priority: 10, label: '{{_("Grant Wizard...") }}',
+ icon: 'wcTabIcon', enable: menu_enabled
+ }];
+
+ // Add supported menus into the menus list
+ for (var idx = 0; idx < supported_nodes.length; idx++) {
+ menus.push({
+ name: 'grant_wizard_schema_context_' + supported_nodes[idx],
+ node: supported_nodes[idx], module: this,
+ applies: ['context'], callback: 'start_grant_wizard',
+ priority: 10, label: '{{_("Grant Wizard...") }}',
+ icon: 'wcTabIcon', enable: menu_enabled
+ });
+ }
+ pgAdmin.Browser.add_menus(menus);
+
+ return this;
+ },
+
+ // Callback to draw Wizard Dialog
+ start_grant_wizard: function(action, item) {
+ if (!alertify.wizardDialog) {
+ alertify.dialog('wizardDialog', function factory() {
+ return {
+ main:function(title) {
+ this.set('title', title);
+ },
+ setup:function() {
+ return {
+ options: {
+ frameless: true,
+ resizable: false,
+ autoReset: false,
+ maximizable: false,
+ closableByDimmer: false
+ }
+ };
+ },
+ hooks:{
+ onshow: function() {
+
+ // Set dimensions for wizard
+ this.elements.dialog.style.width = '100%';
+ this.elements.dialog.style.height = '540px';
+ },
+ },
+ build:function() { },
+
+ /**
+ Returns a Collection used for rendering grid
+ and Pagination
+
+ @class {Backbone.PageableCollection}
+ @param {variable} baseUrl - from which data is fetched
+ @param {Backbone.Model} DatabaseObjectModel
+ @return {Object} coll
+ */
+ getPageableCollection: function(baseUrl){
+ var Coll = Backbone.PageableCollection.extend({
+ model: DatabaseObjectModel,
+ url: baseUrl,
+ mode: "client",
+ state: {
+ pageSize: 100,
+ sortKey: "object_type",
+ order: -1
+ },
+ queryParams: {
+ totalPages: null,
+ totalRecords: null,
+ sortKey: "sort"
+ }
+ });
+ return new Coll();
+ },
+
+ /**
+ Returns a Paginator Class Object which is again to be rendered
+
+ @class {Backgrid.Extension.Paginator}
+ @param {Backbone.Collection} coll - from which data is fetched
+ @return {Object} paginator
+ */
+ DbPaginator: function(coll){
+ var paginator = this.paginator = new Backgrid.Extension.Paginator({
+ collection: coll,
+ windowSize: 8
+ });
+ return paginator;
+ },
+
+ /**
+ Create new Filter which will filter the
+ rendered grid for Select Type Tabular Data
+ @param {Backbone.PageableCollection} coll
+ */
+ DbObjectFilter: function(coll){
+ var clientSideFilter = this.clientSideFilter = new Backgrid.Extension.ClientSideFilter({
+ collection: coll,
+ placeholder: _('Search by object type or name'),
+
+ // The model fields to search for matches
+ fields: ['object_type', 'name'],
+
+ // How long to wait after typing has stopped before searching can start
+ wait: 150
+ });
+ return clientSideFilter;
+ },
+
+ //Enable Disable Next button of Page 2
+ updateButtons: function(modified){
+ if(!modified)
+ $('.wizard-next').prop('disabled', true);
+ else
+ $('.wizard-next').prop('disabled', false);
+ },
+
+ /**
+ Callback called when an errorModel is set
+ with invalid value and errormsg is set into
+ status bar element and next button is disabled
+ */
+ onSessionInvalid: function(msg) {
+ $('.error_msg_div p').html(msg).removeClass("hide");
+
+ // Enable disable Next button
+ this.updateButtons(false);
+ return true;
+ },
+
+ /**
+ Callback called when anything is set into model
+ thus hide error msg element and enable next button
+ status bar element and next button is disabled
+ */
+ onSessionValidated: function(sessHasChanged) {
+ $('.error_msg_div p').empty().addClass("hide");
+
+ // Enable disable Next button
+ this.updateButtons(sessHasChanged);
+ },
+
+ /*
+ Remove/Delete objects, attributes
+ in wizard on wizard close or finish
+ to reclaim memory
+ */
+ releaseObjects: function(){
+ var self = this;
+
+ if(!_.isUndefined(self.dbObjectFilter)) {
+ self.dbObjectFilter.remove();
+ self.dbObjectFilter = undefined;
+ }
+
+ if(!_.isUndefined(self.clientSideFilter)) {
+ self.clientSideFilter.remove();
+ self.clientSideFilter = undefined;
+ }
+
+ if(!_.isUndefined(self.paginator)) {
+ self.paginator.remove();
+ self.paginator = undefined;
+ }
+
+ // Delete Wizard Pages
+ if(!_.isUndefined(self.dbObjectTypePage) &&
+ !_.isNull(self.dbObjectTypePage)) {
+ if(!_.isUndefined(self.dbObjectTypePage.get('model')) &&
+ !_.isNull(self.dbObjectTypePage.get('model'))) {
+ self.dbObjectTypePage.get('model').clear();
+ self.dbObjectTypePage = undefined;
+ }
+ }
+
+ if(!_.isUndefined(self.privilegePage) &&
+ !_.isNull(self.privilegePage)) {
+ if(!_.isUndefined(self.privilegePage.get('model')) &&
+ !_.isNull(self.privilegePage.get('model'))) {
+ self.privilegePage.get('model').clear();
+ self.privilegePage = undefined;
+ }
+ }
+
+ if(!_.isUndefined(self.reviewSQLPage) &&
+ !_.isNull(self.reviewSQLPage)) {
+ if(!_.isUndefined(self.reviewSQLPage.get('model')) &&
+ !_.isNull(self.reviewSQLPage.get('model'))) {
+ self.reviewSQLPage.get('model').clear();
+ self.reviewSQLPage = undefined;
+ }
+ }
+
+ // Remove collection containing db object data
+ if(!_.isNull(self.coll) &&
+ !_.isUndefined(self.coll)) {
+ self.coll.reset();
+ self.coll = undefined;
+ }
+
+ // Delete Wizard
+ if(!_.isNull(self.wizard) &&
+ !_.isUndefined(self.wizard)) {
+ self.wizard.collection.reset();
+ self.wizard.curr_page = undefined;
+ self.wizard.el = undefined;
+ self.wizard = undefined;
+ }
+
+ },
+
+ /**
+ Every time a wizard is opened, this function
+ is called everytime. It has Wizard Pages which
+ are rendered by the Wizard Class:
+
+ @class {pgBrowser.WizardPage} dbObjectType1 - This page
+ @extends {Backbone.Model}
+ renders a grid of Database Object Types such as
+ Schemas, Views and Sequences etc.
+
+ @class {pgBrowser.WizardPage} WizardPage2 - This page
+ @extends {Backbone.Model}
+ adds Privilege Control which provides grant privileges
+ such as "Create, Insert, Delete, Update" so on the
+ database objects selected on Wizard Pages.
+
+ @class {pgBrowser.WizardPage} WizardPage3 - This page
+ displays the generated GRANT SQL query for the Db
+ objects selected with the specific privileges added to it.
+ @extends {Backbone.Model}
+
+ @class {Backbone.Collection} WizardCollection - It is the
+ collection of wizard pages
+
+ @class {pgBrowser.Wizard} wizard - Its task is:
+ - Create a Wizard
+ - Add Buttons, Callbacks to it.
+ - Render WizardPages
+ @extends {Backbone.View}
+
+ */
+ prepare:function() {
+
+ // Remove extra container
+ if($('.ajs-content').size() > 1){
+ $('.ajs-content')[0].remove();
+ }
+
+ // Generate wizard main container
+ var wizard_html = '<div class="wizard_dlg">',
+ wizard_parent = $('.ajs-content').empty().append(wizard_html),
+ el = $(".ajs-content");
+
+ // Extract the data from the selected tree node
+ var t = pgBrowser.tree,
+ i = t.selected(),
+ d = i && i.length == 1 ? t.itemData(i) : undefined,
+ info = pgBrowser.Node.getTreeNodeHierarchy(i),
+ icon = d.icon;
+
+ /**
+ Generate a URL using:
+ gid, did, sid(server id), node_id(node id),
+ node_(node name), node_type(node type)
+ and pass it to collection which will fetch Object Type properties.
+ */
+ var gid = info['server-group']._id,
+ sid = info.server._id,
+ did = info.database._id,
+ node_id = d._id,
+ nspname = info.schema.label;
+ node_type = d._type,
+ node_label = d.label,
+
+ // Collection url to fetch database object types for objects field
+ baseUrl = "{{ url_for('grant_wizard.index') }}" + "grant_wizard/properties/" +
+ S('%s/%s/%s/%s/%s/%s/%s/').sprintf(
+ encodeURI(gid), encodeURI(sid), encodeURI(did),
+ encodeURI(node_id), encodeURI(node_label),
+ encodeURI(node_type), encodeURI(nspname)).value();
+
+ // Model's save url
+ saveUrl = "{{ url_for('grant_wizard.index') }}" + "grant_wizard/save/" +
+ S('%s/%s/%s/%s/%s/').sprintf(
+ encodeURI(gid), encodeURI(sid), encodeURI(did),
+ encodeURI(node_type), encodeURI(nspname)).value(),
+
+ // generate encoded url based on wizard type
+ msql_url = this.msql_url = "/wizard/grant_wizard/msql/"+
+ S('%s/%s/%s/%s/%s/').sprintf(
+ encodeURI(gid), encodeURI(sid), encodeURI(did),
+ encodeURI(node_type), encodeURI(nspname)).value(),
+
+ // Create instances of collection, pagination and filter
+ coll = this.coll = this.getPageableCollection(baseUrl),
+ paginator = this.paginator = this.DbPaginator(coll),
+ dbObjectFilter = this.dbObjectFilter = this.DbObjectFilter(coll);
+
+ /**
+ It is the main model with schema defined
+ Every time a new wizard is opened,
+ a new model should create.
+ */
+ var GrantWizardModel = pgNode.Model.extend({
+ defaults: {
+ objects: undefined,
+ acl: undefined
+ },
+ schema: [
+ {
+ id: 'objects', label: '{{ _("Objects") }}', model: DatabaseObjectModel,
+ type: 'collection', group: 'Objects'
+ },
+ {
+ id: 'acl', label: '{{ _("Privileges") }}', node: 'role',
+ model: pgAdmin.Browser.Node.PrivilegeRoleModel.extend({
+
+ // privileges are selected based on node clicked
+ privileges: privDict[node_type]
+ }), uniqueCol : ['grantee', 'grantor'], editable: true,
+ type: 'collection', group: 'Privileges', canAdd: true,
+ canDelete: true, control: 'unique-col-collection'
+ }
+ ],
+ urlRoot: saveUrl
+ });
+
+ /**
+ Create instance of GrantWizard Model, provide urlRoot
+ node_info object, Generate fields objects
+ */
+ var self = this,
+ newModel = new GrantWizardModel({}, { node_info: info }),
+ fields = Backform.generateViewSchema(
+ info, newModel, 'create', d._type, d
+ );
+
+ /**
+ Fetch data from server and set into grid
+ and show/hide progress bar
+ */
+ $('.wizard-progress-bar p').show();
+
+ coll.fetch({
+ success: function(collection, data) {
+ $('.wizard-progress-bar p').html('');
+ },
+ reset: true
+ }, this);
+
+ /**
+ Override backgrid listener "backgrid:selected" to
+ Add/Remove model to/from objects collection
+ */
+ coll.on('backgrid:selected', function(model, selected) {
+ model.set('selected', selected);
+
+ /**
+ if a row (checkbox) is checked, add that model
+ into collection, when unchecked remove it from
+ model.
+ */
+ if(selected)
+ newModel.get('objects').add(model, { silent: true });
+ else
+ newModel.get('objects').remove(model);
+
+ // validate model on checkbox check/uncheck
+ var msg = model.validate.call(newModel);
+
+ /**
+ If no object type is selected, set error msg
+ and disable next button, else enable next button
+ */
+ if(msg)
+ self.onSessionInvalid.call(self, msg);
+ else
+ self.onSessionValidated.call(self, true);
+ });
+
+ // To track changes into model, start new session
+ newModel.startNewSession();
+
+ // Add event listener for privileges control
+ newModel.on('pgadmin-session:valid', self.onSessionValidated.bind(this));
+ newModel.on('pgadmin-session:invalid', self.onSessionInvalid.bind(this));
+
+ //////////////////////////////////////////////////////////////////////
+ // //
+ // Wizard Page for Db Object Type //
+ // //
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ Create wizard page. It renders a grid of
+ Database Object Types such as
+ Schemas, Views and Sequences etc.
+ Set default values
+ */
+ var dbObjectTypePage = self.dbObjectTypePage = new pgBrowser.WizardPage({
+ id: 1,
+ page_title: 'Object Selection (step 1 of 3)',
+ disable_prev: true,
+ disable_next: true,
+ show_description: _('Please select objects from the below list.'),
+ show_progress_bar: _('Please wait while fetching records...'),
+ model: newModel,
+ view: new (function() {
+
+ // Set page Instance
+ var pageView = this;
+
+ _.extend(pageView, {
+
+ // Remove grid if it is before render
+ cleanup: function() {
+ if (this.grid) {
+ this.grid.remove();
+ delete this.grid;
+ this.grid = null;
+ }
+
+ // Remove grid element if exists
+ if (this.el) {
+ $(this.el).remove();
+ delete this.el;
+ }
+ },
+
+ // Delete grid before render
+ grid: null,
+
+ render: function() {
+
+ // Create a grid container
+ var gridBody =
+ $('<div class="select_db_objects_container"></div>');
+
+ // Remove grid if exits before render
+ if (this.grid) {
+ this.cleanup();
+ }
+
+ // Initialize a new Grid instance
+ this.grid = new Backgrid.Grid({
+ columns: _.clone(columns),
+ collection: coll,
+ className: "backgrid table-bordered object_type_table"
+ });
+
+ // Render selection Type grid and paginator
+ gridBody.append(
+ this.grid.render().$el
+ ).append(self.paginator.render().el);
+
+ // Render Search Filter
+ gridBody.prepend(
+ self.clientSideFilter.render().el);
+
+ // Assign gridBody content to page element
+ this.el = gridBody;
+
+ /**
+ Fetch selected models from collection and
+ make rows checked in grid
+ */
+ newModel.get('objects').each(function(m) {
+ var model = coll.get(m.get('object_id'));
+ if (model) {
+ coll.trigger('backgrid:selected', model, true);
+ }
+ });
+
+ // Refresh grid to re render rows.
+ coll.trigger('backgrid:refresh');
+
+ return this;
+ }
+ });
+ }),
+
+ beforeNext: function(obj){
+ var self = this;
+ obj.options.disable_next = true;
+
+ /**
+ Enable/Disable next button of privilegePage if objects
+ are present in model
+ */
+ if(!_.isNull(newModel.get('acl')) &&
+ !_.isUndefined(newModel.get('acl'))) {
+ if(newModel.get('acl').length > 0)
+ obj.collection.at(1).set('disable_next', false);
+ }
+
+ // Clean the view
+ if (self.view) {
+ self.view.cleanup();
+ delete self.view;
+ self.view = null;
+ }
+ return true;
+ },
+
+ });
+
+ //////////////////////////////////////////////////////////////////////
+ // //
+ // Wizard Page for Privilege Control //
+ // //
+ //////////////////////////////////////////////////////////////////////
+
+ /**
+ Create Field Object which has properties like
+ node_data, node_info which is required for rendering
+ Privilege control
+ */
+ var privilegesField = new Backform.Field(fields[1].fields[0]);
+
+ // Wizard for Privelege control
+ var privilegePage = self.privilegePage = new pgBrowser.WizardPage({
+ id: 2,
+ page_title: _('Privileges Selection (step 2 of 3)'),
+ show_description: _('Please select privileges for the selected objects.'),
+ disable_next: true,
+ model: newModel,
+
+ // Create a view function object
+ view: new (function() {
+ var pageView = this;
+ _.extend(pageView, {
+
+ // Render Privelege control to generate its html markup
+ render: function() {
+ this.privControl = new (privilegesField.get('control')) ({
+ field: privilegesField,
+ model: newModel
+ });
+ return {el: this.privControl.render().$el};
+ },
+
+ // Remove the privilege control
+ cleanup: function() {
+ if (this.privControl) {
+ this.privControl.remove();
+ delete this.privControl;
+ this.privControl = null;
+ }
+ }
+ });
+ }),
+
+ beforePrev: function(wizardObj) {
+
+ // Remove the privilege control
+ if (this.view) {
+ this.view.cleanup();
+ delete this.view;
+ this.view = null;
+ }
+
+ /**
+ Enable/Disable next button of page 1 if objects
+ are present in model
+ */
+ var objectsModel = newModel.get('objects');
+
+ if(!_.isUndefined(objectsModel) && !_.isEmpty(objectsModel) &&
+ objectsModel.length > 0) {
+ wizardObj.collection.at(0).set('disable_next', false);
+
+ // Don't show progress bar
+ wizardObj.collection.at(0).set('show_progress_bar', '');
+ }
+
+ /**
+ We're re-rendering the controls as they are deleted
+ before heading to next page
+ Refresh Backgrid to re-render the elements selected
+ re-render paginator
+ re-render Filter
+ */
+ newModel.trigger("backgrid:refresh", newModel, false);
+ self.paginator.render();
+ self.clientSideFilter.render();
+ return true;
+ },
+
+ beforeNext: function() { return true; },
+
+ onNext: function(obj){
+
+ // Remove the privilege control
+ if (this.view) {
+ this.view.cleanup();
+ delete this.view;
+ this.view = null;
+ }
+
+ // Enable finish button
+ self.wizard.options.disable_finish = false;
+
+ /**
+ triggers to get SQL queries data to render
+ into the reviewSQLPage
+ */
+ newModel.trigger('pgadmin-wizard:nextpage:sql', {'node_type': node_type });
+ }
+ });
+
+
+ //////////////////////////////////////////////////////////////////////
+ // //
+ // Review SQL Query Page //
+ // //
+ //////////////////////////////////////////////////////////////////////
+
+ //Create SqlField Object
+ var sqlField = new Backform.Field(
+ {
+ id: 'sqltab',
+ label: _('Sql Tab'),
+ field: fields,
+
+ /**
+ Extend 'SqlTabControl' to define new
+ function 'onWizardNextPageChange'
+ which gets triggered on next button
+ click to fetch generated SQL query
+ for the selected db objects.
+ */
+ control: Backform.SqlTabControl.extend({
+ initialize: function() {
+
+ // Initialize parent class
+ Backform.SqlTabControl.prototype.initialize.apply(this, arguments);
+
+ this.msql_url = self.msql_url;
+
+ // define trigger events for prev and next page
+ this.model.on('pgadmin-wizard:nextpage:sql', this.onWizardNextPageChange, this);
+ this.model.on('pgadmin-wizard:prevpage:sql', this.onWizardPrevPageChange, this);
+ },
+
+ // This method fetches the modified SQL for the wizard
+ onWizardNextPageChange: function(){
+
+ var self = this;
+
+ // Fetches modified SQL
+ $.ajax({
+ url: this.msql_url,
+ type: 'GET',
+ cache: false,
+ data: self.model.toJSON(true, 'GET'),
+ dataType: "json",
+ contentType: "application/json"
+ }).done(function(res) {
+ self.sqlTab.clearHistory();
+ self.sqlTab.setValue(res.data);
+ }).fail(function() {
+ self.model.trigger('pgadmin-view:msql:error');
+ }).always(function() {
+ self.model.trigger('pgadmin-view:msql:fetched');
+ });
+ },
+
+ remove: function() {
+
+ // Stop listen to next/prev page events
+ this.model.off('pgadmin-wizard:nextpage:sql', this.onWizardNextPageChange, this);
+ this.model.off('pgadmin-wizard:prevpage:sql', this.onWizardPrevPageChange, this);
+ Backform.SqlTabControl.__super__.remove.apply(this, arguments);
+ }
+
+ })
+ }),
+
+ /**
+ Create sqlField view instance
+ to render it into wizard page
+ */
+ sqlControl = new (sqlField.get('control'))({
+ field: sqlField,
+ model: newModel
+ });
+
+ // Wizard for SQL tab control
+ var reviewSQLPage = self.reviewSQLPage = new pgBrowser.WizardPage({
+ id: 3,
+ page_title: _('Final (Review Selection) (step 3 of 3)'),
+ show_description: _('Following query will be executed on the database'
+ +' server for the selected objects, and privileges.'
+ +' Please click on Finish to complete the process.'),
+ model: newModel,
+ view: new(function() {
+
+ // Render SqlTab control to generate its html markup
+ var sqlTabHtml = sqlControl.render().$el;
+ this.render = function() {
+ return { el: sqlTabHtml };
+ };
+ }),
+
+ beforePrev: function(wizardObj) {
+
+ /**
+ Enable next button if privilege
+ model is not empty else disable
+ next button
+ */
+ var aclModel = newModel.get('acl');
+
+ if(!_.isUndefined(wizardObj.collection) &&
+ wizardObj.collection.models.length > 0) {
+ if(!_.isUndefined(aclModel) && !_.isEmpty(aclModel) &&
+ aclModel.length > 0) {
+ wizardObj.collection.at(1).set('disable_next', false);
+ }
+ else {
+ wizardObj.collection.at(1).set('disable_next', true);
+ }
+
+ return true;
+ }
+ },
+ });
+
+
+ // Create Wizard Collection of Wizard Pages
+ var WizardCollection = Backbone.Collection.extend({
+ model: pgBrowser.WizardPage
+ });
+
+ // It holds all the wizard pages to be rendered
+ this.wizardCollection = new WizardCollection(
+ [dbObjectTypePage, privilegePage, reviewSQLPage]
+ );
+
+ /**
+ Create wizard which has following operations:
+ - renders wizard pages
+ - defines the first page to render in wizard
+ - Save the model on finishbutton
+ - Remove wizard on cancel button
+ */
+ var wizard = self.wizard = new (pgBrowser.Wizard.extend({
+ options: {
+ title: 'Grant Wizard', /* Main Wizard Title */
+ width: '',
+ height: '',
+ curr_page: 0,
+ show_left_panel: false,
+ disable_finish: true
+ },
+
+ onFinish: function() {
+ var m = newModel,
+ d = m.toJSON('GET');
+
+ if (d && !_.isEmpty(d) && !_.isUndefined(d.objects)) {
+ m.save({}, {
+ attrs: d,
+ validate: false,
+ cache: false,
+ success: function(res) {
+
+ // Release wizard objects
+ self.releaseObjects();
+ self.close();
+ },
+ error: function(m, jqxhr) {
+ alertify.pgNotifier(
+ "error", jqxhr,
+ S(
+ "{{ _('Error during saving properties - %%s!') }}"
+ ).sprintf(jqxhr.statusText).value()
+ );
+ }
+ });
+ }
+ },
+ onCancel: function() {
+
+ // Release wizard objects
+ self.releaseObjects();
+ self.close();
+ }
+ })) ({
+ collection: this.wizardCollection,
+ el: el,
+ model: newModel
+ });
+
+ // Render wizard
+ wizard.render();
+ }
+ };
+ });
+ }
+
+ // Call Grant Wizard Dialog
+ alertify.wizardDialog('');
+ }
+ };
+
+ return pgBrowser.GrantWizard;
+});
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/allowed_acl.json b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/allowed_acl.json
new file mode 100644
index 0000000..cb73d2c
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/allowed_acl.json
@@ -0,0 +1,30 @@
+{# List of allowed privileges of Schema, table sequence, function and view #}
+{#
+ Format for allowed privileges are:
+ "node_name": {
+ "type": "name",
+ "acl": [...]
+ }
+#}
+{
+ "schema": {
+ "type": "SCHEMA",
+ "acl": ["a", "r", "w", "d", "D", "x", "t", "U", "X"]
+ },
+ "coll-table": {
+ "type": "TABLE",
+ "acl": ["a", "w", "U", "d", "D", "x", "t"]
+ },
+ "coll-sequence": {
+ "type": "SEQUENCE",
+ "acl": ["a", "w", "u"]
+ },
+ "coll-function": {
+ "type": "FUNCTION",
+ "acl": ["X"]
+ },
+ "coll-view": {
+ "type": "VIEW",
+ "acl": ["a", "r", "w", "d", "D", "x", "t"]
+ }
+}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/grant.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/grant.sql
new file mode 100644
index 0000000..02ac1a7
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/grant.sql
@@ -0,0 +1,19 @@
+{#=====Grant Permissions on Database Objects Selected====#}
+{% import 'macros/schemas/privilege.macros' as PRIVILEGE %}
+{% import 'macros/functions/privilege.macros' as PRIVILEGE_FUNCTION %}
+{% if data.acl %}
+{% for obj in data.objects %}
+{% for priv in data.acl %}
+{% set obj_type = obj.object_type %}
+{% if obj_type.upper() != 'FUNCTION' and obj_type.upper() != 'TRIGGER FUNCTION' %}
+{% if obj_type.upper() == 'VIEW' %} {###### Views are also kind of table ######}
+{% set obj_type = 'TABLE' %}
+{% endif %}
+{{ PRIVILEGE.SET(conn, obj_type.upper(), priv.grantee, obj.name, priv.without_grant, priv.with_grant, nspname ) }}
+{% else %}
+{###### if object_type is Function then apply function marcros ######}
+{{ PRIVILEGE_FUNCTION.SET(conn, 'FUNCTION', priv.grantee, obj.name, priv.without_grant, priv.with_grant, nspname, obj.proargs)}}
+{% endif %}
+{% endfor %}
+{% endfor %}
+{% endif %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/properties.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/properties.sql
new file mode 100644
index 0000000..7a33c1f
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/sql/properties.sql
@@ -0,0 +1,65 @@
+{#===================Fetch list of Database object types(Functions, Sequence, Tables or View)===================#}
+{% if type == 'schema' or type == 'trigger_function' and node_id and node_name %}
+{% set func_type = 'Trigger Function' if type == 'trigger_function' else 'Function' %}
+SELECT
+ pr.proname AS name,
+ pg_get_function_identity_arguments(pr.oid) AS proargs,
+ '{{ func_type }}' AS object_type,
+ '{{ nspname }}' AS nspname,
+ 'icon-function' AS icon
+FROM pg_proc pr
+ JOIN pg_type typ ON typ.oid=prorettype
+ JOIN pg_namespace typns ON typns.oid=typ.typnamespace
+ JOIN pg_language lng ON lng.oid=prolang
+ LEFT OUTER JOIN pg_description des ON (des.objoid=pr.oid AND des.classoid='pg_proc'::regclass)
+WHERE proisagg = FALSE AND pronamespace = {{ node_id }}::oid
+ AND typname {{ 'NOT' if type != 'trigger_function' else '' }} IN ('trigger', 'event_trigger')
+ORDER BY proname
+{% endif %}
+
+{% if type == 'sequence' and node_id and node_name %}
+SELECT
+ cl.relname AS name,
+ 'Sequence' AS object_type,
+ '{{ nspname }}' AS nspname,
+ 'icon-sequence' AS icon
+FROM pg_class cl
+ LEFT OUTER JOIN pg_description des ON (des.objoid=cl.oid AND des.classoid='pg_class'::regclass)
+WHERE relkind = 'S' AND relnamespace = {{ node_id }}::oid
+ORDER BY cl.relname
+{% endif %}
+
+{% if type == 'table' and node_id and node_name %}
+SELECT rel.relname AS name,
+ 'Table' AS object_type,
+ '{{ nspname }}' AS nspname,
+ 'icon-table' AS icon
+FROM pg_class rel
+ LEFT OUTER JOIN pg_tablespace spc ON spc.oid=rel.reltablespace
+ LEFT OUTER JOIN pg_description des ON (des.objoid=rel.oid AND des.objsubid=0 AND des.classoid='pg_class'::regclass)
+ LEFT OUTER JOIN pg_constraint con ON con.conrelid=rel.oid AND con.contype='p'
+ LEFT OUTER JOIN pg_class tst ON tst.oid = rel.reltoastrelid
+ LEFT JOIN pg_type typ ON rel.reloftype=typ.oid
+WHERE rel.relkind IN ('r','s','t') AND rel.relnamespace = {{ node_id }}::oid
+ORDER BY rel.relname
+{% endif %}
+
+{% if type == 'coll-view' and node_id and node_name %}
+SELECT
+ c.relname AS name,
+ 'View' AS object_type,
+ '{{ nspname }}' AS nspname,
+ 'icon-coll-view' AS icon
+FROM pg_class c
+ LEFT OUTER JOIN pg_tablespace spc ON spc.oid=c.reltablespace
+ LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid and des.objsubid=0 AND des.classoid='pg_class'::regclass)
+ LEFT OUTER JOIN pg_class tst ON tst.oid = c.reltoastrelid
+WHERE ((c.relhasrules AND (EXISTS (
+ SELECT r.rulename
+ FROM pg_rewrite r
+ WHERE ((r.ev_class = c.oid)
+ AND (bpchar(r.ev_type) = '1'::bpchar)) ))
+ ) OR (c.relkind = 'v'::char))
+AND c.relnamespace = {{ node_id }}::oid
+ORDER BY c.relname
+{% endif %}
view thread (24+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected]
Subject: Re: [pgAdmin4] [Patch]: Grant Wizard
In-Reply-To: <CAM5-9D_D3TGq9-DbcR+07=p2cgT=yEtmEJnHU9rtmov_nby9+g@mail.gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox