mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Added support to select/deselect objects in the Backup dialog. #642
This commit is contained in:
parent
8c91d40932
commit
aa973fc8ae
@ -251,6 +251,16 @@ tab to provide other backup options.
|
|||||||
table locks at the beginning of the dump. Instead, fail if unable to lock a
|
table locks at the beginning of the dump. Instead, fail if unable to lock a
|
||||||
table within the specified timeout.
|
table within the specified timeout.
|
||||||
|
|
||||||
|
Click the *Objects* tab to continue.
|
||||||
|
|
||||||
|
.. image:: images/backup_object_selection.png
|
||||||
|
:alt: Select objects in backup dialog
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
* Select the objects from tree to take backup of selected objects only.
|
||||||
|
* If Schema is selected then it will take the backup of that selected schema only.
|
||||||
|
* If any Table, View, Materialized View, Sequences, or Foreign Table is selected then it will take the backup of those selected objects.
|
||||||
|
|
||||||
When you’ve specified the details that will be incorporated into the pg_dump
|
When you’ve specified the details that will be incorporated into the pg_dump
|
||||||
command:
|
command:
|
||||||
|
|
||||||
|
BIN
docs/en_US/images/backup_object_selection.png
Normal file
BIN
docs/en_US/images/backup_object_selection.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 126 KiB |
@ -141,6 +141,7 @@
|
|||||||
"raf": "^3.4.1",
|
"raf": "^3.4.1",
|
||||||
"rc-dock": "^3.2.9",
|
"rc-dock": "^3.2.9",
|
||||||
"react": "^17.0.1",
|
"react": "^17.0.1",
|
||||||
|
"react-arborist": "^3.2.0",
|
||||||
"react-aspen": "^1.1.0",
|
"react-aspen": "^1.1.0",
|
||||||
"react-checkbox-tree": "^1.7.2",
|
"react-checkbox-tree": "^1.7.2",
|
||||||
"react-data-grid": "https://github.com/pgadmin-org/react-data-grid.git#200d2f5e02de694e3e9ffbe177c279bc40240fb8",
|
"react-data-grid": "https://github.com/pgadmin-org/react-data-grid.git#200d2f5e02de694e3e9ffbe177c279bc40240fb8",
|
||||||
|
268
web/pgadmin/static/js/PgTreeView/index.jsx
Normal file
268
web/pgadmin/static/js/PgTreeView/index.jsx
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
import { Checkbox, makeStyles } from '@material-ui/core';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import gettext from 'sources/gettext';
|
||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import { Tree } from 'react-arborist';
|
||||||
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
|
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||||
|
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import IndeterminateCheckBoxIcon from '@material-ui/icons/IndeterminateCheckBox';
|
||||||
|
import EmptyPanelMessage from '../components/EmptyPanelMessage';
|
||||||
|
import CheckBoxIcon from '@material-ui/icons/CheckBox';
|
||||||
|
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
node: {
|
||||||
|
display: 'inline-block',
|
||||||
|
paddingLeft: '1.5rem',
|
||||||
|
height: '1.5rem'
|
||||||
|
},
|
||||||
|
checkboxStyle: {
|
||||||
|
fill: theme.palette.primary.main
|
||||||
|
},
|
||||||
|
tree: {
|
||||||
|
background: theme.palette.background.default,
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
selectedNode: {
|
||||||
|
background: theme.otherVars.stepBg,
|
||||||
|
},
|
||||||
|
focusedNode: {
|
||||||
|
background: theme.palette.primary.light,
|
||||||
|
},
|
||||||
|
leafNode: {
|
||||||
|
marginLeft: '1.5rem'
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const PgTreeSelectionContext = React.createContext();
|
||||||
|
|
||||||
|
export default function PgTreeView({ data = [], hasCheckbox = false, selectionChange = null}) {
|
||||||
|
let classes = useStyles();
|
||||||
|
let treeData = data;
|
||||||
|
const treeObj = useRef();
|
||||||
|
const [selectedCheckBoxNodes, setSelectedCheckBoxNodes] = React.useState();
|
||||||
|
|
||||||
|
const onSelectionChange = () => {
|
||||||
|
if (hasCheckbox) {
|
||||||
|
let selectedChildNodes = [];
|
||||||
|
treeObj.current.selectedNodes.forEach((node) => {
|
||||||
|
selectedChildNodes.push(node.id);
|
||||||
|
});
|
||||||
|
setSelectedCheckBoxNodes(selectedChildNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectionChange?.(treeObj.current.selectedNodes);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (<>
|
||||||
|
{ treeData.length > 0 ?
|
||||||
|
<PgTreeSelectionContext.Provider value={_.isUndefined(selectedCheckBoxNodes) ? []: selectedCheckBoxNodes}>
|
||||||
|
<div className={clsx(classes.tree)}>
|
||||||
|
<AutoSizer>
|
||||||
|
{({ width, height }) => (
|
||||||
|
<Tree
|
||||||
|
ref={(obj) => {
|
||||||
|
treeObj.current = obj;
|
||||||
|
}}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
data={treeData}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
(props) => <Node onNodeSelectionChange={onSelectionChange} hasCheckbox={hasCheckbox} {...props}></Node>
|
||||||
|
}
|
||||||
|
</Tree>
|
||||||
|
)}
|
||||||
|
</AutoSizer>
|
||||||
|
</div>
|
||||||
|
</PgTreeSelectionContext.Provider>
|
||||||
|
:
|
||||||
|
<EmptyPanelMessage text={gettext('No objects are found to display')}/>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PgTreeView.propTypes = {
|
||||||
|
data: PropTypes.array,
|
||||||
|
selectionChange: PropTypes.func,
|
||||||
|
hasCheckbox: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
function Node({ node, style, tree, hasCheckbox, onNodeSelectionChange}) {
|
||||||
|
const classes = useStyles();
|
||||||
|
const pgTreeSelCtx = React.useContext(PgTreeSelectionContext);
|
||||||
|
const [isSelected, setIsSelected] = React.useState(pgTreeSelCtx.includes(node.id) ? true : false);
|
||||||
|
const [isIndeterminate, setIsIndeterminate] = React.useState(node?.parent.level==0? true: false);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
setIsIndeterminate(node.data.isIndeterminate);
|
||||||
|
}, [node?.data?.isIndeterminate]);
|
||||||
|
|
||||||
|
const onCheckboxSelection = (e) => {
|
||||||
|
if (hasCheckbox) {
|
||||||
|
setIsSelected(e.currentTarget.checked);
|
||||||
|
if (e.currentTarget.checked) {
|
||||||
|
|
||||||
|
node.selectMulti(node.id);
|
||||||
|
if (!node.isLeaf && node.isOpen) {
|
||||||
|
selectAllChild(node, tree);
|
||||||
|
} else {
|
||||||
|
if (node?.parent) {
|
||||||
|
checkAndSelectParent(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(node?.level == 0) {
|
||||||
|
node.data.isIndeterminate = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
node.deselect(node);
|
||||||
|
if (!node.isLeaf) {
|
||||||
|
deselectAllChild(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(node?.parent){
|
||||||
|
delectPrentNode(node.parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onNodeSelectionChange();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={style} className={clsx(node.isFocused ? classes.focusedNode : '', node.isSelected ? classes.selectedNode : '')} onClick={(e) => {
|
||||||
|
node.focus();
|
||||||
|
e.stopPropagation();
|
||||||
|
}}>
|
||||||
|
<CollectionArrow node={node} tree={tree} />
|
||||||
|
{
|
||||||
|
hasCheckbox ? <Checkbox style={{ padding: 0 }} color="primary" className={clsx(!node.isInternal ? classes.leafNode: null)}
|
||||||
|
checked={isSelected ? true: false}
|
||||||
|
checkedIcon={isIndeterminate ? <IndeterminateCheckBoxIcon />: <CheckBoxIcon />}
|
||||||
|
onChange={onCheckboxSelection}/> :
|
||||||
|
<span className={clsx(node.data.icon)}></span>
|
||||||
|
}
|
||||||
|
<div className={clsx(node.data.icon, classes.node)}>{node.data.name}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Node.propTypes = {
|
||||||
|
node: PropTypes.object,
|
||||||
|
style: PropTypes.any,
|
||||||
|
tree: PropTypes.object,
|
||||||
|
hasCheckbox: PropTypes.bool,
|
||||||
|
onNodeSelectionChange: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
function CollectionArrow({ node, tree }) {
|
||||||
|
const toggleNode = () => {
|
||||||
|
node.isInternal && node.toggle();
|
||||||
|
if (node.isSelected && node.isOpen) {
|
||||||
|
setTimeout(()=>{
|
||||||
|
selectAllChild(node, tree);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<span onClick={toggleNode} >
|
||||||
|
{node.isInternal ? <ToggleArrowIcon node={node} /> : null}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectionArrow.propTypes = {
|
||||||
|
node: PropTypes.object,
|
||||||
|
tree: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function ToggleArrowIcon({node}){
|
||||||
|
return (<>{node.isOpen ? <ExpandMoreIcon /> : <ChevronRightIcon />}</>);
|
||||||
|
}
|
||||||
|
|
||||||
|
ToggleArrowIcon.propTypes = {
|
||||||
|
node: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
function checkAndSelectParent(chNode){
|
||||||
|
let isAllChildSelected = true;
|
||||||
|
chNode?.parent?.children?.forEach((child) => {
|
||||||
|
if (!child.isSelected) {
|
||||||
|
isAllChildSelected = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (chNode?.parent) {
|
||||||
|
if (isAllChildSelected) {
|
||||||
|
if (chNode.parent?.level == 0) {
|
||||||
|
chNode.parent.data.isIndeterminate = true;
|
||||||
|
} else {
|
||||||
|
chNode.parent.data.isIndeterminate = false;
|
||||||
|
}
|
||||||
|
chNode.parent.selectMulti(chNode.parent.id);
|
||||||
|
} else {
|
||||||
|
chNode.parent.data.isIndeterminate = true;
|
||||||
|
chNode.parent.selectMulti(chNode.parent.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAndSelectParent(chNode.parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAndSelectParent.propTypes = {
|
||||||
|
chNode: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
function delectPrentNode(chNode){
|
||||||
|
if (chNode) {
|
||||||
|
let isAnyChildSelected = false;
|
||||||
|
chNode.children.forEach((childNode)=>{
|
||||||
|
if(childNode.isSelected && !isAnyChildSelected){
|
||||||
|
isAnyChildSelected = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if(isAnyChildSelected){
|
||||||
|
chNode.data.isIndeterminate = true;
|
||||||
|
} else {
|
||||||
|
chNode.deselect(chNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chNode?.parent) {
|
||||||
|
delectPrentNode(chNode.parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectAllChild(chNode, tree){
|
||||||
|
chNode?.children?.forEach(child => {
|
||||||
|
child.selectMulti(child.id);
|
||||||
|
|
||||||
|
if (child?.children) {
|
||||||
|
selectAllChild(child, tree);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (chNode?.parent) {
|
||||||
|
checkAndSelectParent(chNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deselectAllChild(chNode){
|
||||||
|
chNode?.children.forEach(child => {
|
||||||
|
child.deselect(child);
|
||||||
|
|
||||||
|
if (child?.children) {
|
||||||
|
deselectAllChild(child);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@ -17,7 +17,7 @@ import { MappedFormControl } from './MappedControl';
|
|||||||
import TabPanel from '../components/TabPanel';
|
import TabPanel from '../components/TabPanel';
|
||||||
import DataGridView from './DataGridView';
|
import DataGridView from './DataGridView';
|
||||||
import { SCHEMA_STATE_ACTIONS, StateUtilsContext } from '.';
|
import { SCHEMA_STATE_ACTIONS, StateUtilsContext } from '.';
|
||||||
import { InputSQL } from '../components/FormComponents';
|
import { FormNote, InputSQL } from '../components/FormComponents';
|
||||||
import gettext from 'sources/gettext';
|
import gettext from 'sources/gettext';
|
||||||
import { evalFunc } from 'sources/utils';
|
import { evalFunc } from 'sources/utils';
|
||||||
import CustomPropTypes from '../custom_prop_types';
|
import CustomPropTypes from '../custom_prop_types';
|
||||||
@ -39,6 +39,10 @@ const useStyles = makeStyles((theme)=>({
|
|||||||
nestedControl: {
|
nestedControl: {
|
||||||
height: 'unset',
|
height: 'unset',
|
||||||
},
|
},
|
||||||
|
fullControl: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column'
|
||||||
|
},
|
||||||
errorMargin: {
|
errorMargin: {
|
||||||
/* Error footer space */
|
/* Error footer space */
|
||||||
paddingBottom: '36px !important',
|
paddingBottom: '36px !important',
|
||||||
@ -306,7 +310,7 @@ export default function FormView({
|
|||||||
firstEleID.current = field.id;
|
firstEleID.current = field.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentControl = <MappedFormControl
|
let currentControl = <MappedFormControl
|
||||||
inputRef={(ele)=>{
|
inputRef={(ele)=>{
|
||||||
if(firstEleRef && firstEleID.current === field.id) {
|
if(firstEleRef && firstEleID.current === field.id) {
|
||||||
firstEleRef.current = ele;
|
firstEleRef.current = ele;
|
||||||
@ -344,6 +348,13 @@ export default function FormView({
|
|||||||
]}
|
]}
|
||||||
/>;
|
/>;
|
||||||
|
|
||||||
|
if(field.isFullTab && field.helpMessage) {
|
||||||
|
currentControl = (<React.Fragment key={`coll-${field.id}`}>
|
||||||
|
<FormNote key={`note-${field.id}`} text={field.helpMessage}/>
|
||||||
|
{currentControl}
|
||||||
|
</React.Fragment>);
|
||||||
|
}
|
||||||
|
|
||||||
if(field.inlineNext) {
|
if(field.inlineNext) {
|
||||||
inlineComponents.push(React.cloneElement(currentControl, {
|
inlineComponents.push(React.cloneElement(currentControl, {
|
||||||
withContainer: false, controlGridBasis: 3
|
withContainer: false, controlGridBasis: 3
|
||||||
@ -422,6 +433,8 @@ export default function FormView({
|
|||||||
let contentClassName = [stateUtils.formErr.message ? classes.errorMargin : null];
|
let contentClassName = [stateUtils.formErr.message ? classes.errorMargin : null];
|
||||||
if(fullTabs.indexOf(tabName) == -1) {
|
if(fullTabs.indexOf(tabName) == -1) {
|
||||||
contentClassName.push(classes.nestedControl);
|
contentClassName.push(classes.nestedControl);
|
||||||
|
} else {
|
||||||
|
contentClassName.push(classes.fullControl);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<TabPanel key={tabName} value={tabValue} index={i} classNameRoot={clsx(tabsClassname[tabName], isNested ? classes.nestedTabPanel : null)}
|
<TabPanel key={tabName} value={tabValue} index={i} classNameRoot={clsx(tabsClassname[tabName], isNested ? classes.nestedTabPanel : null)}
|
||||||
|
@ -12,7 +12,7 @@ import _ from 'lodash';
|
|||||||
import {
|
import {
|
||||||
FormInputText, FormInputSelect, FormInputSwitch, FormInputCheckbox, FormInputColor,
|
FormInputText, FormInputSelect, FormInputSwitch, FormInputCheckbox, FormInputColor,
|
||||||
FormInputFileSelect, FormInputToggle, InputSwitch, FormInputSQL, InputSQL, FormNote, FormInputDateTimePicker, PlainString,
|
FormInputFileSelect, FormInputToggle, InputSwitch, FormInputSQL, InputSQL, FormNote, FormInputDateTimePicker, PlainString,
|
||||||
InputSelect, InputText, InputCheckbox, InputDateTimePicker, InputFileSelect, FormInputKeyboardShortcut, FormInputQueryThreshold, FormInputSelectThemes, InputRadio, FormButton
|
InputSelect, InputText, InputCheckbox, InputDateTimePicker, InputFileSelect, FormInputKeyboardShortcut, FormInputQueryThreshold, FormInputSelectThemes, InputRadio, FormButton, InputTree
|
||||||
} from '../components/FormComponents';
|
} from '../components/FormComponents';
|
||||||
import Privilege from '../components/Privilege';
|
import Privilege from '../components/Privilege';
|
||||||
import { evalFunc } from 'sources/utils';
|
import { evalFunc } from 'sources/utils';
|
||||||
@ -35,6 +35,10 @@ function MappedFormControlBase({ type, value, id, onChange, className, visible,
|
|||||||
onChange && onChange(changedValue);
|
onChange && onChange(changedValue);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const onTreeSelection = useCallback((selectedValues)=> {
|
||||||
|
onChange && onChange(selectedValues);
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
@ -89,6 +93,8 @@ function MappedFormControlBase({ type, value, id, onChange, className, visible,
|
|||||||
return <FormInputSelectThemes name={name} value={value} onChange={onTextChange} {...props}/>;
|
return <FormInputSelectThemes name={name} value={value} onChange={onTextChange} {...props}/>;
|
||||||
case 'button':
|
case 'button':
|
||||||
return <FormButton name={name} value={value} className={className} onClick={onClick} {...props} />;
|
return <FormButton name={name} value={value} className={className} onClick={onClick} {...props} />;
|
||||||
|
case 'tree':
|
||||||
|
return <InputTree name={name} treeData={props.treeData} onChange={onTreeSelection} {...props}/>;
|
||||||
default:
|
default:
|
||||||
return <PlainString value={value} {...props} />;
|
return <PlainString value={value} {...props} />;
|
||||||
}
|
}
|
||||||
@ -109,7 +115,8 @@ MappedFormControlBase.propTypes = {
|
|||||||
noLabel: PropTypes.bool,
|
noLabel: PropTypes.bool,
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
withContainer: PropTypes.bool,
|
withContainer: PropTypes.bool,
|
||||||
controlGridBasis: PropTypes.number
|
controlGridBasis: PropTypes.number,
|
||||||
|
treeData: PropTypes.array,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Control mapping for grid cell view */
|
/* Control mapping for grid cell view */
|
||||||
@ -205,7 +212,7 @@ const ALLOWED_PROPS_FIELD_COMMON = [
|
|||||||
'label', 'options', 'optionsLoaded', 'controlProps', 'schema', 'inputRef',
|
'label', 'options', 'optionsLoaded', 'controlProps', 'schema', 'inputRef',
|
||||||
'visible', 'autoFocus', 'helpMessage', 'className', 'optionsReloadBasis',
|
'visible', 'autoFocus', 'helpMessage', 'className', 'optionsReloadBasis',
|
||||||
'orientation', 'isvalidate', 'fields', 'radioType', 'hideBrowseButton', 'btnName', 'hidden',
|
'orientation', 'isvalidate', 'fields', 'radioType', 'hideBrowseButton', 'btnName', 'hidden',
|
||||||
'withContainer', 'controlGridBasis',
|
'withContainer', 'controlGridBasis', 'hasCheckbox', 'treeData'
|
||||||
];
|
];
|
||||||
|
|
||||||
const ALLOWED_PROPS_FIELD_FORM = [
|
const ALLOWED_PROPS_FIELD_FORM = [
|
||||||
|
@ -12,6 +12,7 @@ const useStyles = makeStyles((theme)=>({
|
|||||||
fontSize: '0.8rem',
|
fontSize: '0.8rem',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
flexDirection: 'column'
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -19,8 +20,7 @@ export default function EmptyPanelMessage({text}) {
|
|||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
return (
|
return (
|
||||||
<Box className={classes.root}>
|
<Box className={classes.root}>
|
||||||
<InfoRoundedIcon style={{height: '1.2rem'}}/>
|
<span style={{marginLeft: '4px'}}><InfoRoundedIcon style={{height: '1.2rem'}}/>{text}</span>
|
||||||
<span style={{marginLeft: '4px'}}>{text}</span>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@ import SelectThemes from './SelectThemes';
|
|||||||
import { showFileManager } from '../helpers/showFileManager';
|
import { showFileManager } from '../helpers/showFileManager';
|
||||||
import { withColorPicker } from '../helpers/withColorPicker';
|
import { withColorPicker } from '../helpers/withColorPicker';
|
||||||
import { useWindowSize } from '../custom_hooks';
|
import { useWindowSize } from '../custom_hooks';
|
||||||
|
import PgTreeView from '../PgTreeView';
|
||||||
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
@ -1275,3 +1276,14 @@ FormButton.propTypes = {
|
|||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
btnName: PropTypes.string
|
btnName: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function InputTree({hasCheckbox, treeData, onChange, ...props}){
|
||||||
|
return <PgTreeView data={treeData} hasCheckbox={hasCheckbox} selectionChange={onChange} {...props}></PgTreeView>;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputTree.propTypes = {
|
||||||
|
hasCheckbox: PropTypes.bool,
|
||||||
|
treeData: PropTypes.array,
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
selectionChange: PropTypes.func,
|
||||||
|
};
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import copy
|
||||||
import functools
|
import functools
|
||||||
import operator
|
import operator
|
||||||
|
|
||||||
@ -27,6 +28,8 @@ from config import PG_DEFAULT_DRIVER
|
|||||||
from pgadmin.model import Server, SharedServer
|
from pgadmin.model import Server, SharedServer
|
||||||
from pgadmin.misc.bgprocess import escape_dquotes_process_arg
|
from pgadmin.misc.bgprocess import escape_dquotes_process_arg
|
||||||
from pgadmin.utils.constants import MIMETYPE_APP_JS
|
from pgadmin.utils.constants import MIMETYPE_APP_JS
|
||||||
|
from pgadmin.tools.grant_wizard import _get_rows_for_type, \
|
||||||
|
get_node_sql_with_type, properties, get_data
|
||||||
|
|
||||||
# set template path for sql scripts
|
# set template path for sql scripts
|
||||||
MODULE_NAME = 'backup'
|
MODULE_NAME = 'backup'
|
||||||
@ -56,7 +59,8 @@ class BackupModule(PgAdminModule):
|
|||||||
list: URL endpoints for backup module
|
list: URL endpoints for backup module
|
||||||
"""
|
"""
|
||||||
return ['backup.create_server_job', 'backup.create_object_job',
|
return ['backup.create_server_job', 'backup.create_object_job',
|
||||||
'backup.utility_exists']
|
'backup.utility_exists', 'backup.objects',
|
||||||
|
'backup.schema_objects']
|
||||||
|
|
||||||
|
|
||||||
# Create blueprint for BackupModule class
|
# Create blueprint for BackupModule class
|
||||||
@ -355,6 +359,23 @@ def _get_args_params_values(data, conn, backup_obj_type, backup_file, server,
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if 'objects' in data:
|
||||||
|
selected_objects = data.get('objects', {})
|
||||||
|
for _key in selected_objects:
|
||||||
|
param = 'schema' if _key == 'schema' else 'table'
|
||||||
|
args.extend(
|
||||||
|
functools.reduce(operator.iconcat, map(
|
||||||
|
lambda s: [f'--{param}',
|
||||||
|
r'{0}.{1}'.format(
|
||||||
|
driver.qtIdent(conn, s['schema']).replace(
|
||||||
|
'"', '\"'),
|
||||||
|
driver.qtIdent(conn, s['name']).replace(
|
||||||
|
'"', '\"')) if type(
|
||||||
|
s) is dict else driver.qtIdent(
|
||||||
|
conn, s).replace('"', '\"')],
|
||||||
|
selected_objects[_key] or []), [])
|
||||||
|
)
|
||||||
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
@ -505,3 +526,124 @@ def check_utility_exists(sid, backup_obj_type):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return make_json_response(success=1)
|
return make_json_response(success=1)
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route(
|
||||||
|
'/objects/<int:sid>/<int:did>', endpoint='objects'
|
||||||
|
)
|
||||||
|
@blueprint.route(
|
||||||
|
'/objects/<int:sid>/<int:did>/<int:scid>', endpoint='schema_objects'
|
||||||
|
)
|
||||||
|
@login_required
|
||||||
|
def objects(sid, did, scid=None):
|
||||||
|
"""
|
||||||
|
This function returns backup objects
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sid: Server ID
|
||||||
|
did: database ID
|
||||||
|
scid: schema ID
|
||||||
|
Returns:
|
||||||
|
list of objects
|
||||||
|
"""
|
||||||
|
from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry
|
||||||
|
server = get_server(sid)
|
||||||
|
|
||||||
|
if server is None:
|
||||||
|
return make_json_response(
|
||||||
|
success=0,
|
||||||
|
errormsg=_("Could not find the specified server.")
|
||||||
|
)
|
||||||
|
|
||||||
|
from pgadmin.utils.driver import get_driver
|
||||||
|
from flask_babel import gettext
|
||||||
|
from pgadmin.utils.ajax import precondition_required
|
||||||
|
|
||||||
|
server_info = {}
|
||||||
|
server_info['manager'] = get_driver(PG_DEFAULT_DRIVER) \
|
||||||
|
.connection_manager(sid)
|
||||||
|
server_info['conn'] = server_info['manager'].connection(
|
||||||
|
did=did)
|
||||||
|
# If DB not connected then return error to browser
|
||||||
|
if not server_info['conn'].connected():
|
||||||
|
return precondition_required(
|
||||||
|
gettext("Connection to the server has been lost.")
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set template path for sql scripts
|
||||||
|
server_info['server_type'] = server_info['manager'].server_type
|
||||||
|
server_info['version'] = server_info['manager'].version
|
||||||
|
if server_info['server_type'] == 'pg':
|
||||||
|
server_info['template_path'] = 'grant_wizard/pg/#{0}#'.format(
|
||||||
|
server_info['version'])
|
||||||
|
elif server_info['server_type'] == 'ppas':
|
||||||
|
server_info['template_path'] = 'grant_wizard/ppas/#{0}#'.format(
|
||||||
|
server_info['version'])
|
||||||
|
|
||||||
|
res, msg = get_data(sid, did, scid, 'schema' if scid else 'database',
|
||||||
|
server_info)
|
||||||
|
|
||||||
|
tree_data = {
|
||||||
|
'table': [],
|
||||||
|
'view': [],
|
||||||
|
'materialized view': [],
|
||||||
|
'foreign table': [],
|
||||||
|
'sequence': []
|
||||||
|
}
|
||||||
|
|
||||||
|
schema_group = {}
|
||||||
|
|
||||||
|
for data in res:
|
||||||
|
obj_type = data['object_type'].lower()
|
||||||
|
if obj_type in ['table', 'view', 'materialized view', 'foreign table',
|
||||||
|
'sequence']:
|
||||||
|
|
||||||
|
if data['nspname'] not in schema_group:
|
||||||
|
schema_group[data['nspname']] = {
|
||||||
|
'id': data['nspname'],
|
||||||
|
'name': data['nspname'],
|
||||||
|
'icon': 'icon-schema',
|
||||||
|
'children': copy.deepcopy(tree_data),
|
||||||
|
'is_schema': True,
|
||||||
|
}
|
||||||
|
icon_data = {
|
||||||
|
'materialized view': 'icon-mview',
|
||||||
|
'foreign table': 'icon-foreign_table'
|
||||||
|
}
|
||||||
|
icon = icon_data[obj_type] if obj_type in icon_data \
|
||||||
|
else data['icon']
|
||||||
|
schema_group[data['nspname']]['children'][obj_type].append({
|
||||||
|
'id': f'{data["nspname"]}_{data["name"]}',
|
||||||
|
'name': data['name'],
|
||||||
|
'icon': icon,
|
||||||
|
'schema': data['nspname'],
|
||||||
|
'type': obj_type,
|
||||||
|
'_name': '{0}.{1}'.format(data['nspname'], data['name'])
|
||||||
|
})
|
||||||
|
|
||||||
|
schema_group = [dt for k, dt in schema_group.items()]
|
||||||
|
for ch in schema_group:
|
||||||
|
children = []
|
||||||
|
for obj_type, data in ch['children'].items():
|
||||||
|
if data:
|
||||||
|
icon_data = {
|
||||||
|
'materialized view': 'icon-coll-mview',
|
||||||
|
'foreign table': 'icon-coll-foreign_table'
|
||||||
|
}
|
||||||
|
icon = icon_data[obj_type] if obj_type in icon_data \
|
||||||
|
else f'icon-coll-{obj_type.lower()}',
|
||||||
|
children.append({
|
||||||
|
'id': f'{ch["id"]}_{obj_type}',
|
||||||
|
'name': f'{obj_type.title()}s',
|
||||||
|
'icon': icon,
|
||||||
|
'children': data,
|
||||||
|
'type': obj_type,
|
||||||
|
'is_collection': True,
|
||||||
|
})
|
||||||
|
|
||||||
|
ch['children'] = children
|
||||||
|
|
||||||
|
return make_json_response(
|
||||||
|
data=schema_group,
|
||||||
|
success=200
|
||||||
|
)
|
||||||
|
@ -180,6 +180,7 @@ define([
|
|||||||
gettext(data.errormsg)
|
gettext(data.errormsg)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
pgBrowser.BgProcessManager.startProcess(data.data.job_id, data.data.desc);
|
pgBrowser.BgProcessManager.startProcess(data.data.job_id, data.data.desc);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -237,18 +238,43 @@ define([
|
|||||||
let panel = pgBrowser.Node.addUtilityPanel(pgBrowser.stdW.md, pgBrowser.stdH.lg),
|
let panel = pgBrowser.Node.addUtilityPanel(pgBrowser.stdW.md, pgBrowser.stdH.lg),
|
||||||
j = panel.$container.find('.obj_properties').first();
|
j = panel.$container.find('.obj_properties').first();
|
||||||
|
|
||||||
let schema = that.getUISchema(treeItem, 'backup_objects');
|
let backup_obj_url = '';
|
||||||
panel.title(gettext(`Backup (${pgBrowser.Nodes[data._type].label}: ${data.label})`));
|
if (data._type == 'database') {
|
||||||
panel.focus();
|
let did = data._id;
|
||||||
|
backup_obj_url = url_for('backup.objects', {
|
||||||
|
'sid': sid,
|
||||||
|
'did': did
|
||||||
|
});
|
||||||
|
} else if(data._type == 'schema') {
|
||||||
|
let did = data._pid;
|
||||||
|
let scid = data._id;
|
||||||
|
backup_obj_url = url_for('backup.schema_objects', {
|
||||||
|
'sid': sid,
|
||||||
|
'did': did,
|
||||||
|
'scid': scid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let typeOfDialog = 'backup_objects',
|
api({
|
||||||
serverIdentifier = that.retrieveServerIdentifier(),
|
url: backup_obj_url,
|
||||||
extraData = that.setExtraParameters(typeOfDialog);
|
method: 'GET'
|
||||||
|
}).then((response)=> {
|
||||||
|
let objects = response.data.data;
|
||||||
|
let schema = that.getUISchema(treeItem, 'backup_objects', objects);
|
||||||
|
panel.title(gettext(`Backup (${pgBrowser.Nodes[data._type].label}: ${data.label})`));
|
||||||
|
panel.focus();
|
||||||
|
|
||||||
|
let typeOfDialog = 'backup_objects',
|
||||||
|
serverIdentifier = that.retrieveServerIdentifier(),
|
||||||
|
extraData = that.setExtraParameters(typeOfDialog);
|
||||||
|
|
||||||
|
that.showBackupDialog(schema, treeItem, j, data, panel, typeOfDialog, serverIdentifier, extraData);
|
||||||
|
});
|
||||||
|
|
||||||
that.showBackupDialog(schema, treeItem, j, data, panel, typeOfDialog, serverIdentifier, extraData);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getUISchema: function(treeItem, backupType) {
|
|
||||||
|
getUISchema: function(treeItem, backupType, objects) {
|
||||||
let treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(treeItem);
|
let treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(treeItem);
|
||||||
const selectedNode = pgBrowser.tree.selected();
|
const selectedNode = pgBrowser.tree.selected();
|
||||||
let itemNodeData = pgBrowser.tree.findNodeByDomElement(selectedNode).getData();
|
let itemNodeData = pgBrowser.tree.findNodeByDomElement(selectedNode).getData();
|
||||||
@ -267,7 +293,8 @@ define([
|
|||||||
},
|
},
|
||||||
treeNodeInfo,
|
treeNodeInfo,
|
||||||
pgBrowser,
|
pgBrowser,
|
||||||
backupType
|
backupType,
|
||||||
|
objects
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
getGlobalUISchema: function(treeItem) {
|
getGlobalUISchema: function(treeItem) {
|
||||||
|
@ -416,7 +416,7 @@ export function getMiscellaneousSchema(fieldOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class BackupSchema extends BaseUISchema {
|
export default class BackupSchema extends BaseUISchema {
|
||||||
constructor(sectionSchema, typeObjSchema, saveOptSchema, disabledOptionSchema, miscellaneousSchema, fieldOptions = {}, treeNodeInfo=[], pgBrowser=null, backupType='server') {
|
constructor(sectionSchema, typeObjSchema, saveOptSchema, disabledOptionSchema, miscellaneousSchema, fieldOptions = {}, treeNodeInfo=[], pgBrowser=null, backupType='server', objects={}) {
|
||||||
super({
|
super({
|
||||||
file: undefined,
|
file: undefined,
|
||||||
format: 'custom',
|
format: 'custom',
|
||||||
@ -431,6 +431,7 @@ export default class BackupSchema extends BaseUISchema {
|
|||||||
...fieldOptions,
|
...fieldOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.treeData = objects;
|
||||||
this.treeNodeInfo = treeNodeInfo;
|
this.treeNodeInfo = treeNodeInfo;
|
||||||
this.pgBrowser = pgBrowser;
|
this.pgBrowser = pgBrowser;
|
||||||
this.backupType = backupType;
|
this.backupType = backupType;
|
||||||
@ -699,6 +700,42 @@ export default class BackupSchema extends BaseUISchema {
|
|||||||
label: gettext('Miscellaneous'),
|
label: gettext('Miscellaneous'),
|
||||||
group: gettext('Options'),
|
group: gettext('Options'),
|
||||||
schema: obj.getMiscellaneousSchema(),
|
schema: obj.getMiscellaneousSchema(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'object', label: gettext('Objects'), type: 'group',
|
||||||
|
visible: isVisibleForServerBackup(obj?.backupType)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'objects',
|
||||||
|
label: gettext('objects'),
|
||||||
|
group: gettext('Objects'),
|
||||||
|
type: 'tree',
|
||||||
|
helpMessage: gettext('If Schema(s) is selected then it will take the backup of that selected schema(s) only'),
|
||||||
|
treeData: this.treeData,
|
||||||
|
visible: () => {
|
||||||
|
return isVisibleForServerBackup(obj?.backupType);
|
||||||
|
},
|
||||||
|
depChange: (state)=> {
|
||||||
|
let selectedNodeCollection = {
|
||||||
|
'schema': [],
|
||||||
|
'table': [],
|
||||||
|
'view': [],
|
||||||
|
'sequence': [],
|
||||||
|
'foreign table': [],
|
||||||
|
'materialized view': [],
|
||||||
|
};
|
||||||
|
state?.objects?.forEach((node)=> {
|
||||||
|
if(node.data.is_schema && !node.data?.isIndeterminate) {
|
||||||
|
selectedNodeCollection['schema'].push(node.data.name);
|
||||||
|
} else if(['table', 'view', 'materialized view', 'foreign table', 'sequence'].includes(node.data.type) &&
|
||||||
|
!node.data.is_collection && !selectedNodeCollection['schema'].includes(node.data.schema)) {
|
||||||
|
selectedNodeCollection[node.data.type].push(node.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {'objects': selectedNodeCollection};
|
||||||
|
},
|
||||||
|
hasCheckbox: true,
|
||||||
|
isFullTab: true,
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ class BackupJobTest(BaseTestGenerator):
|
|||||||
blobs=True,
|
blobs=True,
|
||||||
schemas=[],
|
schemas=[],
|
||||||
tables=[],
|
tables=[],
|
||||||
database='postgres'
|
database='postgres',
|
||||||
),
|
),
|
||||||
url='/backup/job/{0}/object',
|
url='/backup/job/{0}/object',
|
||||||
expected_params=dict(
|
expected_params=dict(
|
||||||
@ -48,7 +48,7 @@ class BackupJobTest(BaseTestGenerator):
|
|||||||
blobs=True,
|
blobs=True,
|
||||||
schemas=[],
|
schemas=[],
|
||||||
tables=[],
|
tables=[],
|
||||||
database='postgres'
|
database='postgres',
|
||||||
),
|
),
|
||||||
url='/backup/job/{0}/object',
|
url='/backup/job/{0}/object',
|
||||||
expected_params=dict(
|
expected_params=dict(
|
||||||
@ -60,7 +60,35 @@ class BackupJobTest(BaseTestGenerator):
|
|||||||
server_min_version=160000,
|
server_min_version=160000,
|
||||||
message='--large-objects is not supported by EPAS/PG server '
|
message='--large-objects is not supported by EPAS/PG server '
|
||||||
'less than 16'
|
'less than 16'
|
||||||
))
|
)),
|
||||||
|
('When backup selected objects ',
|
||||||
|
dict(
|
||||||
|
params=dict(
|
||||||
|
file='test_backup',
|
||||||
|
format='custom',
|
||||||
|
verbose=True,
|
||||||
|
blobs=True,
|
||||||
|
schemas=[],
|
||||||
|
tables=[],
|
||||||
|
database='postgres',
|
||||||
|
objects={
|
||||||
|
"schema": [],
|
||||||
|
"table": [
|
||||||
|
{"id": "public_test", "name": "test",
|
||||||
|
"icon": "icon-table", "schema": "public",
|
||||||
|
"type": "table", "_name": "public.test"}
|
||||||
|
],
|
||||||
|
"view": [], "sequence": [], "foreign_table": [],
|
||||||
|
"mview": []
|
||||||
|
}
|
||||||
|
),
|
||||||
|
url='/backup/job/{0}/object',
|
||||||
|
expected_params=dict(
|
||||||
|
expected_cmd_opts=['--verbose', '--format=c', '--blobs'],
|
||||||
|
not_expected_cmd_opts=[],
|
||||||
|
expected_exit_code=[1]
|
||||||
|
)
|
||||||
|
)),
|
||||||
]
|
]
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -270,12 +270,22 @@ def properties(sid, did, node_id, node_type):
|
|||||||
and render into selection page of wizard
|
and render into selection page of wizard
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
res_data, msg = get_data(sid, did, node_id, node_type, server_info)
|
||||||
|
|
||||||
|
return make_json_response(
|
||||||
|
result=res_data,
|
||||||
|
info=msg,
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_data(sid, did, node_id, node_type, server_data):
|
||||||
get_schema_sql_url = '/sql/get_schemas.sql'
|
get_schema_sql_url = '/sql/get_schemas.sql'
|
||||||
|
|
||||||
# unquote encoded url parameter
|
# unquote encoded url parameter
|
||||||
node_type = unquote(node_type)
|
node_type = unquote(node_type)
|
||||||
|
|
||||||
server_prop = server_info
|
server_prop = server_data
|
||||||
|
|
||||||
res_data = []
|
res_data = []
|
||||||
failed_objects = []
|
failed_objects = []
|
||||||
@ -350,12 +360,7 @@ def properties(sid, did, node_id, node_type):
|
|||||||
msg = gettext('Unable to fetch the {} objects'.format(
|
msg = gettext('Unable to fetch the {} objects'.format(
|
||||||
", ".join(failed_objects))
|
", ".join(failed_objects))
|
||||||
)
|
)
|
||||||
|
return res_data, msg
|
||||||
return make_json_response(
|
|
||||||
result=res_data,
|
|
||||||
info=msg,
|
|
||||||
status=200
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_req_data():
|
def get_req_data():
|
||||||
|
@ -854,7 +854,6 @@ def start_query_tool(trans_id):
|
|||||||
Args:
|
Args:
|
||||||
trans_id: unique transaction id
|
trans_id: unique transaction id
|
||||||
"""
|
"""
|
||||||
|
|
||||||
sql = extract_sql_from_network_parameters(
|
sql = extract_sql_from_network_parameters(
|
||||||
request.data, request.args, request.form
|
request.data, request.args, request.form
|
||||||
)
|
)
|
||||||
|
@ -109,7 +109,7 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN
|
|||||||
fgcolor: params.fgcolor,
|
fgcolor: params.fgcolor,
|
||||||
bgcolor: params.bgcolor,
|
bgcolor: params.bgcolor,
|
||||||
conn_title: getTitle(
|
conn_title: getTitle(
|
||||||
pgAdmin, null, selectedNodeInfo, true, _.unescape(params.server_name), _.unescape(params.database_name) || getDatabaseLabel(selectedNodeInfo),
|
pgAdmin, null, selectedNodeInfo, true, _.unescape(params.server_name), _.escape(params.database_name) || getDatabaseLabel(selectedNodeInfo),
|
||||||
_.unescape(params.role) || _.unescape(params.user), params.is_query_tool == 'true' ? true : false),
|
_.unescape(params.role) || _.unescape(params.user), params.is_query_tool == 'true' ? true : false),
|
||||||
server_name: _.unescape(params.server_name),
|
server_name: _.unescape(params.server_name),
|
||||||
database_name: _.unescape(params.database_name) || getDatabaseLabel(selectedNodeInfo),
|
database_name: _.unescape(params.database_name) || getDatabaseLabel(selectedNodeInfo),
|
||||||
|
@ -106,6 +106,7 @@ class StartRunningQuery:
|
|||||||
status = False
|
status = False
|
||||||
result = gettext(
|
result = gettext(
|
||||||
'Either transaction object or session object not found.')
|
'Either transaction object or session object not found.')
|
||||||
|
|
||||||
return make_json_response(
|
return make_json_response(
|
||||||
data={
|
data={
|
||||||
'status': status, 'result': result,
|
'status': status, 'result': result,
|
||||||
|
@ -37,7 +37,8 @@ describe('BackupSchema', ()=>{
|
|||||||
},
|
},
|
||||||
{server: {version: 11000}},
|
{server: {version: 11000}},
|
||||||
pgAdmin.pgBrowser,
|
pgAdmin.pgBrowser,
|
||||||
'backup_objects'
|
'backup_objects',
|
||||||
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
it('create object backup', ()=>{
|
it('create object backup', ()=>{
|
||||||
@ -61,6 +62,43 @@ describe('BackupSchema', ()=>{
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
let backupSelectedSchemaObj = new BackupSchema(
|
||||||
|
()=> getSectionSchema(),
|
||||||
|
()=> getTypeObjSchema(),
|
||||||
|
()=> getSaveOptSchema({nodeInfo: {server: {version: 11000}}}),
|
||||||
|
()=> getDisabledOptionSchema({nodeInfo: {server: {version: 11000}}}),
|
||||||
|
()=> getMiscellaneousSchema({nodeInfo: {server: {version: 11000}}}),
|
||||||
|
{
|
||||||
|
role: ()=>[],
|
||||||
|
encoding: ()=>[],
|
||||||
|
},
|
||||||
|
{server: {version: 11000}},
|
||||||
|
pgAdmin.pgBrowser,
|
||||||
|
'backup_objects',
|
||||||
|
[{'id': 'public','name': 'public','icon': 'icon-schema', 'children': [{'id': 'public_table','name': 'table','icon': 'icon-coll-table','children': [{'id': 'public_test','name': 'test','icon': 'icon-table','schema': 'public','type': 'table','_name': 'public.test'}],'type': 'table','is_collection': true}],'is_schema': true}]
|
||||||
|
);
|
||||||
|
|
||||||
|
it('create selected object backup', ()=>{
|
||||||
|
mount(<Theme>
|
||||||
|
<SchemaView
|
||||||
|
formType='dialog'
|
||||||
|
schema={backupSelectedSchemaObj}
|
||||||
|
viewHelperProps={{
|
||||||
|
mode: 'create',
|
||||||
|
}}
|
||||||
|
onSave={()=>{/*This is intentional (SonarQube)*/}}
|
||||||
|
onClose={()=>{/*This is intentional (SonarQube)*/}}
|
||||||
|
onHelp={()=>{/*This is intentional (SonarQube)*/}}
|
||||||
|
onDataChange={()=>{/*This is intentional (SonarQube)*/}}
|
||||||
|
confirmOnCloseReset={false}
|
||||||
|
hasSQL={false}
|
||||||
|
disableSqlHelp={false}
|
||||||
|
disableDialogHelp={false}
|
||||||
|
/>
|
||||||
|
</Theme>);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
let backupServerSchemaObj = new BackupSchema(
|
let backupServerSchemaObj = new BackupSchema(
|
||||||
()=> getSectionSchema(),
|
()=> getSectionSchema(),
|
||||||
()=> getTypeObjSchema(),
|
()=> getTypeObjSchema(),
|
||||||
@ -73,7 +111,8 @@ describe('BackupSchema', ()=>{
|
|||||||
},
|
},
|
||||||
{server: {version: 11000}},
|
{server: {version: 11000}},
|
||||||
{serverInfo: {}},
|
{serverInfo: {}},
|
||||||
'server'
|
'server',
|
||||||
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
it('create server backup', ()=>{
|
it('create server backup', ()=>{
|
||||||
|
@ -2313,6 +2313,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@react-dnd/asap@npm:^4.0.0":
|
||||||
|
version: 4.0.1
|
||||||
|
resolution: "@react-dnd/asap@npm:4.0.1"
|
||||||
|
checksum: 757db3b5c436a95383b74f187f503321092909401ce9665d9cc1999308a44de22809bf8dbe82c9126bd73b72dd6665bbc4a788e864fc3c243c59f65057a4f87f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@react-dnd/asap@npm:^5.0.1":
|
"@react-dnd/asap@npm:^5.0.1":
|
||||||
version: 5.0.2
|
version: 5.0.2
|
||||||
resolution: "@react-dnd/asap@npm:5.0.2"
|
resolution: "@react-dnd/asap@npm:5.0.2"
|
||||||
@ -2320,6 +2327,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@react-dnd/invariant@npm:^2.0.0":
|
||||||
|
version: 2.0.0
|
||||||
|
resolution: "@react-dnd/invariant@npm:2.0.0"
|
||||||
|
checksum: ef1e989920d70b15c80dccb01af9b598081d76993311aa22d2e9a3ec41d10a88540eeec4b4de7a8b2a2ea52dfc3495ab45e39192c2d27795a9258bd6b79d000e
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@react-dnd/invariant@npm:^4.0.1":
|
"@react-dnd/invariant@npm:^4.0.1":
|
||||||
version: 4.0.2
|
version: 4.0.2
|
||||||
resolution: "@react-dnd/invariant@npm:4.0.2"
|
resolution: "@react-dnd/invariant@npm:4.0.2"
|
||||||
@ -2327,6 +2341,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@react-dnd/shallowequal@npm:^2.0.0":
|
||||||
|
version: 2.0.0
|
||||||
|
resolution: "@react-dnd/shallowequal@npm:2.0.0"
|
||||||
|
checksum: b5bbdc795d65945bb7ba2322bed5cf8d4c6fe91dced98c3b10e3d16822c438f558751135ff296f8d1aa1eaa9d0037dacab2b522ca5eb812175123b9996966dcb
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@react-dnd/shallowequal@npm:^4.0.1":
|
"@react-dnd/shallowequal@npm:^4.0.1":
|
||||||
version: 4.0.2
|
version: 4.0.2
|
||||||
resolution: "@react-dnd/shallowequal@npm:4.0.2"
|
resolution: "@react-dnd/shallowequal@npm:4.0.2"
|
||||||
@ -5855,6 +5876,17 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"dnd-core@npm:14.0.1":
|
||||||
|
version: 14.0.1
|
||||||
|
resolution: "dnd-core@npm:14.0.1"
|
||||||
|
dependencies:
|
||||||
|
"@react-dnd/asap": ^4.0.0
|
||||||
|
"@react-dnd/invariant": ^2.0.0
|
||||||
|
redux: ^4.1.1
|
||||||
|
checksum: dbc50727f53baad1cb1e0430a2a1b81c5c291389322f90fdc46edeb2fd49cc206ce4fa30a95afb53d88238945228e34866d7465f7ea49285c296baf883551301
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"dnd-core@npm:^16.0.1":
|
"dnd-core@npm:^16.0.1":
|
||||||
version: 16.0.1
|
version: 16.0.1
|
||||||
resolution: "dnd-core@npm:16.0.1"
|
resolution: "dnd-core@npm:16.0.1"
|
||||||
@ -12275,6 +12307,22 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"react-arborist@npm:^3.2.0":
|
||||||
|
version: 3.2.0
|
||||||
|
resolution: "react-arborist@npm:3.2.0"
|
||||||
|
dependencies:
|
||||||
|
react-dnd: ^14.0.3
|
||||||
|
react-dnd-html5-backend: ^14.0.1
|
||||||
|
react-window: ^1.8.6
|
||||||
|
redux: ^4.1.1
|
||||||
|
use-sync-external-store: ^1.2.0
|
||||||
|
peerDependencies:
|
||||||
|
react: ">= 16.14"
|
||||||
|
react-dom: ">= 16.14"
|
||||||
|
checksum: 452e8793520b4d69ad897b7bf2584a6ec763f182d6a4e942229e283fe000d579724a3c5c125504a59b66ef68ff898cef41575ccfca5e18a564c1f9e223ef62ca
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"react-aspen@npm:^1.1.0":
|
"react-aspen@npm:^1.1.0":
|
||||||
version: 1.2.0
|
version: 1.2.0
|
||||||
resolution: "react-aspen@npm:1.2.0"
|
resolution: "react-aspen@npm:1.2.0"
|
||||||
@ -12317,6 +12365,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"react-dnd-html5-backend@npm:^14.0.1":
|
||||||
|
version: 14.1.0
|
||||||
|
resolution: "react-dnd-html5-backend@npm:14.1.0"
|
||||||
|
dependencies:
|
||||||
|
dnd-core: 14.0.1
|
||||||
|
checksum: 6aa8d62c6b2288893b3f216d476d2f84495b40d33578ba9e3a5051dc093a71dc59700e6927ed7ac596ff8d7aa3b3f29404f7d173f844bd6144ed633403dd8e96
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"react-dnd-html5-backend@npm:^16.0.1":
|
"react-dnd-html5-backend@npm:^16.0.1":
|
||||||
version: 16.0.1
|
version: 16.0.1
|
||||||
resolution: "react-dnd-html5-backend@npm:16.0.1"
|
resolution: "react-dnd-html5-backend@npm:16.0.1"
|
||||||
@ -12326,6 +12383,31 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"react-dnd@npm:^14.0.3":
|
||||||
|
version: 14.0.5
|
||||||
|
resolution: "react-dnd@npm:14.0.5"
|
||||||
|
dependencies:
|
||||||
|
"@react-dnd/invariant": ^2.0.0
|
||||||
|
"@react-dnd/shallowequal": ^2.0.0
|
||||||
|
dnd-core: 14.0.1
|
||||||
|
fast-deep-equal: ^3.1.3
|
||||||
|
hoist-non-react-statics: ^3.3.2
|
||||||
|
peerDependencies:
|
||||||
|
"@types/hoist-non-react-statics": ">= 3.3.1"
|
||||||
|
"@types/node": ">= 12"
|
||||||
|
"@types/react": ">= 16"
|
||||||
|
react: ">= 16.14"
|
||||||
|
peerDependenciesMeta:
|
||||||
|
"@types/hoist-non-react-statics":
|
||||||
|
optional: true
|
||||||
|
"@types/node":
|
||||||
|
optional: true
|
||||||
|
"@types/react":
|
||||||
|
optional: true
|
||||||
|
checksum: 464e231de8c2b79546049a1600b67b1df0b7f762f23c688d3e9aeddbf334b1e64931ef91d0129df3c8be255f0af76e89426729dbcbefe4bdc09b0f665d2da368
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"react-dnd@npm:^16.0.1":
|
"react-dnd@npm:^16.0.1":
|
||||||
version: 16.0.1
|
version: 16.0.1
|
||||||
resolution: "react-dnd@npm:16.0.1"
|
resolution: "react-dnd@npm:16.0.1"
|
||||||
@ -12580,7 +12662,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"react-window@npm:^1.3.1, react-window@npm:^1.8.5":
|
"react-window@npm:^1.3.1, react-window@npm:^1.8.5, react-window@npm:^1.8.6":
|
||||||
version: 1.8.9
|
version: 1.8.9
|
||||||
resolution: "react-window@npm:1.8.9"
|
resolution: "react-window@npm:1.8.9"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -12710,7 +12792,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"redux@npm:^4.2.0":
|
"redux@npm:^4.1.1, redux@npm:^4.2.0":
|
||||||
version: 4.2.1
|
version: 4.2.1
|
||||||
resolution: "redux@npm:4.2.1"
|
resolution: "redux@npm:4.2.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -13112,6 +13194,7 @@ __metadata:
|
|||||||
raf: ^3.4.1
|
raf: ^3.4.1
|
||||||
rc-dock: ^3.2.9
|
rc-dock: ^3.2.9
|
||||||
react: ^17.0.1
|
react: ^17.0.1
|
||||||
|
react-arborist: ^3.2.0
|
||||||
react-aspen: ^1.1.0
|
react-aspen: ^1.1.0
|
||||||
react-checkbox-tree: ^1.7.2
|
react-checkbox-tree: ^1.7.2
|
||||||
react-data-grid: "https://github.com/pgadmin-org/react-data-grid.git#200d2f5e02de694e3e9ffbe177c279bc40240fb8"
|
react-data-grid: "https://github.com/pgadmin-org/react-data-grid.git#200d2f5e02de694e3e9ffbe177c279bc40240fb8"
|
||||||
@ -14928,6 +15011,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"use-sync-external-store@npm:^1.2.0":
|
||||||
|
version: 1.2.0
|
||||||
|
resolution: "use-sync-external-store@npm:1.2.0"
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
checksum: 5c639e0f8da3521d605f59ce5be9e094ca772bd44a4ce7322b055a6f58eeed8dda3c94cabd90c7a41fb6fa852210092008afe48f7038792fd47501f33299116a
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2, util-deprecate@npm:~1.0.1":
|
"util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2, util-deprecate@npm:~1.0.1":
|
||||||
version: 1.0.2
|
version: 1.0.2
|
||||||
resolution: "util-deprecate@npm:1.0.2"
|
resolution: "util-deprecate@npm:1.0.2"
|
||||||
|
Loading…
Reference in New Issue
Block a user