diff --git a/web/package.json b/web/package.json
index 9ec32d45..df303b03 100644
--- a/web/package.json
+++ b/web/package.json
@@ -126,6 +126,7 @@
"react-dom": "^17.0.1",
"react-select": "^4.2.1",
"react-table": "^7.6.3",
+ "react-window": "^1.8.5",
"select2": "^4.0.13",
"shim-loader": "^1.0.1",
"slickgrid": "git+https://github.com/6pac/SlickGrid.git#2.3.16",
diff --git a/web/pgadmin/browser/static/css/wizard.css b/web/pgadmin/browser/static/css/wizard.css
index 7ed0f34e..a671546c 100644
--- a/web/pgadmin/browser/static/css/wizard.css
+++ b/web/pgadmin/browser/static/css/wizard.css
@@ -83,3 +83,18 @@
margin-top: 20px !important;
margin-bottom: 10px !important;
}
+
+.wizard {
+ width: 100%;
+ /*height: 550px;*/
+}
+
+.step {
+ height: inherit;
+ width: initial;
+}
+
+.wizard-footer {
+ border-top: 1px solid #dde0e6 !important;
+ padding: 0.5rem;
+}
diff --git a/web/pgadmin/browser/static/js/PgTable.jsx b/web/pgadmin/browser/static/js/PgTable.jsx
new file mode 100644
index 00000000..6c45c58a
--- /dev/null
+++ b/web/pgadmin/browser/static/js/PgTable.jsx
@@ -0,0 +1,272 @@
+/* eslint-disable react/display-name */
+/* eslint-disable react/prop-types */
+
+import React from 'react';
+import { useTable, useBlockLayout, useRowSelect, useSortBy, useResizeColumns, useFlexLayout } from 'react-table';
+import { FixedSizeList } from 'react-window';
+import { makeStyles } from '@material-ui/core/styles';
+import clsx from 'clsx';
+import PropTypes from 'prop-types';
+
+const useStyles = makeStyles((theme) => ({
+ root: {
+ display: 'flex',
+ flexDirection: 'column',
+ height: '100%',
+ ...theme.mixins.panelBorder,
+ backgroundColor: theme.palette.background.default,
+ },
+ table: {
+ borderSpacing: 0,
+ width: '100%',
+ overflow: 'hidden',
+ backgroundColor: theme.otherVars.tableBg,
+ ...theme.mixins.panelBorder,
+ //backgroundColor: theme.palette.background.default,
+ },
+
+ tableCell: {
+ margin: 0,
+ padding: theme.spacing(0.5),
+ ...theme.mixins.panelBorder.bottom,
+ ...theme.mixins.panelBorder.right,
+ position: 'relative',
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ },
+ selectCell: {
+ textAlign: 'center'
+ },
+ tableCellHeader: {
+ fontWeight: theme.typography.fontWeightBold,
+ padding: theme.spacing(1, 0.5),
+ textAlign: 'left',
+ overflowY: 'auto',
+ overflowX: 'hidden',
+ alignContent: 'center',
+ ...theme.mixins.panelBorder.bottom,
+ ...theme.mixins.panelBorder.right,
+ },
+ resizer: {
+ display: 'inline-block',
+ width: '5px',
+ height: '100%',
+ position: 'absolute',
+ right: 0,
+ top: 0,
+ transform: 'translateX(50%)',
+ zIndex: 1,
+ touchAction: 'none',
+ },
+ cellIcon: {
+ paddingLeft: '1.5em',
+ height: 35
+ }
+}),
+);
+
+export default function pgTable({ columns, data, enableSelect, ...props }) {
+ // Use the state and functions returned from useTable to build your UI
+ const classes = useStyles();
+ const defaultColumn = React.useMemo(
+ () => ({
+ minWidth: 175,
+ }),
+ []
+ );
+
+ const scrollBarSize = React.useMemo(() => scrollbarWidth(), []);
+
+ const IndeterminateCheckbox = React.forwardRef(
+ ({ indeterminate, ...rest }, ref) => {
+ const defaultRef = React.useRef();
+ const resolvedRef = ref || defaultRef;
+
+ React.useImperativeHandle(ref, () => ({
+ getSelectedRows: () => selectedRowIds
+ }), [selectedRowIds]);
+
+
+ React.useEffect(() => {
+ resolvedRef.current.indeterminate = indeterminate;
+ }, [resolvedRef, indeterminate]);
+ return (
+ <>
+
+ >
+ );
+ },
+ );
+
+ IndeterminateCheckbox.displayName = 'SelectCheckbox';
+
+ IndeterminateCheckbox.propTypes = {
+ indeterminate: PropTypes.bool,
+ rest: PropTypes.func,
+ getToggleAllRowsSelectedProps: PropTypes.func,
+ row: PropTypes.object,
+ };
+
+ const {
+ getTableProps,
+ getTableBodyProps,
+ headerGroups,
+ rows,
+ totalColumnsWidth,
+ prepareRow,
+ selectedFlatRows,
+ state: { selectedRowIds }
+ } = useTable(
+ {
+ columns,
+ data,
+ defaultColumn,
+ enableSelect,
+ },
+ useBlockLayout,
+ useSortBy,
+ useRowSelect,
+ useResizeColumns,
+ useFlexLayout,
+ hooks => {
+ hooks.visibleColumns.push(CLOUMNS => {
+ if (enableSelect) {
+ return [
+ // Let's make a column for selection
+ {
+ id: 'selection',
+ // The header can use the table's getToggleAllRowsSelectedProps method
+ // to render a checkbox
+ Header: ({ getToggleAllRowsSelectedProps }) => (
+
+
+
+ ),
+ // The cell can use the individual row's getToggleRowSelectedProps method
+ // to the render a checkbox
+ Cell: ({ row }) => (
+
+
+
+ ),
+ sortble: false,
+ width: 50,
+ minWidth: 0,
+ },
+ ...CLOUMNS,
+ ];
+ } else {
+ return [...CLOUMNS];
+ }
+ });
+ hooks.useInstanceBeforeDimensions.push(({ headerGroups }) => {
+ // fix the parent group of the selection button to not be resizable
+ const selectionGroupHeader = headerGroups[0].headers[0];
+ selectionGroupHeader.resizable = false;
+ });
+ }
+ );
+
+ React.useEffect(() => {
+ if (props.setSelectedRows) {
+ props.setSelectedRows(selectedFlatRows);
+ }
+ }, [selectedRowIds]);
+
+ React.useEffect(() => {
+ if (props.getSelectedRows) {
+ props.getSelectedRows(selectedFlatRows);
+ }
+ }, [selectedRowIds]);
+
+
+ const RenderRow = React.useCallback(
+ ({ index, style }) => {
+ const row = rows[index];
+ prepareRow(row);
+ return (
+
+ {row.cells.map((cell) => {
+ return (
+
+ {cell.render('Cell')}
+
+ );
+ })}
+
+ );
+ },
+ [prepareRow, rows, selectedRowIds]
+ );
+ // Render the UI for your table
+ return (
+
+
+ {headerGroups.map(headerGroup => (
+
+ {headerGroup.headers.map(column => (
+
+
+ {column.render('Header')}
+
+ {column.isSorted
+ ? column.isSortedDesc
+ ? ' 🔽'
+ : ' 🔼'
+ : ''}
+
+ {column.resizable &&
+
}
+
+
+ ))}
+
+ ))}
+
+
+
+ 385 ? totalColumnsWidth + scrollBarSize : totalColumnsWidth}
+ sorted={props?.sortOptions}
+ >
+ {RenderRow}
+
+
+
+
+
+
+ );
+}
+
+const scrollbarWidth = () => {
+ // thanks too https://davidwalsh.name/detect-scrollbar-width
+ const scrollDiv = document.createElement('div');
+ scrollDiv.setAttribute('style', 'width: 100px; height: 100px; overflow: scroll; position:absolute; top:-9999px;');
+ document.body.appendChild(scrollDiv);
+ const scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
+ document.body.removeChild(scrollDiv);
+ return scrollbarWidth;
+};
+
+pgTable.propTypes = {
+ stepId: PropTypes.number,
+ height: PropTypes.number,
+ className: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
+ children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]),
+ getToggleAllRowsSelectedProps: PropTypes.func,
+};
+
+
diff --git a/web/pgadmin/browser/static/js/WizardStep.jsx b/web/pgadmin/browser/static/js/WizardStep.jsx
new file mode 100644
index 00000000..8d842a6e
--- /dev/null
+++ b/web/pgadmin/browser/static/js/WizardStep.jsx
@@ -0,0 +1,44 @@
+import React from 'react';
+import { makeStyles } from '@material-ui/core/styles';
+import { Box } from '@material-ui/core';
+import clsx from 'clsx';
+import PropTypes from 'prop-types';
+const useStyles = makeStyles(() =>
+ ({
+ stepPanel: {
+ height: '100%',
+ width: '100%',
+ // paddingLeft: '2em',
+ minHeight: '100px',
+ // paddingTop: '1em',
+ paddingBottom: '1em',
+ paddingRight: '1em',
+ overflow: 'auto',
+ }
+ }));
+
+export default function WizardStep({stepId, className, ...props }) {
+ const classes = useStyles();
+
+ return (
+
+
+ {
+ React.Children.map(props.children, (child) => {
+ return (
+ <>
+ {child}
+ >
+ );
+ })
+ }
+
+ );
+}
+
+WizardStep.propTypes = {
+ stepId: PropTypes.number,
+ height: PropTypes.number,
+ className: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
+ children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]),
+};
diff --git a/web/pgadmin/browser/static/js/WizardView.jsx b/web/pgadmin/browser/static/js/WizardView.jsx
new file mode 100644
index 00000000..85653ccf
--- /dev/null
+++ b/web/pgadmin/browser/static/js/WizardView.jsx
@@ -0,0 +1,189 @@
+import React from 'react';
+import { makeStyles } from '@material-ui/core/styles';
+import clsx from 'clsx';
+import FastForwardIcon from '@material-ui/icons/FastForward';
+import FastRewindIcon from '@material-ui/icons/FastRewind';
+import ChevronRightIcon from '@material-ui/icons/ChevronRight';
+import CheckIcon from '@material-ui/icons/Check';
+import { DefaultButton, PrimaryButton } from '../../../static/js/components/Buttons';
+import PropTypes from 'prop-types';
+import { Box } from '@material-ui/core';
+import gettext from 'sources/gettext';
+
+
+const useStyles = makeStyles((theme) =>
+ ({
+ root: {
+ display: 'flex',
+ flexDirection: 'column',
+ height: '100%',
+ },
+ rightPanel: {
+ position: 'relative',
+ minHeight: 100,
+ display: 'flex',
+ paddingLeft: '1.5em',
+ paddingTop: '0em',
+ flex: 5,
+ overflow: 'auto',
+ height: '100%'
+ },
+ leftPanel: {
+ display: 'flex',
+ // padding: '2em',
+ flexDirection: 'column',
+ alignItems: 'flex-start',
+ borderRight: '1px solid',
+ ...theme.mixins.panelBorder.right,
+ flex: 1.5
+ },
+ label: {
+ display: 'inline-block',
+ position: 'relative',
+ top: '20%'
+ },
+ labelArrow: {
+ display: 'inline-block',
+ position: 'relative',
+ top: '20%'
+ // verticalAlign: 'bottom'
+ },
+ stepLabel: {
+ padding: '1em',
+ display: 'inline-flex'
+ },
+ active: {
+ fontWeight: 600
+ },
+ activeIndex: {
+ backgroundColor: '#326690 !important',
+ color: '#fff'
+ },
+ stepIndex: {
+ padding: '0.5em 1em ',
+ width: '2.5em',
+ height: '2.5em',
+ borderRadius: '2em',
+ marginRight: '0.5em',
+ backgroundColor: '#ddd',
+ display: 'inline-block'
+ },
+ wizard: {
+ width: '100%',
+ height: '100%',
+ minHeight: 100,
+ display: 'flex',
+ flexWrap: 'wrap',
+ },
+ wizardFooter: {
+ borderTop: '1px solid #dde0e6 !important',
+ padding: '0.5rem',
+ display: 'flex',
+ flexDirection: 'row-reverse',
+ flex: 1
+ },
+ backButton: {
+
+ marginRight: theme.spacing(1),
+ },
+ instructions: {
+ marginTop: theme.spacing(1),
+ marginBottom: theme.spacing(1),
+ },
+ actionBtn: {
+ alignItems: 'flex-start',
+ },
+ buttonMargin: {
+ marginLeft: '0.5em'
+ },
+ stepDefaultStyle: {
+ width: '100%',
+ height: '100%'
+ }
+
+ }),
+);
+
+function Wizard({ stepList, onStepChange, onSave, className, ...props }) {
+ const classes = useStyles();
+ const [activeStep, setActiveStep] = React.useState(0);
+ const steps = stepList && stepList.length > 0 ? stepList : [];
+ const [disableNext, setdisableNext] = React.useState(false);
+
+
+ const handleNext = () => {
+ setActiveStep((prevActiveStep) => prevActiveStep + 1);
+ };
+
+ const handleBack = () => {
+ setActiveStep((prevActiveStep) => prevActiveStep - 1 < 0 ? prevActiveStep : prevActiveStep - 1);
+ };
+
+ React.useEffect(() => {
+ if (onStepChange) {
+ onStepChange({ currentStep: activeStep });
+ }
+ }, [activeStep]);
+
+ React.useEffect(() => {
+ if (props.disableNextStep) {
+ setdisableNext(props.disableNextStep());
+ }
+ });
+
+
+ return (
+
+
+
+ {steps.map((label, index) => (
+
+ {index + 1}
+ {label}
+ {index === activeStep ? : null}
+
+ ))}
+
+
+
+ {
+ React.Children.map(props.children, (child) => {
+ return (
+
+ {child}
+
+ );
+ })
+ }
+
+
+
+
+
+ }>
+ {gettext('Back')}
+
+ handleNext()} className={classes.buttonMargin} startIcon={} disabled={activeStep == steps.length - 1 || disableNext}>
+ {gettext('Next')}
+
+ } disabled={activeStep == steps.length - 1 ? false : true} onClick={onSave}>
+ {gettext('Finish')}
+
+
+
+
+ );
+}
+
+export default Wizard;
+
+Wizard.propTypes = {
+ props: PropTypes.object,
+ stepList: PropTypes.array,
+ onSave: PropTypes.func,
+ onStepChange: PropTypes.func,
+ className: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
+ disableNextStep: PropTypes.func,
+ stepPanelCss: PropTypes.string,
+ children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]),
+};
diff --git a/web/pgadmin/browser/static/js/table.jsx b/web/pgadmin/browser/static/js/table.jsx
new file mode 100644
index 00000000..e69de29b
diff --git a/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js b/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js
index 4d19ec4b..827d0a44 100644
--- a/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js
+++ b/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js
@@ -6,6 +6,11 @@
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
+import React from 'react';
+import ReactDOM from 'react-dom';
+import Theme from 'sources/Theme';
+import GrantWizard from './grant_wizard_view';
+
// Grant Wizard
define([
@@ -28,134 +33,6 @@ define([
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() {
- 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.object_type == 'Procedure'
- ))
- 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'] = gettext('Please select any database object.');
- 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() {
-
- // Do not use parent's render function. It set's tabindex to -1 on
- // checkboxes.
-
- var col = this.column.get('name');
- let id = `row-${_.uniqueId(col)}`;
- this.$el.empty().append(`
-
-
-
-
- `);
- this.delegateEvents();
-
- if (this.model && this.model.has(col)) {
- if (this.model.get(col)) {
- this.$el.parent().toggleClass('selected', true);
- this.model.trigger('backgrid:selected', this.model, true);
- }
- }
- return this;
- },
- }),
-
- headerCell: 'select-all',
-
- }, {
- name: 'object_type',
- label: gettext('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: gettext('Schema'),
- cell: 'string',
- editable: false,
- }, {
- name: 'name_with_args',
- label: gettext('Name'),
- cell: 'string',
- editable: false,
- }];
// Create an Object GrantWizard of pgBrowser class
pgBrowser.GrantWizard = {
@@ -211,997 +88,66 @@ define([
Alertify.dialog('wizardDialog', function factory() {
// Generate wizard main container
- var $container = $('');
+ var $container = $('');
return {
- main: function(title) {
- this.set('title', title);
+ main: function () {
},
- setup: function() {
+ setup: function () {
return {
// Set options for dialog
options: {
frameless: true,
resizable: true,
autoReset: false,
- maximizable: false,
- closable: false,
+ maximizable: true,
+ closable: true,
closableByDimmer: false,
- modal: false,
+ modal: true,
pinnable: false,
+ footerless: true
},
};
},
- /**
- 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: gettext('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) {
- $('.pg-prop-status-bar .alert-text').html(msg);
- $('.pg-prop-status-bar').css('visibility', 'visible');
-
- // 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) {
- $('.pg-prop-status-bar .alert-text').empty();
- $('.pg-prop-status-bar').css('visibility', 'hidden');
-
- // 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() {
+ build: function () {
this.elements.content.appendChild($container.get(0));
Alertify.pgDialogBuild.apply(this);
- },
-
- //Returns list of Acls defined for nodes
- get_json_data: function(gid, sid, did) {
- return $.ajax({
- async: false,
- url: url_for(
- 'grant_wizard.acl', {
- 'sid': encodeURI(sid),
- 'did': encodeURI(did),
- }
- ),
- dataType: 'json',
- });
-
- },
-
- prepare: function() {
- var that = this;
- $container.empty().append('');
-
- // 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);
- if(_.isUndefined(d))
- return;
- /**
- 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_', ''
- );
-
- // Fetch privileges specific to nodes
- var json_data = this.get_json_data(gid, sid, did);
- var privDict = JSON.parse(json_data.responseText);
+ var sid = info.server._id,
+ did = info.database._id;
- // Collection url to fetch database object types for objects field
- var baseUrl = url_for(
- 'grant_wizard.objects', {
- 'sid': encodeURI(sid),
- 'did': encodeURI(did),
- 'node_id': encodeURI(node_id),
- 'node_type': encodeURI(node_type),
- }),
- // Model's save url
- saveUrl = url_for(
- 'grant_wizard.apply', {
- 'sid': encodeURI(sid),
- 'did': encodeURI(did),
- }),
- Coll = Backbone.Collection.extend({
- model: DatabaseObjectModel,
- url: baseUrl,
- }),
- // Create instances of collection and filter
- coll = this.coll = new Coll(),
- self = this;
-
- // Generate encoded url based on wizard type
- this.msql_url = url_for(
- 'grant_wizard.modified_sql', {
- 'sid': encodeURI(sid),
- 'did': encodeURI(did),
- });
-
- coll.comparator = function(model) {
- return model.get('object_type');
- };
-
- coll.sort();
- this.dbObjectFilter = this.DbObjectFilter(coll);
- coll.fullCollection = this.dbObjectFilter.shadowCollection;
-
- /**
- privArray holds objects selected which further helps
- in creating privileges Model
- */
- 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 'Procedure':
- object_type = 'procedure';
- break;
- case 'Table':
- object_type = 'table';
- break;
- case 'Sequence':
- object_type = 'sequence';
- break;
- case 'View':
- object_type = 'table';
- break;
- case 'Materialized View':
- object_type = 'table';
- break;
- case 'Foreign Table':
- object_type = 'foreign_table';
- break;
- case 'Package':
- object_type = 'package';
- break;
- default:
- break;
+ setTimeout(function () {
+ if (document.getElementById('grantWizardDlg')) {
+ ReactDOM.render(
+
+
+ ,
+ document.getElementById('grantWizardDlg'));
}
+ }, 500);
- /**
- 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: gettext('Objects'),
- model: DatabaseObjectModel,
- type: 'collection',
- group: gettext('Objects'),
- },
- {
- id: 'acl',
- label: gettext('Privileges'),
- model: pgBrowser.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();
-
- var fetchAjaxHook = function() {
- $('.wizard-progress-bar p').removeClass('alert-danger').addClass('alert-info');
- $('.wizard-progress-bar p').text(gettext('Please wait while fetching records...'));
- coll.fetch({
- success: function(c, xhr) {
- $('.wizard-progress-bar p').html('');
- $('.wizard-progress-bar').hide();
- c.set(xhr.result, {parse: true});
- // If some objects failed while fetching then we will notify the user
- if (xhr && xhr.info && xhr.info !== '') {
- $('.pg-prop-status-bar .alert-text').html(xhr.info);
- $('.pg-prop-status-bar').css('visibility', 'visible');
- }
- },
- error: function(model, xhr, options) {
- // If the main request fails as whole then
- $('.wizard-progress-bar p').removeClass('alert-info').addClass('alert-danger');
- $('.wizard-progress-bar p').text(gettext('Unable to fetch the database objects'));
-
- Alertify.pgNotifier(
- options.textStatus, xhr,
- gettext('Unable to fetch the database objects'),
- function(msg) {
- if(msg === 'CRYPTKEY_SET') {
- fetchAjaxHook();
- } else {
- $('.wizard-progress-bar p').removeClass('alert-info').addClass('alert-danger');
- $('.wizard-progress-bar p').text(msg);
- }
- }
- );
- },
- reset: true,
- }, this);
- };
- fetchAjaxHook();
-
- //////////////////////////////////////////////////////////////////////
- // //
- // 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: gettext('Object Selection (step 1 of 3)'),
- disable_prev: true,
- disable_next: true,
- show_description: '',
- show_progress_bar: gettext('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 presentation table table-bordered table-hover object_type_table',
- });
-
- // Render selection Type grid and paginator
- gridBody.find('.db_objects_grid').append(this.grid.render().$el);
-
- // Render Search Filter
- self.clientSideFilter.setCustomSearchBox(gridBody.find('#txtGridSearch'));
-
- // 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 ctx = 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 (ctx.view) {
- ctx.view.cleanup();
- delete ctx.view;
- ctx.view = null;
- }
- return true;
- },
-
- });
-
- //////////////////////////////////////////////////////////////////////
- // //
- // Wizard Page for Privilege Control //
- // //
- //////////////////////////////////////////////////////////////////////
-
- // Wizard for Privelege control
- var privilegePage = self.privilegePage = new pgBrowser.WizardPage({
- id: 2,
- page_title: gettext('Privilege Selection (step 2 of 3)'),
- show_description: gettext('Please add the required 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 = pgNode.Model.extend({
- defaults: {
- acl: undefined,
- },
- schema: [{
- id: 'acl',
- label: gettext('Privileges'),
- model: pgBrowser.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 (self.privModel) {
- data = self.privModel.toJSON();
- var rolePrivs = data['acl'];
- if (!_.isUndefined(rolePrivs) && rolePrivs.length > 0) {
- 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);
- }
- }
- } else {
- console.warn('Acls are not defined');
- }
- }
-
- // Instantiate 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() {
-
- // 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: gettext('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 ctx = this;
-
- // Fetches modified SQL
- $.ajax({
- url: this.msql_url,
- type: 'POST',
- cache: false,
- data: JSON.stringify(ctx.model.toJSON(true)),
- dataType: 'json',
- contentType: 'application/json',
- }).done(function(res) {
- ctx.sqlCtrl.clearHistory();
- ctx.sqlCtrl.setValue(res.data);
- ctx.sqlCtrl.refresh();
- }).fail(function() {
- ctx.model.trigger('pgadmin-view:msql:error');
- }).always(function() {
- ctx.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.sqlCtrl.getWrapperElement();
- $(cmElem).remove();
- self.sqlControl.sqlCtrl = 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: gettext('Final (Review Selection) (step 3 of 3)'),
- show_description: gettext('The SQL below will be executed on the ' +
- 'database server to grant the selected privileges. ' +
- 'Please click on Finish to complete the process.'),
- model: newModel,
- view: new(function() {
-
- // Render SqlTab control to generate its html markup
- var sqlCtrlHtml = sqlControl.render().$el;
- sqlCtrlHtml.addClass('h-100');
- this.render = function() {
- return {
- el: sqlCtrlHtml,
- };
- };
- }),
-
- 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: gettext('Grant Wizard'),
- /* Main Wizard Title */
- width: '',
- height: '',
- curr_page: 0,
- show_left_panel: false,
- show_header_cancel_btn: true,
- show_header_maximize_btn: true,
- disable_finish: true,
- dialog_api: that,
- wizard_help: url_for(
- 'help.static', {
- 'filename': 'grant_wizard.html',
- }
- ),
- },
-
- // Callback for finish button
- onFinish: function() {
- var m = newModel,
- grant_data = m.toJSON('GET');
-
- // Save model
- if (grant_data && !_.isEmpty(grant_data) && !_.isUndefined(grant_data.objects)) {
- m.save({}, {
- attrs: grant_data,
- validate: false,
- cache: false,
- success: function() {
-
- // Release wizard objects
- self.releaseObjects();
- self.close();
- },
- error: function(model, jqxhr) {
- Alertify.pgNotifier(
- 'error', jqxhr,
- gettext('Error saving properties')
- );
-
- // 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();
},
+ prepare: function () {
+ $container.empty().append('');
+ },
+ hooks: {
+ // Triggered when the dialog is closed
+ onclose: function () {
+ // Clear the view and remove the react component.
+ return setTimeout((function () {
+ ReactDOM.unmountComponentAtNode(document.getElementById('grantWizardDlg'));
+ return Alertify.wizardDialog().destroy();
+ }), 500);
+ },
+ }
};
});
}
// Call Grant Wizard Dialog and set dimensions for wizard
- Alertify.wizardDialog(true).resizeTo(pgBrowser.stdW.lg,pgBrowser.stdH.lg);
+ Alertify.wizardDialog('').resizeTo(pgBrowser.stdW.lg, pgBrowser.stdH.lg);
},
};
diff --git a/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.ui.js b/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.ui.js
new file mode 100644
index 00000000..ef23d7f9
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.ui.js
@@ -0,0 +1,35 @@
+import gettext from 'sources/gettext';
+import BaseUISchema from 'sources/SchemaView/base_schema.ui';
+
+export default class GrantWizardSchema extends BaseUISchema {
+ constructor(getPrivilegeRoleSchema, fieldOptions = {}, initValues) {
+ super({
+ oid: null,
+ privilege: [],
+ ...initValues
+ });
+
+ this.getPrivilegeRoleSchema = getPrivilegeRoleSchema;
+ this.fieldOptions = {
+ ...fieldOptions,
+ };
+
+ }
+
+ get idAttribute() {
+ return 'oid';
+ }
+
+ get baseFields() {
+ return [
+ {
+ id: 'privilege', label: gettext('Privileges'), type: 'collection',
+ schema: this.getPrivilegeRoleSchema(['a', 'r', 'w', 'd', 'D', 'x', 't', 'C', 'T', 'U', 'X']),
+ uniqueCol: ['grantee'],
+ editable: false, mode: ['create'],
+ canAdd: true, canDelete: true,
+ }
+ ];
+ }
+
+}
diff --git a/web/pgadmin/tools/grant_wizard/static/js/grant_wizard_view.jsx b/web/pgadmin/tools/grant_wizard/static/js/grant_wizard_view.jsx
new file mode 100644
index 00000000..98475f05
--- /dev/null
+++ b/web/pgadmin/tools/grant_wizard/static/js/grant_wizard_view.jsx
@@ -0,0 +1,214 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import gettext from 'sources/gettext';
+import url_for from 'sources/url_for';
+import React from 'react';
+import { Box } from '@material-ui/core';
+import Wizard from '../../../../browser/static/js/WizardView';
+import WizardStep from '../../../../browser/static/js/WizardStep';
+import PgTable from '../../../../browser/static/js/PgTable';
+import { getNodePrivilegeRoleSchema } from '../../../../browser/server_groups/servers/static/js/privilege.ui.js';
+import { InputSQL } from '../../../../static/js/components/FormComponents';
+import getApiInstance from '../../../../static/js/api_instance';
+import SchemaView from '../../../../static/js/SchemaView';
+import clsx from 'clsx';
+import Loader from 'sources/components/Loader';
+import GrantWizardSchema from './grant_wizard.ui';
+import Alertify from 'pgadmin.alertifyjs';
+import PropTypes from 'prop-types';
+
+
+export default function GrantWizard({ sid, did, nodeInfo, nodeData}) {
+ var columns = [
+ {
+
+ Header: 'Object Type',
+ accessor: 'object_type',
+ sortble: true,
+ resizable: true,
+ },
+ {
+ Header: 'Schema',
+ accessor: 'nspname',
+ sortble: true,
+ resizable: true,
+ },
+ {
+ Header: 'Name',
+ accessor: 'name',
+ sortble: true,
+ resizable: true,
+ }
+ ];
+ var steps = ['Select object', 'Privileges', 'SQL'];
+ const [selectedObject, setSelectedObject] = React.useState([]);
+ const [selectedAcl, setSelectedAcl] = React.useState({});
+ const [msqlData, setSQL] = React.useState('');
+ const [stepType, setStepType] = React.useState('');
+ const [grantWizardTitle, setgrantWizardTitle] = React.useState('Grant Wizard - ');
+ const [loaderText, setLoaderText] = React.useState('');
+ const [tablebData, setTableData] = React.useState([]);
+
+ const api = getApiInstance();
+ var selectedObjectTypes = new Set();
+
+ const validatePrivilege = () => {
+ var isValid = true;
+ selectedAcl.privilege.forEach((priv) => {
+ if ((_.isUndefined(priv.grantee) || _.isUndefined(priv.privileges) || priv.privileges.length === 0) && isValid) {
+ isValid = false;
+ }
+ });
+
+ return !isValid;
+ };
+
+ React.useEffect(() => {
+ setLoaderText('Loading...');
+ var node_type = nodeData._type.replace('coll-', '').replace(
+ 'materialized_', ''
+ );
+ var _url = url_for(
+ 'grant_wizard.objects', {
+ 'sid': encodeURI(sid),
+ 'did': encodeURI(did),
+ 'node_id': encodeURI(nodeData._id),
+ 'node_type': encodeURI(node_type),
+ });
+ api.get(_url)
+ .then(res => {
+ var data = res.data.result;
+ data.forEach(element => {
+ if (element.icon)
+ element['icon'] = {
+ 'object_type': element.icon
+ };
+ });
+ setTableData(data);
+ setLoaderText('');
+ })
+ .catch(() => {
+ Alertify.error(gettext('Error while fetching grant wizard data.'));
+ setLoaderText('');
+ });
+ }, [nodeData]);
+
+ return (
+ <>
+ {grantWizardTitle}
+
+ {
+ return selectedObject.length > 0 && stepType === 'object_type' ? false : selectedAcl?.privilege?.length > 0 && stepType === 'privileges' ? validatePrivilege() : true;
+ }}
+ onStepChange={(data) => {
+ var stepName = '';
+
+ switch (data.currentStep) {
+ case 0:
+ stepName = 'Object selection';
+ setStepType('object_type');
+ break;
+ case 1:
+ stepName = 'Privilege selection';
+ setStepType('privileges');
+ break;
+ case 2:
+ setLoaderText('Loading SQL ...');
+ stepName = 'Final (Review selection)';
+
+ var msql_url = url_for(
+ 'grant_wizard.modified_sql', {
+ 'sid': encodeURI(sid),
+ 'did': encodeURI(did),
+ });
+ var post_data = {
+ acl: selectedAcl.privilege,
+ objects: selectedObject
+ };
+ api.post(msql_url, post_data)
+ .then(res => {
+ setSQL(res.data.data);
+ setLoaderText('');
+ })
+ .catch(() => {
+ Alertify.error(gettext('Error while fetching SQL.'));
+ });
+ break;
+ default:
+ stepName = '';
+ setStepType('');
+ }
+ setgrantWizardTitle(`Grant Wizard - ${stepName} ( step ${data.currentStep + 1} of ${steps.length} )`);
+ }}
+ onSave={() => {
+ setLoaderText('Saving...');
+ var _url = url_for(
+ 'grant_wizard.apply', {
+ 'sid': encodeURI(sid),
+ 'did': encodeURI(did),
+ });
+ const post_data = {
+ acl: selectedAcl.privilege,
+ objects: selectedObject
+ };
+ api.post(_url, post_data)
+ .then(() => {
+ setLoaderText('');
+ Alertify.wizardDialog().close();
+ })
+ .catch(() => {
+ setLoaderText('');
+ Alertify.error(gettext('Error while saving grant wizard data.'));
+ });
+ }}>
+
+
+ {
+ var selObj = [];
+ if (selRows.length > 0) {
+ selRows.forEach((row) => {
+ selectedObjectTypes.add(row.values.object_type);
+ selObj.push(row.values);
+ });
+ }
+ setSelectedObject(selObj);
+ }}>
+
+
+
+ {}}
+ viewHelperProps={{ mode: 'create' }}
+ schema={new GrantWizardSchema((privileges) => getNodePrivilegeRoleSchema('', nodeInfo, nodeData, privileges))}
+ showFooter={false}
+ isTabView={false}
+ onDataChange={(isChanged, changedData) => {
+ setSelectedAcl(changedData);
+ }}
+ />
+
+
+
+
+
+ >
+ );
+}
+
+GrantWizard.propTypes = {
+ sid: PropTypes.string,
+ did: PropTypes.number,
+ nodeInfo: PropTypes.object,
+ nodeData: PropTypes.object,
+};
+
+
diff --git a/web/pgadmin/tools/grant_wizard/static/scss/_grant_wizard.scss b/web/pgadmin/tools/grant_wizard/static/scss/_grant_wizard.scss
index 185a2f69..1e5d390c 100644
--- a/web/pgadmin/tools/grant_wizard/static/scss/_grant_wizard.scss
+++ b/web/pgadmin/tools/grant_wizard/static/scss/_grant_wizard.scss
@@ -130,3 +130,37 @@
width: 100%;
height: 100%;
}
+
+.grant-wizard-sql {
+ height: 100%;
+ width: 100%;
+}
+
+#grantWizardDlg {
+ padding-top: 3em;
+}
+
+.grant-wizard-title{
+ top: 0px !important;
+ opacity: 1 !important;
+ border-radius: 6px 6px 0px 0px !important;
+ margin: 0 !important;
+ width: 100%;
+}
+
+.grant-wizard-step {
+ height: 100%;
+}
+
+.grant-wizard-objcol {
+ min-width: 8em !important;
+ width: 8em !important;
+}
+
+.privilege-step {
+ height: 400px;;
+}
+
+.step-panel-css {
+ height: 500px;
+}
diff --git a/web/regression/javascript/grant_wizard/wizard_spec.js b/web/regression/javascript/grant_wizard/wizard_spec.js
new file mode 100644
index 00000000..5a1c5ba6
--- /dev/null
+++ b/web/regression/javascript/grant_wizard/wizard_spec.js
@@ -0,0 +1,58 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import jasmineEnzyme from 'jasmine-enzyme';
+import React from 'react';
+import '../helper/enzyme.helper';
+import { createMount } from '@material-ui/core/test-utils';
+import pgAdmin from 'sources/pgadmin';
+import { messages } from '../fake_messages';
+// import GrantWizard from '../../../pgadmin/tools/grant_wizard/static/js/grant_wizard_view';
+import Theme from 'sources/Theme';
+import Wizard from '../../../pgadmin/browser/static/js/WizardView';
+import WizardStep from '../../../pgadmin/browser/static/js/WizardStep';
+
+describe('Wizard', () => {
+ let mount;
+
+ /* Use createMount so that material ui components gets the required context */
+ /* https://material-ui.com/guides/testing/#api */
+ beforeAll(() => {
+ mount = createMount();
+ });
+
+ afterAll(() => {
+ mount.cleanUp();
+ });
+
+ beforeEach(() => {
+ jasmineEnzyme();
+ /* messages used by validators */
+ pgAdmin.Browser = pgAdmin.Browser || {};
+ pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages;
+ pgAdmin.Browser.utils = pgAdmin.Browser.utils || {};
+ });
+
+ it('WizardPanel', () => {
+ mount(
+
+ {}}
+ onSave={()=>{}}
+ className={''}
+ disableNextStep={()=>{return false;}}
+ >
+
+
+
+ );
+ });
+});
+
diff --git a/web/regression/javascript/schema_ui_files/grant_wizard.ui.spec.js b/web/regression/javascript/schema_ui_files/grant_wizard.ui.spec.js
new file mode 100644
index 00000000..662ede23
--- /dev/null
+++ b/web/regression/javascript/schema_ui_files/grant_wizard.ui.spec.js
@@ -0,0 +1,64 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import jasmineEnzyme from 'jasmine-enzyme';
+import React from 'react';
+import '../helper/enzyme.helper';
+import { createMount } from '@material-ui/core/test-utils';
+import pgAdmin from 'sources/pgadmin';
+import { messages } from '../fake_messages';
+import SchemaView from '../../../pgadmin/static/js/SchemaView';
+import BaseUISchema from 'sources/SchemaView/base_schema.ui';
+import GrantWizardSchema from '../../../pgadmin/tools/grant_wizard/static/js/grant_wizard.ui';
+
+
+class MockSchema extends BaseUISchema {
+ get baseFields() {
+ return [];
+ }
+}
+
+describe('GrantWizard', () => {
+ let mount;
+ let schemaObj = new GrantWizardSchema(
+ () => new MockSchema(),
+ );
+
+ /* Use createMount so that material ui components gets the required context */
+ /* https://material-ui.com/guides/testing/#api */
+ beforeAll(() => {
+ mount = createMount();
+ });
+
+ afterAll(() => {
+ mount.cleanUp();
+ });
+
+ beforeEach(() => {
+ jasmineEnzyme();
+ /* messages used by validators */
+ pgAdmin.Browser = pgAdmin.Browser || {};
+ pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages;
+ pgAdmin.Browser.utils = pgAdmin.Browser.utils || {};
+ });
+
+ it('create', () => {
+ mount( { }}
+ showFooter={false}
+ isTabView={false}
+ />);
+ });
+});
+