',
+ 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 =
+ $('
');
+
+ // 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 %}