public inbox for [email protected]
help / color / mirror / Atom feedFrom: Surinder Kumar <[email protected]>
To: Dave Page <[email protected]>
Cc: Ashesh Vashi <[email protected]>
Cc: pgadmin-hackers <[email protected]>
Subject: Re: [pgAdmin4] [Patch]: Grant Wizard
Date: Fri, 8 Apr 2016 23:29:57 +0530
Message-ID: <CAM5-9D_TmnFfVEw1OXTL-f4jgtCiE+e2rQuSKa4yqPX7ZsSBNg@mail.gmail.com> (raw)
In-Reply-To: <CA+OCxoyC-U+u8eESm8NXchX+s9dwdCFKMJ3x5JcZNCAgN4CnPw@mail.gmail.com>
References: <CAM5-9D8oYyEaL89coD9FozPDziGhYutYAat9dMP9dAwhOp5OKQ@mail.gmail.com>
<CA+OCxozy4aH9=2T0Sp_WworU_3UQaaHBcQXTDhEUKHAkZJT8ow@mail.gmail.com>
<CA+OCxoyGKAorswp0fwUojT4gB0Ex+dcRxzKbLkfG48qeCmSCrQ@mail.gmail.com>
<CAM5-9D_D3TGq9-DbcR+07=p2cgT=yEtmEJnHU9rtmov_nby9+g@mail.gmail.com>
<CAM5-9D_6kD3yEcZ3A4A6WOT7aRk_EGru6LSswQv+PBmK=2QKfA@mail.gmail.com>
<CA+OCxozwOV+FixF3-9KgmGOaytZQ3Fsn3_BNAyCNqQPtLp+_VQ@mail.gmail.com>
<CAM5-9D9vGUgGaACFZc1YxFNp_gi-fAm=w2rf_JmNtD+HBi64=w@mail.gmail.com>
<CA+OCxozvNDNi0MBVQ3Gq7GhT27o2MX2=KuFUE+pUUqOCbufe+A@mail.gmail.com>
<CAM5-9D8ZHTvx53WhL2S0XUt8o+LE0aiCxMLQJGP2PUg_Q4S1dw@mail.gmail.com>
<CAM5-9D-bSJx-u_K+o2eQhd8r_39+CTxmabg9wkXP_NzVu9Eo2A@mail.gmail.com>
<CA+OCxozp8ZS5wHLAFuONT9Ap93De6so2Eg=ta028eusQwt=fEA@mail.gmail.com>
<CAM5-9D9Z9-x9Bbb0rFFS34xCy_GNsdot2bQ4rbYMPq=pWL3Vew@mail.gmail.com>
<CAM5-9D9hQZWsYETszCV7=dVsLf6KHojC9P+_icmHFGCh8jFHnQ@mail.gmail.com>
<CAG7mmowJONpMzMva7qxLQ=Wd5bFxXMsV7URosk8O5zVKLPXwnA@mail.gmail.com>
<CAM5-9D9R4vR6TfOLd+NiSVWZy+L0Z+8Jj=A-v-tP5f_5Of5ksA@mail.gmail.com>
<CA+OCxownatDkVQsZGKJUwO1=co0XOf0J=U5ydSYh8=xXi7ogTQ@mail.gmail.com>
<CAM5-9D-ixhoFahG2dxmZCxhv_VVm0ncsqm075kemZ+M7kuoKPw@mail.gmail.com>
<CA+OCxoyC-U+u8eESm8NXchX+s9dwdCFKMJ3x5JcZNCAgN4CnPw@mail.gmail.com>
List-Unsubscribe: <mailto:[email protected]?body=unsub%20pgadmin-hackers>
Hi
PFA patch with resolved review comments.
On Fri, Apr 8, 2016 at 12:52 AM, Dave Page <[email protected]> wrote:
> Hi
>
> Nearly there :-). Assuming no regressions, I believe we'll be ready to
> commit once the following issues are resolved:
>
> - The select object grid should fill the available vertical space in the
> dialogue - at present there's a gap below it.
>
Done
>
> - When scrolling to the top of bottom of the select object grid, the
> dialogue jumps up or down the screen a little, if the dialogue has been
> resized to a larger size.
>
As this change is generic for all alertifyjs dialogs, I have added its
style css in overrides.css. Done
>
> - Please allow the wizard to be opened from a Database node, in which case
> objects from all schemas should be listed. This will also require support
> for schemas themselves.
>
Done
>
> - When selecting privileges, each time I click on a checkbox, the row
> closes. Similar grids elsewhere in the app close the row when the cell
> loses focus, *however*, that is also the incorrect behaviour - the row
> should only close when the row itself loses focus (and should open when it
> gets focus).
>
I checked the row closes either when gets clicked on row or outside row, it
doesn't seems to close on click on checkbox.
>
> - The message:
>
> Please select objects from the below list.
>
> should read:
>
> Please select objects from the list below.
>
Done
>
> - The message:
>
> Following query will be executed on the database server for the selected
> objects, and privileges. Please click on Finish to complete the process.
>
> should read:
>
> The SQL below will be executed on the database server to grant the
> selected privileges. Please click on <b>Finish</b> to complete the process.
>
Done
>
> Thanks!
>
> On Thu, Apr 7, 2016 at 6:10 PM, Surinder Kumar <
> [email protected]> wrote:
>
>> Hi Dave,
>>
>> Please find updated patch with above issue resolved.
>>
>> On Thu, Apr 7, 2016 at 7:47 PM, Dave Page <[email protected]> wrote:
>>
>>> Hi
>>>
>>> On Wed, Apr 6, 2016 at 12:37 PM, Surinder Kumar <
>>> [email protected]> wrote:
>>>
>>>> Hi
>>>>
>>>> PFA updated patch with resolved review comments.
>>>>
>>>> On Tue, Apr 5, 2016 at 11:06 AM, Ashesh Vashi <
>>>> [email protected]> wrote:
>>>>
>>>>> On Wed, Mar 30, 2016 at 5:14 PM, Surinder Kumar <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi,
>>>>>>
>>>>>> Please find updated patch.
>>>>>>
>>>>>> This patch has following changes:
>>>>>> 1. Improved code commenting.
>>>>>> 2. Properly handling memory leak issues in js code.
>>>>>>
>>>>> Hi Surinder,
>>>>>
>>>>> As discussed offline, here are the list of some of the review comments:
>>>>>
>>>>> * CSS should be relative to its parent element. Please make sure -
>>>>> whenever you make
>>>>> some changes in CSS, it should not affect the existing CSS unless
>>>>> discussed.
>>>>>
>>>> Done
>>>>
>>>>>
>>>>> * Change class name for 'error_msg_div' as it is common name. Please
>>>>> name a class
>>>>> with prefixed as the module name.
>>>>>
>>>> Done
>>>>
>>>>>
>>>>> * Add comments for the blow line changed in node.ui.js file. Always
>>>>> add logical
>>>>> explanation for a change as a comment for any changes.
>>>>> *while(p && p.length > 0) {*
>>>>>
>>>> Done
>>>>
>>>>>
>>>>> * Please make sure, we wrap the code around 80 characters for better
>>>>> readability.
>>>>> Line length should not be greater than 80 characters.
>>>>>
>>>> Done
>>>>
>>>>>
>>>>> * Put the allowed ACLs logic with server version support. We need to
>>>>> be flexible
>>>>> enough to accommodate possible future change in ACLs.
>>>>>
>>>> Done
>>>>
>>>>>
>>>>> * Avoid using name as reference in each of the given. It will make
>>>>> the search faster
>>>>> in the database and less prone to character conversion issue.
>>>>> i.e.
>>>>> Use schema/namespace OID instead of nspname, object OID instead of
>>>>> their name.
>>>>>
>>>> Done
>>>>
>>>>>
>>>>> * Use separate templates for each type of objects.
>>>>>
>>>> Done
>>>>
>>>>>
>>>>> * Use the existing functionalities as much as possible instead of
>>>>> introducing new
>>>>> one. That will make the code/results consistent across the
>>>>> application.
>>>>> i.e.
>>>>> Use existing 'parse_priv_to_db' method, instead of creating new one.
>>>>>
>>>> Done
>>>>
>>>>>
>>>>> * Please remove unnecessary suffixed white-spaces.
>>>>>
>>>> Done
>>>>
>>>
>>> I get the attached error in the browser console when selecting "Grant
>>> Wizard" from the menu when the current object is a schema. I've tried all
>>> the normal refreshing/restarting.
>>>
>>> [image: Inline image 1]
>>>
>>>
>>> --
>>> Dave Page
>>> Blog: http://pgsnake.blogspot.com
>>> Twitter: @pgsnake
>>>
>>> EnterpriseDB UK: http://www.enterprisedb.com
>>> The Enterprise PostgreSQL Company
>>>
>>
>>
>
>
> --
> 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:
[image/png] Screen Shot 2016-04-07 at 15.15.21.png (117.8K, 3-Screen%20Shot%202016-04-07%20at%2015.15.21.png)
download | view image
[application/octet-stream] grant_wizard_v9.patch (89.7K, 4-grant_wizard_v9.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..0be48dc
--- /dev/null
+++ b/web/pgadmin/browser/static/css/wizard.css
@@ -0,0 +1,143 @@
+/** CSS for Wizard **/
+.pgadmin_grant_wizard_body .ajs-content {
+ padding-top: 0px !important;
+}
+
+.wizard-header {
+ background: #428bca;
+ padding-left: 15px;
+ padding-bottom: 7px;
+ color: #fff;
+ font-size: 18px;
+ height: 76px;
+ line-height: 76px;
+ border-radius: 6px 6px 0 0;
+}
+
+.wizard-header h3 {
+ font-size: 18px;
+ display: inline-block;
+}
+
+.wizard_dlg {
+ float: left;
+ height: 100%;
+ width: 100%;
+}
+
+.grant_wizard_container {
+ position: relative;
+ overflow: hidden;
+ width: 100%;
+ height: 100%;
+}
+
+.grant_wizard_container .pgadmin-wizard {
+ width: 100%;
+ height: 100%;
+}
+
+.grant_wizard_container .wizard-content {
+ position: relative;
+ padding: 0;
+ height: 78%;
+}
+
+.grant_wizard_container .wizard-right-panel {
+ overflow-y: auto;
+ height: 100%;
+ top: 0;
+ right: 0;
+ position: absolute;
+ bottom: 0;
+}
+
+.grant_wizard_container .wizard-left-panel {
+ position: absolute;
+ top: 0;
+ display: flex;
+ bottom: 0;
+ left: 0;
+ align-items: center;
+ justify-content: center;
+ right: 0;
+}
+
+.grant_wizard_container .wizard-left-panel img {
+ width: 140px;
+}
+
+.grant_wizard_container .wizard-right-panel_content {
+ height: 60%;
+}
+
+.grant_wizard_container {
+ height: 100%;
+}
+
+/* Wizard Footer CSS */
+.grant_wizard_container .footer {
+ position: absolute;
+ background: #fff;
+ border-top: 1px solid #ccc;
+ bottom: 0px;
+ height: 62px;
+ right: 0px;
+ padding-top: 22px;
+ border-radius: 0 0 6px 6px;
+ z-index: 10;
+}
+
+/* Wizard Button CSS */
+.grant_wizard_container .wizard-buttons {
+ float: right;
+}
+
+.grant_wizard_container .wizard-buttons button {
+ float: left;
+ padding: 7px 15.2px;
+ font-size: 14px;
+ margin: 0px 5px 0 0 !important;
+}
+
+.grant_wizard_container .wizard-buttons button.wizard-next i.fa {
+ padding-left: 5px;
+}
+
+.grant_wizard_container .wizard-buttons button.wizard-back i.fa,
+.grant_wizard_container .wizard-buttons button.wizard-cancel i.fa {
+ padding-right: 5px;
+}
+
+.grant_wizard_container .wizard-buttons .wizard-finish {
+ margin-right: 0 !important;
+}
+
+/* Wizard Status bar CSS */
+.grant_wizard_container .wizard-description {
+ padding: 1.7em 0.1em;
+}
+
+/* Error message css */
+.grant_wizard_container .error_msg_div {
+ display: block;
+ position: absolute;
+ bottom: 55px;
+ background: #fff;
+}
+
+.grant_wizard_container .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/node.ui.js b/web/pgadmin/browser/static/js/node.ui.js
index 4d69e1f..de3f577 100644
--- a/web/pgadmin/browser/static/js/node.ui.js
+++ b/web/pgadmin/browser/static/js/node.ui.js
@@ -279,7 +279,8 @@ function($, _, pgAdmin, Backbone, Backform, Alertify, Node) {
return false;
};
- while(p) {
+ // check if p is not empty
+ while(p && p.length > 0) {
top = p.get(0).offsetTop + p.height();
p = p.parent();
if (hasScrollbar(p)) {
diff --git a/web/pgadmin/browser/static/js/wizard.js b/web/pgadmin/browser/static/js/wizard.js
new file mode 100644
index 0000000..383a312
--- /dev/null
+++ b/web/pgadmin/browser/static/js/wizard.js
@@ -0,0 +1,233 @@
+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){ %>9<% }"
+ + " 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 btn-primary wizard-back' <%=this.options.disable_prev ? 'disabled' : ''%>>"
+ + " <i class='fa fa-backward'></i>Back</button>"
+ + " <button class='btn btn-primary wizard-next' <%=this.options.disable_next ? 'disabled' : ''%>>Next"
+ + " <i class='fa fa-forward'></i></button>"
+ + " <button class='btn btn-danger wizard-cancel' <%=this.options.disable_cancel ? 'disabled' : ''%>>"
+ + " <i class='fa fa-lg fa-close'></i>Cancel</button>"
+ + " <button class='btn 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();
+ this.remove(); // Remove view from DOM
+ this.unbind(); // Unbind all local event bindings
+ delete this.$el; // Delete the jQuery wrapped object variable
+ delete this.el; // Delete the variable reference to this node
+ 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/static/css/overrides.css b/web/pgadmin/static/css/overrides.css
index 2fcaae2..f1de6a5 100755
--- a/web/pgadmin/static/css/overrides.css
+++ b/web/pgadmin/static/css/overrides.css
@@ -45,6 +45,10 @@ body {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAVElEQVQY05WQuQ0AMQgEB8lN0H9vdhnj4B7ZJwfcJkjsImBCpaIGMMYQIDNjNbe+Su9d1bsee5yM0+AbXMPfkHrdWNKv1ZVn2oJiw5OZ8eABiCrwCW8QwRIof5qAAAAAAElFTkSuQmCC) !important;
}
+.alertify .ajs-modal {
+ overflow: hidden;
+}
+
/* iFrames should have no border */
iframe {
border-width: 0;
diff --git a/web/pgadmin/tools/grant_wizard/__init__.py b/web/pgadmin/tools/grant_wizard/__init__.py
new file mode 100644
index 0000000..f53b979
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/__init__.py
@@ -0,0 +1,461 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Implements Grant Wizard"""
+
+import simplejson as 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
+from pgadmin.utils.ajax import precondition_required
+from functools import wraps
+from pgadmin.utils.preferences import Preferences
+
+# set template path for sql scripts
+MODULE_NAME = 'grant_wizard'
+server_info = {}
+
+
+class GrantWizardModule(PgAdminModule):
+ """
+ class GrantWizardModule(Object):
+
+ It is a wizard which inherits PgAdminModule
+ class and define methods to load its own
+ javascript file.
+
+ LABEL = gettext('Browser')
+ """
+ def get_own_stylesheets(self):
+ """
+ Returns:
+ list: the stylesheets used by this module.
+ """
+ stylesheets = [
+ url_for('browser.static', filename='css/wizard.css'),
+ url_for('grant_wizard.static', filename='css/grant_wizard.css')
+ ]
+ 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
+
+ def show_system_objects(self):
+ """
+ return system preference objects
+ """
+ return self.pref_show_system_objects
+
+ def register_preferences(self):
+ """
+ Get show_system_objects preference
+ """
+ self.browser_preference = Preferences.module('browser')
+ self.pref_show_system_objects = self.browser_preference.preference(
+ 'show_system_objects'
+ )
+
+# Create blueprint for GrantWizardModule class
+blueprint = GrantWizardModule(
+ MODULE_NAME, __name__, static_url_path='')
+
+
+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 instance of the method.
+
+ Assumptions:
+ This function will always be used as decorator of a class method.
+ """
+ @wraps(f)
+ def wrap(*args, **kwargs):
+ # Here args[0] will hold self & kwargs will hold gid,sid,did
+
+ server_info.clear()
+ server_info['manager'] = get_driver(
+ PG_DEFAULT_DRIVER).connection_manager(
+ kwargs['sid']
+ )
+ server_info['conn'] = server_info['manager'].connection(
+ did=kwargs['did']
+ )
+ # If DB not connected then return error to browser
+ if not server_info['conn'].connected():
+ return precondition_required(
+ gettext("Connection to the server has been lost!")
+ )
+
+ # Set template path for sql scripts
+ server_info['server_type'] = server_info['manager'].server_type
+ server_info['version'] = server_info['manager'].version
+ if server_info['server_type'] == 'pg':
+ server_info['template_path'] = 'grant_wizard/pg/9.1_plus'
+ elif server_info['server_type'] == 'ppas':
+ server_info['template_path'] = 'grant_wizard/ppas/9.1_plus'
+
+ return f(*args, **kwargs)
+
+ return wrap
+
+
[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](
+ '/acl/<int:gid>/<int:sid>/<int:did>/', methods=('GET', 'POST'))
+@login_required
+@check_precondition
+def acl_list(gid, sid, did):
+ """render list of acls"""
+ server_prop = server_info
+ return Response(response=render_template(
+ server_prop['template_path']+"/acl.json", _=gettext),
+ status=200,
+ mimetype="application/json")
+
+
[email protected](
+ '/properties/<int:gid>/<int:sid>/<int:did>'
+ '/<int:node_id>/<node_type>/',
+ methods=('GET', 'POST'))
+@login_required
+@check_precondition
+def properties(gid, sid, did, node_id, node_type):
+ """It fetches the properties of object types
+ and render into selection page of wizard
+ """
+
+ # unquote encoded url parameter
+ node_type = unquote(node_type)
+
+ server_prop = server_info
+
+ res_data = []
+ manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+ conn = manager.connection(did=did)
+
+ node_types = []
+ nspname = ''
+ show_sysobj = blueprint.show_system_objects().get()
+ if node_type == 'database':
+
+ # Fetch list of schemas
+ # Get sys_obj_values and get list of schemas
+ ntype = 'schema'
+ SQL = render_template("/".join(
+ [server_prop['template_path'], '/sql/get_schemas.sql']),
+ show_sysobj=show_sysobj)
+ status, res = conn.execute_dict(SQL)
+
+ if not status:
+ return internal_server_error(errormsg=res)
+ node_types = res['rows']
+ else:
+ SQL = render_template("/".join(
+ [server_prop['template_path'], '/sql/get_schemas.sql']),
+ nspid=node_id, show_sysobj=False)
+ status, res = conn.execute_dict(SQL)
+
+ if not status:
+ return internal_server_error(errormsg=res)
+ node_types = res['rows']
+ ntype = node_type
+ nspname = node_types[0]['name']
+
+ for row in node_types:
+ if 'oid' in row:
+ node_id = row['oid']
+ nspname = row['name']
+
+ # Fetch functions against schema
+ if ntype in ['schema', 'function']:
+ SQL = render_template("/".join(
+ [server_prop['template_path'], '/sql/function.sql']),
+ node_id=node_id, nspname=nspname, type='function')
+
+ status, res = conn.execute_dict(SQL)
+
+ if not status:
+ return internal_server_error(errormsg=res)
+
+ res_data.extend(res['rows'])
+
+ # Fetch trigger functions
+ if ntype in ['schema', 'trigger_function']:
+ SQL = render_template("/".join(
+ [server_prop['template_path'], '/sql/function.sql']),
+ node_id=node_id, nspname=nspname, type='trigger_function')
+ 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 ntype in ['schema', 'sequence']:
+ SQL = render_template("/".join(
+ [server_prop['template_path'], '/sql/sequence.sql']),
+ node_id=node_id, 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 ntype in ['schema', 'table']:
+ SQL = render_template("/".join(
+ [server_prop['template_path'], '/sql/table.sql']),
+ node_id=node_id, 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 ntype in ['schema', 'view']:
+ SQL = render_template("/".join(
+ [server_prop['template_path'], '/sql/view.sql']),
+ node_id=node_id, 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](
+ '/msql/<int:gid>/<int:sid>/<int:did>/',
+ methods=('GET', 'POST'))
+@login_required
+@check_precondition
+def msql(gid, sid, did):
+ """
+ This function will return modified SQL
+ """
+
+ server_prop = server_info
+ 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([server_prop['template_path'], '/acl.json'])
+ )
+ acls = json.loads(acls)
+ except Exception as e:
+ current_app.logger.exception(e)
+
+ try:
+
+ # Parse privileges
+ data['priv'] = {}
+ if 'acl' in data:
+ # Get function acls
+ data['priv']['function'] = parse_priv_to_db(
+ data['acl'],
+ acls['function']['acl'])
+
+ data['priv']['sequence'] = parse_priv_to_db(
+ data['acl'],
+ acls['sequence']['acl'])
+
+ data['priv']['table'] = parse_priv_to_db(
+ data['acl'],
+ acls['table']['acl'])
+
+ # Pass database objects and get SQL for privileges
+ SQL_data = ''
+ data_func = {}
+ data_func['objects'] = data['objects']
+ data_func['priv'] = data['priv']['function']
+ SQL = render_template(
+ "/".join([server_prop['template_path'],
+ '/sql/grant_function.sql']),
+ data=data_func, conn=conn)
+ if SQL and SQL.strip('\n') != '':
+ SQL_data += SQL
+
+ data_seq = {}
+ data_seq['objects'] = data['objects']
+ data_seq['priv'] = data['priv']['sequence']
+ SQL = render_template(
+ "/".join([server_prop['template_path'],
+ '/sql/grant_sequence.sql']),
+ data=data_seq, conn=conn)
+ if SQL and SQL.strip('\n') != '':
+ SQL_data += SQL
+
+ data_table = {}
+ data_table['objects'] = data['objects']
+ data_table['priv'] = data['priv']['table']
+ SQL = render_template(
+ "/".join([server_prop['template_path'], '/sql/grant_table.sql']),
+ data=data_table, conn=conn)
+ if SQL and SQL.strip('\n') != '':
+ SQL_data += SQL
+
+ res = {'data': SQL_data}
+
+ return ajax_response(
+ response=res,
+ status=200
+ )
+
+ except Exception as e:
+ return make_json_response(
+ status=410,
+ success=0,
+ errormsg=e.message
+ )
+
+
[email protected](
+ '/save/<int:gid>/<int:sid>/<int:did>/',
+ methods=('GET', 'POST'))
+@login_required
+@check_precondition
+def save(gid, sid, did):
+ """
+ This function will apply the privileges to the selected
+ Database Objects
+ """
+ server_prop = server_info
+ 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([server_prop['template_path'], 'acl.json']),
+ )
+ acls = json.loads(acls)
+ except Exception as e:
+ current_app.logger.exception(e)
+
+ try:
+
+ # Parse privileges
+ data['priv'] = {}
+ if 'acl' in data:
+ # Get function acls
+ data['priv']['function'] = parse_priv_to_db(
+ data['acl'],
+ acls['function']['acl'])
+
+ data['priv']['sequence'] = parse_priv_to_db(
+ data['acl'],
+ acls['sequence']['acl'])
+
+ data['priv']['table'] = parse_priv_to_db(
+ data['acl'],
+ acls['table']['acl'])
+
+ # Pass database objects and get SQL for privileges
+ # Pass database objects and get SQL for privileges
+ SQL_data = ''
+ data_func = {}
+ data_func['objects'] = data['objects']
+ data_func['priv'] = data['priv']['function']
+ SQL = render_template(
+ "/".join([server_prop['template_path'],
+ '/sql/grant_function.sql']),
+ data=data_func, conn=conn)
+ if SQL and SQL.strip('\n') != '':
+ SQL_data += SQL
+
+ data_seq = {}
+ data_seq['objects'] = data['objects']
+ data_seq['priv'] = data['priv']['sequence']
+ SQL = render_template(
+ "/".join([server_prop['template_path'],
+ '/sql/grant_sequence.sql']),
+ data=data_seq, conn=conn)
+ if SQL and SQL.strip('\n') != '':
+ SQL_data += SQL
+
+ data_table = {}
+ data_table['objects'] = data['objects']
+ data_table['priv'] = data['priv']['table']
+ SQL = render_template(
+ "/".join([server_prop['template_path'], '/sql/grant_table.sql']),
+ data=data_table, conn=conn)
+ if SQL and SQL.strip('\n') != '':
+ SQL_data += SQL
+
+ status, res = conn.execute_dict(SQL_data)
+ 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..18240e9
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/static/css/grant_wizard.css
@@ -0,0 +1,109 @@
+/** Grant Wizard CSS **/
+
+/**
+ CSS to make db object type table
+ fixed so that tbody content may
+ scroll
+ */
+
+.db_objects_container {
+ height: 100%;
+}
+
+.object_type_table {
+ display: inline-block;
+ height: 100%;
+ border: 0 !important;
+}
+
+.object_type_table thead tr {
+ position: relative;
+ display: block;
+ width: 100%;
+}
+
+.object_type_table tbody {
+ display: block;
+ overflow: scroll;
+ border: 1px solid #ddd;
+ width: 100%;
+ min-height: 100%;
+ max-height: 66%;
+ height: 66%;
+}
+
+.object_type_table tbody tr {
+ display: table;
+ max-width: 100%;
+ width: 100%;
+}
+
+.object_type_table tbody tr td {
+ background-position: 4px 4px;
+ border-radius: 0;
+}
+
+.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) {
+ width: 161px;
+ min-width: 161px;
+ max-width: 161px;
+}
+
+.object_type_table thead tr th:nth-child(2) {
+ width: 161px;
+ min-width: 161px;
+ max-width: 161px;
+}
+
+.object_type_table tbody tr td:nth-child(3) {
+ width: 109px;
+ min-width: 109px;
+ max-width: 109px;
+}
+
+.object_type_table thead tr th:nth-child(3) {
+ width: 109px;
+ min-width: 109px;
+ max-width: 109px;
+}
+
+.object_type_table thead tr th:nth-child(4) {
+ width: 100%;
+}
+
+.object_type_table tbody tr td:nth-child(4) {
+ width: 100%;
+ max-width: 100%;
+}
+
+/** Override Backgrid filter CSS **/
+.db_objects_container .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..2acb162
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/js/grant_wizard.js
@@ -0,0 +1,1101 @@
+define([
+ 'jquery', 'underscore', 'underscore.string', 'alertify',
+ 'pgadmin.browser', 'backbone', 'backgrid', 'pgadmin.browser.node',
+ 'backgrid.select.all', 'backgrid.filter', 'pgadmin.browser.server.privilege',
+ 'pgadmin.browser.wizard',
+ ],
+
+ // This defines Grant Wizard dialog
+ function($, _, S, alertify, pgBrowser, Backbone, Backgrid, pgNode) {
+
+ // if module is already initialized, refer to that.
+ 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,
+ name_with_args: undefined,
+ nspname: undefined,
+ proargs: undefined,
+ object_type: undefined,
+ object_id: undefined
+ },
+ idAttribute: 'object_id', // to uniquely identify a model object
+ toJSON: function(obj) {
+ var d = pgNode.Model.prototype.toJSON.apply(this);
+ delete d.icon;
+ return d;
+ },
+ parse: function(res) {
+
+ // Create unique object id
+ res.object_id = res.name_with_args;
+
+ // create name with args if its object is function
+ if(!_.isUndefined(res.object_type) && (res.object_type == 'Function' ||
+ res.object_type == 'Trigger Function'))
+ res.name_with_args = res.name+'('+(typeof(res.proargs) != 'undefined' ? res.proargs : '')+')';
+ else
+ res.name_with_args = 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 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": "24px"});
+
+ return this;
+ }
+ })
+ },{
+ name: "nspname",
+ label: "Schema",
+ cell: "string",
+ editable: false
+ },{
+ name: "name_with_args",
+ 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 context menu option appears
+ var supported_nodes = [
+ 'schema', 'coll-function', 'coll-sequence',
+ 'coll-table', 'coll-view',
+ 'coll-materialized_view', 'database'
+ ],
+
+ /**
+ Enable/disable grantwizard menu in tools based
+ on node selected
+ if selected node is present in supported_nodes,
+ menu will be enabled otherwise disabled.
+ Also, hide it for system view in catalogs
+ */
+ menu_enabled = function(itemData, item, data) {
+ var t = pgBrowser.tree, i = item, d = itemData;
+ var parent_item = t.hasParent(i) ? t.parent(i): null,
+ parent_data = parent_item ? t.itemData(parent_item) : null;
+ if(!_.isUndefined(d) && !_.isNull(d) && !_.isNull(parent_data))
+ return ((_.indexOf(supported_nodes, d._type) !== -1 && parent_data._type != 'catalog') ? 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) {
+
+ // Declare Wizard dialog
+ if (!alertify.wizardDialog) {
+ alertify.dialog('wizardDialog', function factory() {
+
+ // Generate wizard main container
+ var $container = $("<div class='wizard_dlg'></div>");
+
+ return {
+ main: function(title) {
+ this.set('title', title);
+ },
+ setup:function() {
+ return {
+
+ // Set options for dialog
+ options: {
+ frameless: true,
+ resizable: true,
+ autoReset: false,
+ maximizable: false,
+ closableByDimmer: false
+ }
+ };
+ },
+ hooks:{
+ onshow: function() {
+
+ // Set dimensions for wizard
+ this.elements.dialog.style.width = '100%';
+ this.elements.dialog.style.height = '68%';
+
+ // Add pgadmin_grant_wizard_body class to dialog
+ $(this.elements.body).addClass('pgadmin_grant_wizard_body');
+ },
+ },
+
+ /**
+ 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 PrivilegePage
+ 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;
+ }
+
+ // clear object priv array
+ if(!_.isNull(self.obj_priv) &&
+ !_.isUndefined(self.obj_priv)) {
+ self.obj_priv = [];
+ delete self.obj_priv;
+ }
+
+ // Delete Wizard Pages, clear model and cleanup view
+ 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.get('view').cleanup();
+ 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.get('view').cleanup();
+ 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 Sql control
+ if (!_.isUndefined(self.sqlControl)) {
+ self.sqlControl.remove();
+ }
+
+ // Clear privModel
+ if(!_.isNull(self.privModel) &&
+ !_.isUndefined(self.privModel)) {
+ self.privModel.clear();
+ }
+
+ // 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;
+ }
+
+ },
+
+ /**
+ 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}
+
+ */
+ build: function() {
+ this.elements.content.appendChild($container.get(0));
+ },
+
+ //Returns list of Acls defined for nodes
+ get_json_data: function(gid, sid, did) {
+ var url = "{{ url_for('grant_wizard.index') }}" + "acl/" +
+ S('%s/%s/%s/').sprintf(
+ encodeURI(gid), encodeURI(sid), encodeURI(did)).value();
+ return $.ajax({
+ async: false,
+ url: url,
+ dataType: 'jsonp'
+ });
+
+ },
+ prepare:function() {
+
+ $container.empty().append("<div class='grant_wizard_container'></div>");
+
+ // Define el for wizard view
+ var el = $('.grant_wizard_container');
+
+ // Extract the data from the selected tree node
+ var t = pgBrowser.tree,
+ i = t.selected(),
+ d = this.d = i && i.length == 1 ? t.itemData(i) : undefined,
+ info = this.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,
+
+ /**
+ get node name only. used in mapping with object types defined
+ in allowed_acl.json
+ */
+ node_type = d._type.replace('coll-', '').replace('materialized_', ''),
+ node_label = d.label;
+
+ // Fetch privileges specific to nodes
+ var json_data = this.get_json_data(gid, sid, did);
+ var privDict = JSON.parse(json_data.responseText);
+
+ // Collection url to fetch database object types for objects field
+ var baseUrl = "{{ url_for('grant_wizard.index') }}" + "properties/" +
+ S('%s/%s/%s/%s/%s/').sprintf(
+ encodeURI(gid), encodeURI(sid), encodeURI(did),
+ encodeURI(node_id), encodeURI(node_type)).value();
+
+ // Model's save url
+ saveUrl = "{{ url_for('grant_wizard.index') }}" + "save/" +
+ S('%s/%s/%s/').sprintf(
+ encodeURI(gid), encodeURI(sid),
+ encodeURI(did)).value(),
+
+ // generate encoded url based on wizard type
+ msql_url = this.msql_url = "/grant_wizard/msql/"+
+ S('%s/%s/%s/').sprintf(
+ encodeURI(gid), encodeURI(sid),
+ encodeURI(did)).value(),
+
+ Coll = Backbone.Collection.extend({
+ model: DatabaseObjectModel,
+ url: baseUrl
+ }),
+
+ // Create instances of collection and filter
+ coll = this.coll = new Coll(),
+
+ coll.comparator = function(model) {
+ return model.get('object_type');
+ }
+
+ coll.sort();
+ dbObjectFilter = this.dbObjectFilter = this.DbObjectFilter(coll);
+
+ /**
+ privArray holds objects selected which further helps
+ in creating privileges Model
+ */
+ var self = this;
+ self.privArray = [];
+
+ /**
+ Override backgrid listener "backgrid:selected" to
+ Add/Remove model to/from objects collection
+ */
+ coll.on('backgrid:selected', function(model, selected) {
+ model.set('selected', selected);
+
+ var object_type = model.get('object_type');
+ switch (object_type)
+ {
+ case 'Function':
+ object_type = 'function';
+ break;
+ case 'Trigger Function':
+ object_type = 'function';
+ break;
+ case 'Table':
+ object_type = 'table';
+ break;
+ case 'Sequence':
+ object_type = 'sequence';
+ break;
+ case 'View':
+ object_type = 'table';
+ break;
+ }
+
+ /**
+ if a row (checkbox) is checked, add that model
+ into collection, when unchecked remove it from
+ model.
+
+ Also push/pop object type in/from privArray
+ */
+ if(selected) {
+ if(_.indexOf(self.privArray, object_type) == -1)
+ self.privArray.push(object_type);
+ newModel.get('objects').add(model, { silent: true });
+ }
+ else {
+ var idx = self.privArray.indexOf(object_type);
+ if(idx !=-1)
+ self.privArray.splice(idx, 1);
+ 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);
+ });
+
+ /**
+ 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") }}',
+ model: pgAdmin.Browser.Node.PrivilegeRoleModel,
+ type: 'collection', canAdd: true,
+ canDelete: true, control: 'unique-col-collection'
+ }
+ ],
+ urlRoot: saveUrl
+ });
+
+ /**
+ Create instance of GrantWizard Model, provide urlRoot
+ node_info object, Generate fields objects
+ */
+ var newModel = new GrantWizardModel({}, { node_info: info });
+
+ /**
+ 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);
+
+ //////////////////////////////////////////////////////////////////////
+ // //
+ // 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 list below.'),
+ 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="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);
+
+ // 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 //
+ // //
+ //////////////////////////////////////////////////////////////////////
+
+ // 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() {
+
+ var obj_priv = [];
+ self.privArray = _.uniq(self.privArray);
+ _.each(self.privArray, function(priv){
+ self.obj_priv = obj_priv = _.union(obj_priv , privDict[priv].acl);
+ });
+
+ /**
+ Define PrivModel and its instance.
+ Privileges array is generated based on
+ the type of nodes selected.
+ */
+ var privModel = self.privModel;
+ var PrivModel = pgNode.Model.extend({
+ defaults: {
+ acl: undefined
+ },
+ schema: [
+ {
+ id: 'acl', label: '{{ _("Privileges") }}',
+ model: pgAdmin.Browser.Node.PrivilegeRoleModel.extend({
+
+ // privileges are selected based on node clicked
+ privileges: obj_priv
+ }), uniqueCol : ['grantee', 'grantor'], editable: true,
+ type: 'collection', canAdd: true,
+ canDelete: true, control: 'unique-col-collection'
+ }
+ ]
+ });
+
+ /**
+ When privelege control is re-rendered, in order to
+ render privileges based on object type selected,
+ delete privileges from privModel which are now not
+ present in object privileges array(object_priv)
+ */
+ var data = {};
+ if (privModel) {
+ data = privModel.toJSON();
+ var rolePrivs = data['acl'];
+
+ for (var idx in rolePrivs) {
+ var rolePriv = (rolePrivs[idx])['privileges'],
+ removeIdx = [], j;
+
+ for (j in rolePriv) {
+ var p = rolePriv[j];
+ if (_.indexOf(obj_priv, p['privilege_type']) == -1) {
+ removeIdx.push(j);
+ }
+ }
+
+ for (j in removeIdx) {
+ rolePriv.splice(j, 1);
+ }
+ }
+ }
+
+ // Instantiate privModel
+ privModel = self.privModel = new PrivModel(data, { node_info: self.info });
+
+ /*
+ To track changes into model, start new session
+ and Add event listener for privileges control
+ */
+ self.privModel.startNewSession();
+ self.privModel.on('pgadmin-session:valid', self.onSessionValidated.bind(self));
+ self.privModel.on('pgadmin-session:invalid', self.onSessionInvalid.bind(self));
+
+ /**
+ Create Field Object which has properties like
+ node_data, node_info which is required for rendering
+ Privilege control
+ */
+ var fields = Backform.generateViewSchema(
+ self.info, self.privModel, 'create', self.d._type, self.d
+ );
+ var privilegesField = new Backform.Field(fields[0].fields[0]);
+
+ this.privControl = new (privilegesField.get('control')) ({
+ field: privilegesField,
+ model: self.privModel
+ });
+
+ 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 DbObjectType page 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 Filter
+ */
+ newModel.trigger("backgrid:refresh", newModel, false);
+ self.clientSideFilter.render();
+ return true;
+ },
+
+ beforeNext: function() { return true; },
+
+ onNext: function(obj){
+
+ // Assign acls of privModel to main model newModel
+ if (!_.isUndefined(self.privModel)) {
+ newModel.set({'acl': self.privModel.get('acl')});
+ }
+
+ // 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'),
+
+ /**
+ 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() {
+
+ // Clear html dom elements of CodeMirror sql tab
+ self.sqlControl.unbind(); // Unbind all local event bindings
+ var cmElem = self.sqlControl.sqlTab.getWrapperElement();
+ cmElem.remove();
+ self.sqlControl.sqlTab = undefined;
+ }
+
+ })
+ }),
+
+ /**
+ Create sqlField view instance
+ to render it into wizard page
+ */
+ sqlControl = self.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: _('The SQL below will be executed on the ' +
+ 'database server to grant the selected privileges. ' +
+ 'Please click on <b>Finish</b> 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
+ */
+ 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
+ },
+
+ // Callback for finish button
+ onFinish: function() {
+ var m = newModel,
+ d = m.toJSON('GET');
+
+ // Save model
+ 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()
+ );
+
+ // Release wizard objects
+ self.releaseObjects();
+ self.close();
+ }
+ });
+ }
+ },
+
+ // Callback for cancel button
+ onCancel: function() {
+
+ // Release wizard objects
+ self.releaseObjects();
+ self.close();
+ }
+ })) ({
+ collection: this.wizardCollection,
+ el: el,
+ model: newModel
+ });
+
+ // Render wizard
+ self.wizard.render();
+ }
+ };
+ });
+ }
+
+ // Call Grant Wizard Dialog
+ alertify.wizardDialog(true);
+ }
+ };
+
+ return pgBrowser.GrantWizard;
+ });
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/acl.json b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/acl.json
new file mode 100644
index 0000000..2d21f14
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/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"]
+ },
+ "table": {
+ "type": "TABLE",
+ "acl": ["a", "w", "d", "D", "x", "t"]
+ },
+ "view": {
+ "type": "VIEW",
+ "acl": ["a", "w", "d", "D", "x", "t"]
+ },
+ "sequence": {
+ "type": "SEQUENCE",
+ "acl": ["w", "U"]
+ },
+ "function": {
+ "type": "FUNCTION",
+ "acl": ["X"]
+ }
+}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/function.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/function.sql
new file mode 100644
index 0000000..7fad225
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/function.sql
@@ -0,0 +1,23 @@
+{# ===== Fetch list of Database object types(Functions) ====== #}
+{% if type and node_id and nspname %}
+{% set func_type = 'Trigger Function' if type == 'trigger_function' else 'Function' %}
+SELECT
+ pr.oid,
+ pg_get_function_identity_arguments(pr.oid) AS proargs,
+ {# pr.proname || '(' || pg_get_function_identity_arguments(pr.oid) || ')' AS name,#}
+ pr.proname AS name,
+ '{{ nspname }}' AS nspname,
+ '{{ func_type }}' AS object_type,
+ '{{ "icon-function" if type != "trigger_function" else "icon-trigger_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 %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/get_schemas.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/get_schemas.sql
new file mode 100644
index 0000000..4f6fd5a
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/get_schemas.sql
@@ -0,0 +1,19 @@
+{# ===== Fetch list of all schemas ===== #}
+{% import 'catalog/pg/macros/catalogs.sql' as CATALOGS %}
+SELECT
+ nsp.oid,
+ nsp.nspname as name
+FROM
+ pg_namespace nsp
+WHERE
+ {% if nspid %}
+ nsp.oid={{nspid}}::int AND
+ {% else %}
+ {% if not show_sysobj %}
+ nspname NOT LIKE E'pg\_%' AND
+ {% endif %}
+ {% endif %}
+ NOT (
+{{ CATALOGS.LIST('nsp') }}
+ )
+ORDER BY nspname;
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_function.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_function.sql
new file mode 100644
index 0000000..aea68af
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_function.sql
@@ -0,0 +1,10 @@
+{# ===== Grant Permissions on Database Objects Selected ==== #}
+{% import 'macros/functions/privilege.macros' as PRIVILEGE_FUNCTION %}
+{% for obj in data.objects -%}
+{% for priv in data.priv -%}
+{# ===== if object_type is Function then apply function marcros ===== #}
+{% if (obj.object_type == 'Function' or obj.object_type == 'Trigger Function') %}
+{{ PRIVILEGE_FUNCTION.SET(conn, 'FUNCTION', priv['grantee'], obj.name, priv['without_grant'], priv['with_grant'], obj.nspname, obj.proargs)}}
+{% endif -%}
+{% endfor -%}
+{% endfor -%}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_sequence.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_sequence.sql
new file mode 100644
index 0000000..8e25d17
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_sequence.sql
@@ -0,0 +1,9 @@
+{# ===== Grant Permissions on Database Objects Selected ==== #}
+{% import 'macros/schemas/privilege.macros' as PRIVILEGE %}
+{% for obj in data.objects -%}
+{% for priv in data.priv -%}
+{% if obj.object_type == 'Sequence' %}
+{{ PRIVILEGE.SET(conn, 'SEQUENCE', priv['grantee'], obj.name, priv['without_grant'], priv['with_grant'], obj.nspname ) }}
+{% endif %}
+{% endfor -%}
+{% endfor -%}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_table.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_table.sql
new file mode 100644
index 0000000..c74b86e
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/grant_table.sql
@@ -0,0 +1,9 @@
+{# ===== Grant Permissions on Database Objects Selected ==== #}
+{% import 'macros/schemas/privilege.macros' as PRIVILEGE %}
+{% for obj in data.objects -%}
+{% for priv in data.priv -%}
+{% if obj.object_type == 'Table' or obj.object_type == 'View' %}
+{{ PRIVILEGE.SET(conn, 'TABLE', priv['grantee'], obj.name, priv['without_grant'], priv['with_grant'], obj.nspname ) }}
+{% endif %}
+{% endfor -%}
+{% endfor -%}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/sequence.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/sequence.sql
new file mode 100644
index 0000000..2950df1
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/sequence.sql
@@ -0,0 +1,15 @@
+{# ===== Fetch list of Database object types(Sequence) ===== #}
+{% if node_id, nspname %}
+SELECT
+ cl.relname AS name,
+ 'Sequence' AS object_type,
+ 'icon-sequence' AS icon,
+ '{{ nspname }}' AS nspname
+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 %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/table.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/table.sql
new file mode 100644
index 0000000..5f67c49
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/table.sql
@@ -0,0 +1,19 @@
+{# ===== Fetch list of Database object types(Tables) ===== #}
+{% if node_id and nspname %}
+SELECT
+ rel.relname AS name,
+ 'Table' AS object_type,
+ 'icon-table' AS icon,
+ '{{ nspname }}' AS nspname
+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 %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/view.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/view.sql
new file mode 100644
index 0000000..fab77cc
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/pg/9.1_plus/sql/view.sql
@@ -0,0 +1,28 @@
+{# ===== Fetch list of Database object types(View) ===== #}
+{% if node_id %}
+SELECT
+ c.relname AS name,
+ 'View' AS object_type,
+ 'icon-view' AS icon,
+ '{{ nspname }}' AS nspname
+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 %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/acl.json b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/acl.json
new file mode 100644
index 0000000..2d21f14
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/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"]
+ },
+ "table": {
+ "type": "TABLE",
+ "acl": ["a", "w", "d", "D", "x", "t"]
+ },
+ "view": {
+ "type": "VIEW",
+ "acl": ["a", "w", "d", "D", "x", "t"]
+ },
+ "sequence": {
+ "type": "SEQUENCE",
+ "acl": ["w", "U"]
+ },
+ "function": {
+ "type": "FUNCTION",
+ "acl": ["X"]
+ }
+}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/function.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/function.sql
new file mode 100644
index 0000000..7fad225
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/function.sql
@@ -0,0 +1,23 @@
+{# ===== Fetch list of Database object types(Functions) ====== #}
+{% if type and node_id and nspname %}
+{% set func_type = 'Trigger Function' if type == 'trigger_function' else 'Function' %}
+SELECT
+ pr.oid,
+ pg_get_function_identity_arguments(pr.oid) AS proargs,
+ {# pr.proname || '(' || pg_get_function_identity_arguments(pr.oid) || ')' AS name,#}
+ pr.proname AS name,
+ '{{ nspname }}' AS nspname,
+ '{{ func_type }}' AS object_type,
+ '{{ "icon-function" if type != "trigger_function" else "icon-trigger_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 %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/get_schemas.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/get_schemas.sql
new file mode 100644
index 0000000..4f6fd5a
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/get_schemas.sql
@@ -0,0 +1,19 @@
+{# ===== Fetch list of all schemas ===== #}
+{% import 'catalog/pg/macros/catalogs.sql' as CATALOGS %}
+SELECT
+ nsp.oid,
+ nsp.nspname as name
+FROM
+ pg_namespace nsp
+WHERE
+ {% if nspid %}
+ nsp.oid={{nspid}}::int AND
+ {% else %}
+ {% if not show_sysobj %}
+ nspname NOT LIKE E'pg\_%' AND
+ {% endif %}
+ {% endif %}
+ NOT (
+{{ CATALOGS.LIST('nsp') }}
+ )
+ORDER BY nspname;
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_function.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_function.sql
new file mode 100644
index 0000000..aea68af
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_function.sql
@@ -0,0 +1,10 @@
+{# ===== Grant Permissions on Database Objects Selected ==== #}
+{% import 'macros/functions/privilege.macros' as PRIVILEGE_FUNCTION %}
+{% for obj in data.objects -%}
+{% for priv in data.priv -%}
+{# ===== if object_type is Function then apply function marcros ===== #}
+{% if (obj.object_type == 'Function' or obj.object_type == 'Trigger Function') %}
+{{ PRIVILEGE_FUNCTION.SET(conn, 'FUNCTION', priv['grantee'], obj.name, priv['without_grant'], priv['with_grant'], obj.nspname, obj.proargs)}}
+{% endif -%}
+{% endfor -%}
+{% endfor -%}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_sequence.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_sequence.sql
new file mode 100644
index 0000000..8e25d17
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_sequence.sql
@@ -0,0 +1,9 @@
+{# ===== Grant Permissions on Database Objects Selected ==== #}
+{% import 'macros/schemas/privilege.macros' as PRIVILEGE %}
+{% for obj in data.objects -%}
+{% for priv in data.priv -%}
+{% if obj.object_type == 'Sequence' %}
+{{ PRIVILEGE.SET(conn, 'SEQUENCE', priv['grantee'], obj.name, priv['without_grant'], priv['with_grant'], obj.nspname ) }}
+{% endif %}
+{% endfor -%}
+{% endfor -%}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_table.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_table.sql
new file mode 100644
index 0000000..c74b86e
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/grant_table.sql
@@ -0,0 +1,9 @@
+{# ===== Grant Permissions on Database Objects Selected ==== #}
+{% import 'macros/schemas/privilege.macros' as PRIVILEGE %}
+{% for obj in data.objects -%}
+{% for priv in data.priv -%}
+{% if obj.object_type == 'Table' or obj.object_type == 'View' %}
+{{ PRIVILEGE.SET(conn, 'TABLE', priv['grantee'], obj.name, priv['without_grant'], priv['with_grant'], obj.nspname ) }}
+{% endif %}
+{% endfor -%}
+{% endfor -%}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/sequence.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/sequence.sql
new file mode 100644
index 0000000..2950df1
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/sequence.sql
@@ -0,0 +1,15 @@
+{# ===== Fetch list of Database object types(Sequence) ===== #}
+{% if node_id, nspname %}
+SELECT
+ cl.relname AS name,
+ 'Sequence' AS object_type,
+ 'icon-sequence' AS icon,
+ '{{ nspname }}' AS nspname
+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 %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/table.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/table.sql
new file mode 100644
index 0000000..5f67c49
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/table.sql
@@ -0,0 +1,19 @@
+{# ===== Fetch list of Database object types(Tables) ===== #}
+{% if node_id and nspname %}
+SELECT
+ rel.relname AS name,
+ 'Table' AS object_type,
+ 'icon-table' AS icon,
+ '{{ nspname }}' AS nspname
+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 %}
diff --git a/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/view.sql b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/view.sql
new file mode 100644
index 0000000..fab77cc
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/templates/grant_wizard/ppas/9.1_plus/sql/view.sql
@@ -0,0 +1,28 @@
+{# ===== Fetch list of Database object types(View) ===== #}
+{% if node_id %}
+SELECT
+ c.relname AS name,
+ 'View' AS object_type,
+ 'icon-view' AS icon,
+ '{{ nspname }}' AS nspname
+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], [email protected]
Subject: Re: [pgAdmin4] [Patch]: Grant Wizard
In-Reply-To: <CAM5-9D_TmnFfVEw1OXTL-f4jgtCiE+e2rQuSKa4yqPX7ZsSBNg@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