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 ( + + ); + }) + } + +
+
+
+ + }> + {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 = - $(` -
-
-
` + gettext('Please select the objects to grant privileges to from the list below.') + `
-
-
-
- -
- -
-
-
-
-
`); - - // 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} + />); + }); +}); +