Remove the usage of MUI makeStyles as it doesn't support React 18. #7363

This commit is contained in:
Yogesh Mahajan 2024-06-06 17:13:12 +05:30 committed by GitHub
parent f66bd4bcfb
commit cc999ae5a5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
129 changed files with 3129 additions and 4066 deletions

View File

@ -81,7 +81,6 @@
"@mui/icons-material": "^5.15.10",
"@mui/lab": "^5.0.0-alpha.165",
"@mui/material": "^5.15.10",
"@mui/styles": "^5.15.10",
"@mui/x-date-pickers": "^6.19.7",
"@projectstorm/react-diagrams": "^6.6.1",
"@simonwep/pickr": "^1.5.1",

View File

@ -8,35 +8,27 @@
//////////////////////////////////////////////////////////////
import gettext from 'sources/gettext';
import { styled } from '@mui/material/styles';
import url_for from 'sources/url_for';
import React, { useEffect, useState, useRef } from 'react';
import { Box, Grid, InputLabel } from '@mui/material';
import { DefaultButton } from '../../../static/js/components/Buttons';
import { makeStyles } from '@mui/styles';
import { InputText } from '../../../static/js/components/FormComponents';
import getApiInstance from '../../../static/js/api_instance';
import { copyToClipboard } from '../../../static/js/clipboard';
import { useDelayedCaller } from '../../../static/js/custom_hooks';
import { usePgAdmin } from '../../../static/js/BrowserComponent';
const useStyles = makeStyles((theme)=>({
container: {
padding: '16px',
height: '100%',
display: 'flex',
flexDirection: 'column',
},
copyBtn: {
const StyledDefaultButton = styled(DefaultButton)(({theme}) => ({
'&.AboutComponent-copyBtn': {
marginRight: '1px',
float: 'right',
borderColor: theme.otherVars.borderColor,
fontSize: '13px',
},
}
}));
export default function AboutComponent() {
const classes = useStyles();
const containerRef = useRef();
const [aboutData, setAboutData] = useState([]);
const [copyText, setCopyText] = useState(gettext('Copy'));
@ -57,7 +49,7 @@ export default function AboutComponent() {
}, []);
return (
<Box className={classes.container} ref={containerRef}>
<Box sx={{ padding: '16px', height: '100%', display: 'flex',flexDirection: 'column'}} ref={containerRef}>
<Grid container spacing={0} style={{marginBottom: '8px'}}>
<Grid item lg={3} md={3} sm={3} xs={12}>
<InputLabel style={{fontWeight: 'bold'}}>{gettext('Version')}</InputLabel>
@ -134,11 +126,11 @@ export default function AboutComponent() {
<Box flexGrow="1" display="flex" flexDirection="column">
<Box>
<span style={{fontWeight: 'bold'}}>{gettext('Server Configuration')}</span>
<DefaultButton className={classes.copyBtn} onClick={()=>{
<StyledDefaultButton className='AboutComponent-copyBtn' onClick={()=>{
copyToClipboard(aboutData.settings);
setCopyText(gettext('Copied!'));
revertCopiedText(1500);
}}>{copyText}</DefaultButton>
}}>{copyText}</StyledDefaultButton>
</Box>
<Box flexGrow="1" paddingTop="1px">
<InputText style={{height: '100%'}} controlProps={{multiline: true, rows: 8}} inputStyle={{resize: 'none'}}

View File

@ -7,12 +7,12 @@
//
//////////////////////////////////////////////////////////////
import React, { useEffect, useMemo, useState } from 'react';
import { styled } from '@mui/material/styles';
import gettext from 'sources/gettext';
import PropTypes from 'prop-types';
import getApiInstance from 'sources/api_instance';
import PgTable from 'sources/components/PgTable';
import { InputCheckbox } from '../../../static/js/components/FormComponents';
import { makeStyles } from '@mui/styles';
import url_for from 'sources/url_for';
import Graphs from './Graphs';
import { Box, Tab, Tabs } from '@mui/material';
@ -48,88 +48,39 @@ function parseData(data) {
return res;
}
const useStyles = makeStyles((theme) => ({
emptyPanel: {
const Root = styled('div')(({theme}) => ({
height: '100%',
width: '100%',
'& .Dashboard-dashboardPanel': {
height: '100%',
background: theme.palette.grey[400],
'& .Dashboard-panelContent': {
...theme.mixins.panelBorder.all,
display: 'flex',
flexDirection: 'column',
overflow: 'hidden !important',
height: '100%',
width: '100%',
minHeight: '400px',
padding: '4px',
'& .Dashboard-mainTabs': {
...theme.mixins.panelBorder.all,
height: '100%',
display: 'flex',
flexDirection: 'column',
'& .Dashboard-terminateButton': {
color: theme.palette.error.main
},
},
},
},
'& .Dashboard-emptyPanel': {
width: '100%',
background: theme.otherVars.emptySpaceBg,
overflow: 'auto',
padding: '8px',
display: 'flex',
},
dashboardPanel: {
height: '100%',
background: theme.palette.grey[400],
},
cardHeader: {
padding: '0.25rem 0.5rem',
fontWeight: 'bold !important',
backgroundColor: theme.otherVars.tableBg,
borderBottom: '1px solid',
borderBottomColor: theme.otherVars.borderColor,
},
searchPadding: {
display: 'flex',
flex: 2.5,
},
component: {
padding: '8px',
},
searchInput: {
flex: 1,
},
panelIcon: {
width: '80%',
margin: '0 auto',
marginTop: '25px !important',
position: 'relative',
textAlign: 'center',
},
panelMessage: {
marginLeft: '0.5rem',
fontSize: '0.875rem',
},
panelContent: {
...theme.mixins.panelBorder.all,
display: 'flex',
flexDirection: 'column',
overflow: 'hidden !important',
height: '100%',
width: '100%',
minHeight: '400px',
padding: '4px'
},
mainTabs: {
...theme.mixins.panelBorder.all,
height: '100%',
display: 'flex',
flexDirection: 'column'
},
terminateButton: {
color: theme.palette.error.main
},
chartCard: {
border: '1px solid '+theme.otherVars.borderColor,
},
chartCardContent: {
padding: '0.25rem 0.5rem',
height: '165px',
display: 'flex',
},
chartLegend: {
marginLeft: 'auto',
'& > div': {
display: 'flex',
fontWeight: 'normal',
flexWrap: 'wrap',
'& .legend-value': {
marginLeft: '4px',
'& .legend-label': {
marginLeft: '4px',
}
}
}
}
}));
let activeQSchemaObj = new ActiveQuery();
@ -138,7 +89,7 @@ function Dashboard({
nodeItem, nodeData, node, treeNodeInfo,
...props
}) {
const classes = useStyles();
let tabs = [gettext('Sessions'), gettext('Locks'), gettext('Prepared Transactions')];
let mainTabs = [gettext('General'), gettext('System Statistics')];
if(treeNodeInfo?.server?.replication_type) {
@ -261,7 +212,7 @@ function Dashboard({
size="xs"
noBorder
icon={<CancelIcon />}
className={classes.terminateButton}
className='Dashboard-terminateButton'
onClick={() => {
if (
!canTakeAction(row, 'terminate')
@ -796,8 +747,8 @@ function Dashboard({
const showDefaultContents = () => {
return (
sid && !serverConnected ? (
<Box className={classes.dashboardPanel}>
<div className={classes.emptyPanel}>
<Box className='Dashboard-dashboardPanel'>
<div className='Dashboard-emptyPanel'>
<EmptyPanelMessage text={msg}/>
</div>
</Box>
@ -823,7 +774,7 @@ function Dashboard({
<InputCheckbox
label={gettext('Active sessions only')}
labelPlacement="end"
className={classes.searchInput}
className='Dashboard-searchInput'
onChange={(e) => {
e.preventDefault();
setActiveOnly(e.target.checked);
@ -834,11 +785,11 @@ function Dashboard({
};
return (
<>
(<Root>
{sid && serverConnected ? (
<Box className={classes.dashboardPanel}>
<Box className={classes.panelContent}>
<Box className={classes.mainTabs}>
<Box className='Dashboard-dashboardPanel'>
<Box className='Dashboard-panelContent'>
<Box className='Dashboard-mainTabs'>
<Box>
<Tabs
value={mainTabVal}
@ -850,7 +801,7 @@ function Dashboard({
</Tabs>
</Box>
{/* General Statistics */}
<TabPanel value={mainTabVal} index={0} classNameRoot={classes.tabPanel}>
<TabPanel value={mainTabVal} index={0}>
{!_.isUndefined(preferences) && preferences.show_graphs && (
<Graphs
key={sid + did}
@ -876,7 +827,7 @@ function Dashboard({
}}/>
</Tabs>
</Box>
<TabPanel value={tabVal} index={0} classNameRoot={classes.tabPanel}>
<TabPanel value={tabVal} index={0}>
<PgTable
caveTable={false}
tableNoBorder={false}
@ -886,7 +837,7 @@ function Dashboard({
schema={activeQSchemaObj}
></PgTable>
</TabPanel>
<TabPanel value={tabVal} index={1} classNameRoot={classes.tabPanel}>
<TabPanel value={tabVal} index={1}>
<PgTable
caveTable={false}
tableNoBorder={false}
@ -894,7 +845,7 @@ function Dashboard({
data={dashData}
></PgTable>
</TabPanel>
<TabPanel value={tabVal} index={2} classNameRoot={classes.tabPanel}>
<TabPanel value={tabVal} index={2}>
<PgTable
caveTable={false}
tableNoBorder={false}
@ -902,7 +853,7 @@ function Dashboard({
data={dashData}
></PgTable>
</TabPanel>
<TabPanel value={tabVal} index={3} classNameRoot={classes.tabPanel}>
<TabPanel value={tabVal} index={3}>
<PgTable
caveTable={false}
tableNoBorder={false}
@ -914,7 +865,7 @@ function Dashboard({
)}
</TabPanel>
{/* System Statistics */}
<TabPanel value={mainTabVal} index={1} classNameRoot={classes.tabPanel}>
<TabPanel value={mainTabVal} index={1} classNameRoot='Dashboard-tabPanel'>
<Box height="100%" display="flex" flexDirection="column">
{ssMsg === 'installed' && did === ldid ?
<ErrorBoundary>
@ -928,7 +879,7 @@ function Dashboard({
})}
</Tabs>
</Box>
<TabPanel value={systemStatsTabVal} index={0} classNameRoot={classes.tabPanel}>
<TabPanel value={systemStatsTabVal} index={0} classNameRoot='Dashboard-tabPanel'>
<Summary
key={sid + did}
preferences={preferences}
@ -938,7 +889,7 @@ function Dashboard({
serverConnected={serverConnected}
/>
</TabPanel>
<TabPanel value={systemStatsTabVal} index={1} classNameRoot={classes.tabPanel}>
<TabPanel value={systemStatsTabVal} index={1} classNameRoot='Dashboard-tabPanel'>
<CPU
key={sid + did}
preferences={preferences}
@ -948,7 +899,7 @@ function Dashboard({
serverConnected={serverConnected}
/>
</TabPanel>
<TabPanel value={systemStatsTabVal} index={2} classNameRoot={classes.tabPanel}>
<TabPanel value={systemStatsTabVal} index={2} classNameRoot='Dashboard-tabPanel'>
<Memory
key={sid + did}
preferences={preferences}
@ -958,7 +909,7 @@ function Dashboard({
serverConnected={serverConnected}
/>
</TabPanel>
<TabPanel value={systemStatsTabVal} index={3} classNameRoot={classes.tabPanel}>
<TabPanel value={systemStatsTabVal} index={3} classNameRoot='Dashboard-tabPanel'>
<Storage
key={sid + did}
preferences={preferences}
@ -970,14 +921,14 @@ function Dashboard({
/>
</TabPanel>
</ErrorBoundary> :
<div className={classes.emptyPanel}>
<div className='Dashboard-emptyPanel'>
<EmptyPanelMessage text={ssMsg}/>
</div>
}
</Box>
</TabPanel>
{/* Replication */}
<TabPanel value={mainTabVal} index={2} classNameRoot={classes.tabPanel}>
<TabPanel value={mainTabVal} index={2} classNameRoot='Dashboard-tabPanel'>
<Replication key={sid} sid={sid} node={node}
preferences={preferences} treeNodeInfo={treeNodeInfo} nodeData={nodeData} pageVisible={props.isActive} />
</TabPanel>
@ -985,7 +936,7 @@ function Dashboard({
</Box>
</Box>
) : showDefaultContents() }
</>
</Root>)
);
}

View File

@ -11,7 +11,6 @@ import React, { useState, useEffect, useRef, useReducer, useMemo } from 'react';
import PgTable from 'sources/components/PgTable';
import gettext from 'sources/gettext';
import PropTypes from 'prop-types';
import { makeStyles } from '@mui/styles';
import {getGCD, getEpoch} from 'sources/utils';
import ChartContainer from '../components/ChartContainer';
import { Box, Grid } from '@mui/material';
@ -23,40 +22,6 @@ import { getStatsUrl, transformData, statsReducer, X_AXIS_LENGTH } from './utili
import { toPrettySize } from '../../../../static/js/utils';
import SectionContainer from '../components/SectionContainer.jsx';
const useStyles = makeStyles((theme) => ({
autoResizer: {
height: '100% !important',
width: '100% !important',
background: theme.palette.grey[400],
padding: '8px',
overflowX: 'auto !important',
overflowY: 'hidden !important',
minHeight: '100%',
minWidth: '100%',
},
container: {
height: 'auto',
padding: '0px !important',
marginBottom: '4px',
},
fixedContainer: {
flexGrow: 1,
padding: '0px !important',
marginBottom: '4px',
},
tableContainer: {
padding: '6px',
width: '100%',
},
containerHeader: {
fontSize: '15px',
fontWeight: 'bold',
display: 'flex',
alignItems: 'center',
height: '100%',
},
}));
const chartsDefault = {
'cpu_stats': {'User Normal': [], 'User Niced': [], 'Kernel': [], 'Idle': []},
'la_stats': {'1 min': [], '5 mins': [], '10 mins': [], '15 mins': []},
@ -246,15 +211,14 @@ CPU.propTypes = {
};
export function CPUWrapper(props) {
const classes = useStyles();
const options = useMemo(()=>({
showDataPoints: props.showDataPoints,
showTooltip: props.showTooltip,
lineBorderWidth: props.lineBorderWidth,
}), [props.showTooltip, props.showDataPoints, props.lineBorderWidth]);
return (
<Box display="flex" flexDirection="column" height="100%">
<Grid container spacing={0.5} className={classes.container}>
(<Box display="flex" flexDirection="column" height="100%">
<Grid container spacing={0.5} sx={{marginBottom: '4px'}}>
<Grid item md={6}>
<ChartContainer id='cu-graph' title={gettext('CPU usage')} datasets={props.cpuUsageInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
<StreamingChart data={props.cpuUsageInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
@ -269,7 +233,6 @@ export function CPUWrapper(props) {
<Box flexGrow={1} minHeight={0}>
<SectionContainer title={gettext('Process CPU usage')}>
<PgTable
className={classes.autoResizer}
columns={props.tableHeader}
data={props.processCpuUsageStats}
msg={props.errorMsg}
@ -280,6 +243,7 @@ export function CPUWrapper(props) {
</SectionContainer>
</Box>
</Box>
)
);
}

View File

@ -10,7 +10,6 @@ import React, { useState, useEffect, useRef, useReducer, useMemo } from 'react';
import PgTable from 'sources/components/PgTable';
import gettext from 'sources/gettext';
import PropTypes from 'prop-types';
import { makeStyles } from '@mui/styles';
import {getGCD, getEpoch} from 'sources/utils';
import ChartContainer from '../components/ChartContainer';
import { Box, Grid } from '@mui/material';
@ -22,39 +21,6 @@ import { getStatsUrl, transformData, statsReducer, X_AXIS_LENGTH } from './utili
import { toPrettySize } from '../../../../static/js/utils';
import SectionContainer from '../components/SectionContainer.jsx';
const useStyles = makeStyles((theme) => ({
autoResizer: {
height: '100% !important',
width: '100% !important',
background: theme.palette.grey[400],
padding: '8px',
overflowX: 'auto !important',
overflowY: 'hidden !important',
minHeight: '100%',
minWidth: '100%',
},
container: {
height: 'auto',
padding: '0px !important',
marginBottom: '4px',
},
fixedContainer: {
flexGrow: 1,
padding: '0px !important',
marginBottom: '4px',
},
tableContainer: {
padding: '6px',
width: '100%',
},
containerHeader: {
fontSize: '15px',
fontWeight: 'bold',
display: 'flex',
alignItems: 'center',
height: '100%',
}
}));
const chartsDefault = {
'm_stats': {'Total': [], 'Used': [], 'Free': []},
@ -248,7 +214,7 @@ Memory.propTypes = {
};
export function MemoryWrapper(props) {
const classes = useStyles();
const options = useMemo(()=>({
showDataPoints: props.showDataPoints,
showTooltip: props.showTooltip,
@ -256,35 +222,36 @@ export function MemoryWrapper(props) {
}), [props.showTooltip, props.showDataPoints, props.lineBorderWidth]);
return (
<Box display="flex" flexDirection="column" height="100%">
<Grid container spacing={0.5} className={classes.container}>
<Grid item md={6}>
<ChartContainer id='m-graph' title={gettext('Memory')} datasets={props.memoryUsageInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
<StreamingChart data={props.memoryUsageInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options}
valueFormatter={toPrettySize}/>
</ChartContainer>
(
<Box display="flex" flexDirection="column" height="100%">
<Grid container spacing={0.5} sx={{marginBottom: '4px'}}>
<Grid item md={6}>
<ChartContainer id='m-graph' title={gettext('Memory')} datasets={props.memoryUsageInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
<StreamingChart data={props.memoryUsageInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options}
valueFormatter={toPrettySize}/>
</ChartContainer>
</Grid>
<Grid item md={6}>
<ChartContainer id='sm-graph' title={gettext('Swap memory')} datasets={props.swapMemoryUsageInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
<StreamingChart data={props.swapMemoryUsageInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options}
valueFormatter={toPrettySize}/>
</ChartContainer>
</Grid>
</Grid>
<Grid item md={6}>
<ChartContainer id='sm-graph' title={gettext('Swap memory')} datasets={props.swapMemoryUsageInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
<StreamingChart data={props.swapMemoryUsageInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options}
valueFormatter={toPrettySize}/>
</ChartContainer>
</Grid>
</Grid>
<Box flexGrow={1} minHeight={0}>
<SectionContainer title={gettext('Process memory usage')}>
<PgTable
className={classes.autoResizer}
columns={props.tableHeader}
data={props.processMemoryUsageStats}
msg={props.errorMsg}
type={'panel'}
caveTable={false}
tableNoBorder={false}
></PgTable>
</SectionContainer>
<Box flexGrow={1} minHeight={0}>
<SectionContainer title={gettext('Process memory usage')}>
<PgTable
columns={props.tableHeader}
data={props.processMemoryUsageStats}
msg={props.errorMsg}
type={'panel'}
caveTable={false}
tableNoBorder={false}
></PgTable>
</SectionContainer>
</Box>
</Box>
</Box>
)
);
}

View File

@ -7,9 +7,9 @@
//
//////////////////////////////////////////////////////////////
import React, { useState, useEffect, useRef, useReducer, useMemo } from 'react';
import { styled } from '@mui/material/styles';
import gettext from 'sources/gettext';
import PropTypes from 'prop-types';
import { makeStyles } from '@mui/styles';
import url_for from 'sources/url_for';
import {getGCD, getEpoch} from 'sources/utils';
import ChartContainer from '../components/ChartContainer';
@ -21,52 +21,29 @@ import axios from 'axios';
import { BarChart, PieChart } from '../../../../static/js/chartjs';
import { getStatsUrl, transformData, X_AXIS_LENGTH } from './utility.js';
import { toPrettySize } from '../../../../static/js/utils';
import clsx from 'clsx';
import { commonTableStyles } from '../../../../static/js/Theme';
import Table from '../../../../static/js/components/Table';
import SectionContainer from '../components/SectionContainer.jsx';
const useStyles = makeStyles((theme) => ({
container: {
height: 'auto',
padding: '8px',
marginBottom: '6px',
},
driveContainer: {
width: '100%',
},
diskInfoCharts: {
marginBottom: '4px',
},
containerHeaderText: {
fontWeight: 'bold',
padding: '4px 8px',
},
tableContainer: {
const Root = styled('div')(({theme}) => ({
'& .Storage-tableContainer': {
background: theme.otherVars.tableBg,
padding: '0px',
border: '1px solid '+theme.otherVars.borderColor,
borderCollapse: 'collapse',
borderRadius: '4px',
overflow: 'auto',
width: '100%',
marginBottom: '4px',
},
tableWhiteSpace: {
'& td, & th': {
whiteSpace: 'break-spaces !important',
margin: '4px 4px 4px 4px',
'& .Storage-containerHeaderText': {
fontWeight: 'bold',
padding: '4px 8px',
},
'& .Storage-tableWhiteSpace': {
'& td, & th': {
whiteSpace: 'break-spaces !important',
},
},
},
driveContainerHeader: {
height: 'auto',
padding: '5px 0px 0px 0px',
background: theme.otherVars.tableBg,
marginBottom: '5px',
borderRadius: '4px 4px 0px 0px',
},
driveContainerBody: {
height: 'auto',
padding: '0px',
background: theme.otherVars.tableBg,
borderRadius: '0px 0px 4px 4px',
},
}));
@ -122,12 +99,11 @@ const chartsDefault = {
const DiskStatsTable = (props) => {
const tableClasses = commonTableStyles();
const classes = useStyles();
const tableHeader = props.tableHeader;
const data = props.data;
return (
<table className={clsx(tableClasses.table, classes.tableWhiteSpace)}>
<Table classNameRoot='Storage-tableWhiteSpace'>
<thead>
<tr>
{tableHeader.map((item, index) => (
@ -144,7 +120,7 @@ const DiskStatsTable = (props) => {
</tr>
))}
</tbody>
</table>
</Table>
);
};
@ -358,7 +334,7 @@ export default function Storage({preferences, sid, did, pageVisible, enablePoll=
}, enablePoll ? pollDelay : -1);
return (
<>
(<Root>
<div data-testid='graph-poll-delay' style={{display: 'none'}}>{pollDelay}</div>
{chartDrawnOnce &&
<StorageWrapper
@ -373,7 +349,7 @@ export default function Storage({preferences, sid, did, pageVisible, enablePoll=
isTest={false}
/>
}
</>
</Root>)
);
}
@ -387,7 +363,7 @@ Storage.propTypes = {
};
export function StorageWrapper(props) {
const classes = useStyles();
const options = useMemo(()=>({
showDataPoints: props.showDataPoints,
showTooltip: props.showTooltip,
@ -414,10 +390,10 @@ export function StorageWrapper(props) {
},
};
return (
<>
<div className={classes.tableContainer}>
<div className={classes.containerHeaderText}>{gettext('Disk information')}</div>
return (
<Root>
<div className='Storage-tableContainer'>
<div className='Storage-containerHeaderText'>{gettext('Disk information')}</div>
<DiskStatsTable tableHeader={props.tableHeader} data={props.diskStats} />
</div>
<Grid container spacing={0.5} sx={{marginBottom: '4px'}}>
@ -511,7 +487,7 @@ export function StorageWrapper(props) {
</Grid>
</SectionContainer>
))}
</>
</Root>
);
}

View File

@ -7,9 +7,9 @@
//
//////////////////////////////////////////////////////////////
import React, { useState, useEffect, useRef, useReducer, useMemo } from 'react';
import { styled } from '@mui/material/styles';
import gettext from 'sources/gettext';
import PropTypes from 'prop-types';
import { makeStyles } from '@mui/styles';
import url_for from 'sources/url_for';
import getApiInstance from 'sources/api_instance';
import {getGCD, getEpoch} from 'sources/utils';
@ -20,31 +20,22 @@ import StreamingChart from '../../../../static/js/components/PgChart/StreamingCh
import {useInterval, usePrevious} from 'sources/custom_hooks';
import axios from 'axios';
import { getStatsUrl, transformData,statsReducer, X_AXIS_LENGTH } from './utility.js';
import clsx from 'clsx';
import { commonTableStyles } from '../../../../static/js/Theme';
import Table from '../../../../static/js/components/Table';
const useStyles = makeStyles((theme) => ({
container: {
height: 'auto',
padding: '0px !important',
marginBottom: '4px',
},
tableContainer: {
const Root = styled('div')(({theme}) => ({
'& .Summary-tableContainer': {
background: theme.otherVars.tableBg,
padding: '0px',
border: '1px solid '+theme.otherVars.borderColor,
borderCollapse: 'collapse',
borderRadius: '4px',
overflow: 'hidden',
},
chartContainer: {
padding: '4px',
},
containerHeader: {
fontWeight: 'bold',
marginBottom: '0px',
borderBottom: '1px solid '+theme.otherVars.borderColor,
padding: '4px 8px',
'& .Summary-containerHeader': {
fontWeight: 'bold',
marginBottom: '0px',
borderBottom: '1px solid '+theme.otherVars.borderColor,
padding: '4px 8px',
}
},
}));
@ -53,10 +44,9 @@ const chartsDefault = {
};
const SummaryTable = (props) => {
const tableClasses = commonTableStyles();
const data = props.data;
return (
<table className={clsx(tableClasses.table)}>
<Table >
<thead>
<tr>
<th>Property</th>
@ -71,7 +61,7 @@ const SummaryTable = (props) => {
</tr>
))}
</tbody>
</table>
</Table>
);
};
@ -224,7 +214,7 @@ export default function Summary({preferences, sid, did, pageVisible, enablePoll=
}, enablePoll ? pollDelay : -1);
return (
<>
(<Root>
<div data-testid='graph-poll-delay' style={{display: 'none'}}>{pollDelay}</div>
{chartDrawnOnce &&
<SummaryWrapper
@ -238,7 +228,7 @@ export default function Summary({preferences, sid, did, pageVisible, enablePoll=
isTest={false}
/>
}
</>
</Root>)
);
}
@ -251,7 +241,7 @@ Summary.propTypes = {
};
function SummaryWrapper(props) {
const classes = useStyles();
const options = useMemo(()=>({
showDataPoints: props.showDataPoints,
showTooltip: props.showTooltip,
@ -259,23 +249,23 @@ function SummaryWrapper(props) {
}), [props.showTooltip, props.showDataPoints, props.lineBorderWidth]);
return (
<>
<Grid container spacing={0.5} className={classes.container}>
<Grid container spacing={0.5} sx={{height: 'auto', padding: '0px !important', marginBottom: '4px'}}>
<Grid item md={6}>
<div className={classes.tableContainer}>
<div className={classes.containerHeader}>{gettext('OS information')}</div>
<div className='Summary-tableContainer'>
<div className='Summary-containerHeader'>{gettext('OS information')}</div>
<SummaryTable data={props.osStats} />
</div>
</Grid>
<Grid item md={6}className={classes.chartContainer}>
<Grid item md={6} sx={{padding: '4px'}}>
<ChartContainer id='hpc-graph' title={gettext('Process & handle count')} datasets={props.processHandleCount.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
<StreamingChart data={props.processHandleCount} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} showSecondAxis={true} />
</ChartContainer>
</Grid>
</Grid>
<Grid container spacing={0.5} className={classes.container}>
<Grid container spacing={0.5} sx={{height: 'auto', padding: '0px !important', marginBottom: '4px'}}>
<Grid item md={6}>
<div className={classes.tableContainer}>
<div className={classes.containerHeader}>{gettext('CPU information')}</div>
<div className='Summary-tableContainer'>
<div className='Summary-containerHeader'>{gettext('CPU information')}</div>
<SummaryTable data={props.cpuStats} />
</div>
</Grid>

View File

@ -8,96 +8,111 @@
//////////////////////////////////////////////////////////////
import React from 'react';
import { styled } from '@mui/material/styles';
import gettext from 'sources/gettext';
import _ from 'lodash';
import PropTypes from 'prop-types';
import { makeStyles } from '@mui/styles';
import pgAdmin from 'sources/pgadmin';
import PgAdminLogo from './PgAdminLogo';
import { Link } from '@mui/material';
const useStyles = makeStyles((theme) => ({
emptyPanel: {
background: theme.palette.grey[400],
overflow: 'hidden',
padding: '8px',
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
height: '100%'
},
dashboardContainer: {
const Root = styled('div')(({theme}) => ({
background: theme.palette.grey[400],
overflow: 'hidden',
padding: '8px',
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
height: '100%',
'& .WelcomeDashboard-dashboardContainer': {
paddingBottom: '8px',
minHeight: '100%'
},
card: {
position: 'relative',
minWidth: 0,
wordWrap: 'break-word',
backgroundColor: theme.otherVars.tableBg,
backgroundClip: 'border-box',
border: '1px solid' + theme.otherVars.borderColor,
borderRadius: theme.shape.borderRadius,
marginTop: 8
},
row: {
marginRight: '-8px',
marginLeft: '-8px'
},
rowContent: {
display: 'flex',
flexWrap: 'wrap',
marginRight: '-7.5px',
marginLeft: '-7.5px'
},
cardHeader: {
padding: '0.25rem 0.5rem',
fontWeight: 'bold',
backgroundColor: theme.otherVars.tableBg,
borderBottom: '1px solid',
borderBottomColor: theme.otherVars.borderColor,
},
dashboardLink: {
color: theme.otherVars.colorFg + '!important',
flex: '0 0 50%',
maxWidth: '50%',
textAlign: 'center',
cursor: 'pointer'
},
gettingStartedLink: {
flex: '0 0 25%',
maxWidth: '50%',
textAlign: 'center',
cursor: 'pointer'
},
link: {
color: theme.palette.text.primary + '!important',
},
cardColumn: {
flex: '0 0 100%',
maxWidth: '100%',
margin: '8px'
},
cardBody: {
flex: '1 1 auto',
minHeight: '1px',
padding: '0.5rem !important',
},
welcomeLogo: {
width: '400px',
'& .app-name': {
fill: theme.otherVars.colorBrand
minHeight: '100%',
'& .WelcomeDashboard-row': {
marginRight: '-8px',
marginLeft: '-8px'
},
'& .app-name-underline': {
stroke: theme.palette.text.primary
'& .WelcomeDashboard-cardColumn': {
flex: '0 0 100%',
maxWidth: '100%',
margin: '8px',
'& .WelcomeDashboard-card': {
position: 'relative',
minWidth: 0,
wordWrap: 'break-word',
backgroundColor: theme.otherVars.tableBg,
backgroundClip: 'border-box',
border: '1px solid' + theme.otherVars.borderColor,
borderRadius: theme.shape.borderRadius,
marginTop: 8,
'& .WelcomeDashboard-cardHeader': {
padding: '0.25rem 0.5rem',
fontWeight: 'bold',
backgroundColor: theme.otherVars.tableBg,
borderBottom: '1px solid',
borderBottomColor: theme.otherVars.borderColor,
},
'& .WelcomeDashboard-cardBody': {
flex: '1 1 auto',
minHeight: '1px',
padding: '0.5rem !important',
'& .WelcomeDashboard-welcomeLogo': {
width: '400px',
'& .app-name': {
fill: theme.otherVars.colorBrand
},
'& .app-name-underline': {
stroke: theme.palette.text.primary
},
'& .app-tagline': {
fill: theme.palette.text.primary
}
},
'& .WelcomeDashboard-rowContent': {
display: 'flex',
flexWrap: 'wrap',
marginRight: '-7.5px',
marginLeft: '-7.5px',
'& .WelcomeDashboard-dashboardLink': {
color: theme.palette.text.primary + ' !important',
flex: '0 0 50%',
maxWidth: '50%',
textAlign: 'center',
cursor: 'pointer',
'& .WelcomeDashboard-link': {
color: theme.palette.text.primary + ' !important',
'& .WelcomeDashboard-dashboardIcon': {
color: theme.otherVars.colorBrand
}
},
},
'& .WelcomeDashboard-gettingStartedLink': {
flex: '0 0 25%',
maxWidth: '50%',
textAlign: 'center',
cursor: 'pointer',
'& .WelcomeDashboard-link': {
color: theme.palette.text.primary + ' !important',
'& .WelcomeDashboard-dashboardIcon': {
color: theme.otherVars.colorBrand
}
},
},
},
},
},
},
'& .app-tagline': {
fill: theme.palette.text.primary
}
},
dashboardIcon: {
color: theme.otherVars.colorBrand
},
}));
@ -130,18 +145,15 @@ function AddNewServer(pgBrowser) {
}
export default function WelcomeDashboard({ pgBrowser }) {
const classes = useStyles();
return (
<div className={classes.emptyPanel}>
<div className={classes.dashboardContainer}>
<div className={classes.row}>
<div className={classes.cardColumn}>
<div className={classes.card}>
<div className={classes.cardHeader}>{gettext('Welcome')}</div>
<div className={classes.cardBody}>
<div className={classes.welcomeLogo}>
<Root>
<div className='WelcomeDashboard-dashboardContainer'>
<div className='WelcomeDashboard-row'>
<div className='WelcomeDashboard-cardColumn'>
<div className='WelcomeDashboard-card'>
<div className='WelcomeDashboard-cardHeader'>{gettext('Welcome')}</div>
<div className='WelcomeDashboard-cardBody'>
<div className='WelcomeDashboard-welcomeLogo'>
<PgAdminLogo />
</div>
<h4>
@ -157,15 +169,15 @@ export default function WelcomeDashboard({ pgBrowser }) {
</div>
</div>
</div>
<div className={classes.row}>
<div className={classes.cardColumn}>
<div className={classes.card}>
<div className={classes.cardHeader}>{gettext('Quick Links')}</div>
<div className={classes.cardBody}>
<div className={classes.rowContent}>
<div className={classes.dashboardLink}>
<Link onClick={() => { AddNewServer(pgBrowser); }} className={classes.link}>
<div className={classes.dashboardIcon}>
<div className='WelcomeDashboard-row'>
<div className='WelcomeDashboard-cardColumn'>
<div className='WelcomeDashboard-card'>
<div className='WelcomeDashboard-cardHeader'>{gettext('Quick Links')}</div>
<div className='WelcomeDashboard-cardBody'>
<div className='WelcomeDashboard-rowContent'>
<div className='WelcomeDashboard-dashboardLink'>
<Link onClick={() => { AddNewServer(pgBrowser); }} className='WelcomeDashboard-link'>
<div className='WelcomeDashboard-dashboardIcon'>
<span
className="fa fa-4x fa-server"
aria-hidden="true"
@ -174,9 +186,9 @@ export default function WelcomeDashboard({ pgBrowser }) {
{gettext('Add New Server')}
</Link>
</div>
<div className={classes.dashboardLink}>
<Link onClick={() => pgAdmin.Preferences.show()} className={classes.link}>
<div className={classes.dashboardIcon}>
<div className='WelcomeDashboard-dashboardLink'>
<Link onClick={() => pgAdmin.Preferences.show()} className='WelcomeDashboard-link'>
<div className='WelcomeDashboard-dashboardIcon'>
<span
id="mnu_preferences"
className="fa fa-4x fa-cogs"
@ -191,19 +203,19 @@ export default function WelcomeDashboard({ pgBrowser }) {
</div>
</div>
</div>
<div className={classes.row}>
<div className={classes.cardColumn}>
<div className={classes.card}>
<div className={classes.cardHeader}>{gettext('Getting Started')}</div>
<div className={classes.cardBody}>
<div className={classes.rowContent}>
<div className={classes.gettingStartedLink}>
<div className='WelcomeDashboard-row'>
<div className='WelcomeDashboard-cardColumn'>
<div className='WelcomeDashboard-card'>
<div className='WelcomeDashboard-cardHeader'>{gettext('Getting Started')}</div>
<div className='WelcomeDashboard-cardBody'>
<div className='WelcomeDashboard-rowContent'>
<div className='WelcomeDashboard-gettingStartedLink'>
<a
href="https://www.postgresql.org/docs"
target="postgres_help"
className={classes.link}
className='WelcomeDashboard-link'
>
<div className={classes.dashboardIcon}>
<div className='WelcomeDashboard-dashboardIcon'>
<span
className="fa fa-4x dashboard-pg-doc"
aria-hidden="true"
@ -212,9 +224,9 @@ export default function WelcomeDashboard({ pgBrowser }) {
{gettext('PostgreSQL Documentation')}
</a>
</div>
<div className={classes.gettingStartedLink}>
<a href="https://www.pgadmin.org" target="pgadmin_website" className={classes.link}>
<div className={classes.dashboardIcon}>
<div className='WelcomeDashboard-gettingStartedLink'>
<a href="https://www.pgadmin.org" target="pgadmin_website" className='WelcomeDashboard-link'>
<div className='WelcomeDashboard-dashboardIcon'>
<span
className="fa fa-4x fa-globe"
aria-hidden="true"
@ -223,13 +235,13 @@ export default function WelcomeDashboard({ pgBrowser }) {
{gettext('pgAdmin Website')}
</a>
</div>
<div className={classes.gettingStartedLink}>
<div className='WelcomeDashboard-gettingStartedLink'>
<a
href="https://planet.postgresql.org"
target="planet_website"
className={classes.link}
className='WelcomeDashboard-link'
>
<div className={classes.dashboardIcon}>
<div className='WelcomeDashboard-dashboardIcon'>
<span
className="fa fa-4x fa-book"
aria-hidden="true"
@ -238,13 +250,13 @@ export default function WelcomeDashboard({ pgBrowser }) {
{gettext('Planet PostgreSQL')}
</a>
</div>
<div className={classes.gettingStartedLink}>
<div className='WelcomeDashboard-gettingStartedLink'>
<a
href="https://www.postgresql.org/community"
target="postgres_website"
className={classes.link}
className='WelcomeDashboard-link'
>
<div className={classes.dashboardIcon}>
<div className='WelcomeDashboard-dashboardIcon'>
<span
className="fa fa-4x fa-users"
aria-hidden="true"
@ -259,7 +271,7 @@ export default function WelcomeDashboard({ pgBrowser }) {
</div>
</div>
</div>
</div>
</Root>
);
}

View File

@ -7,29 +7,20 @@
//
//////////////////////////////////////////////////////////////
import React from 'react';
import { styled } from '@mui/material/styles';
import PropTypes from 'prop-types';
import { Box, Card, CardContent, CardHeader } from '@mui/material';
import { makeStyles } from '@mui/styles';
import EmptyPanelMessage from '../../../../static/js/components/EmptyPanelMessage';
const useStyles = makeStyles((theme) => ({
chartCard: {
border: '1px solid '+theme.otherVars.borderColor,
height: '100%',
},
chartCardContent: {
padding: '0.25rem 0.5rem',
height: '165px',
display: 'flex',
},
chartLegend: {
const StyledCard = styled(Card)(({theme}) => ({
border: '1px solid '+theme.otherVars.borderColor,
height: '100%',
'& .ChartContainer-chartLegend': {
marginLeft: 'auto',
'& > div': {
display: 'flex',
fontWeight: 'normal',
'& .legend-value': {
marginLeft: '4px',
'& .legend-label': {
@ -37,17 +28,22 @@ const useStyles = makeStyles((theme) => ({
}
}
}
},
'& .ChartContainer-cardContent': {
padding: '0.25rem 0.5rem',
height: '165px',
display: 'flex',
}
}));
export default function ChartContainer(props) {
const classes = useStyles();
return (
<Card className={classes.chartCard} elevation={0} data-testid="chart-container">
<StyledCard elevation={0} data-testid="chart-container">
<CardHeader title={<Box display="flex" justifyContent="space-between">
<div id={props.id}>{props.title}</div>
<div className={classes.chartLegend}>
<div className='ChartContainer-chartLegend'>
<div style={{display: 'flex', flexWrap: 'wrap'}}>
{props.datasets?.map((datum)=>(
<div className="legend-value" key={datum.label}>
@ -58,11 +54,11 @@ export default function ChartContainer(props) {
</div>
</div>
</Box>} />
<CardContent className={classes.chartCardContent}>
<CardContent className='ChartContainer-cardContent'>
{!props.errorMsg && !props.isTest && props.children}
{props.errorMsg && <EmptyPanelMessage text={props.errorMsg}/>}
</CardContent>
</Card>
</StyledCard>
);
}

View File

@ -8,36 +8,33 @@
//////////////////////////////////////////////////////////////
import React from 'react';
import { styled } from '@mui/material/styles';
import gettext from 'sources/gettext';
import CachedOutlinedIcon from '@mui/icons-material/CachedOutlined';
import { PgIconButton } from '../../../../static/js/components/Buttons';
import { makeStyles } from '@mui/styles';
import PropTypes from 'prop-types';
const useStyles = makeStyles((theme) => ({
refreshButton: {
const StyledPgIconButton = styled(PgIconButton)(({theme}) => ({
'&.RefreshButtons': {
marginLeft: 'auto',
height: '1.9rem',
width: '2.2rem',
height: '1.9rem !important',
width: '2.2rem !important',
...theme.mixins.panelBorder,
},
}
}));
export default function RefreshButton({onClick}) {
const classes = useStyles();
return(
<PgIconButton
return (
<StyledPgIconButton
size="xs"
noBorder
className={classes.refreshButton}
className='RefreshButtons'
icon={<CachedOutlinedIcon />}
onClick={onClick}
color="default"
aria-label="Refresh"
title={gettext('Refresh')}
></PgIconButton>
></StyledPgIconButton>
);
}

View File

@ -7,42 +7,37 @@
//
//////////////////////////////////////////////////////////////
import React from 'react';
import { styled } from '@mui/material/styles';
import PropTypes from 'prop-types';
import { Box } from '@mui/material';
import { makeStyles } from '@mui/styles';
const useStyles = makeStyles((theme) => ({
root: {
...theme.mixins.panelBorder.all,
display: 'flex',
flexDirection: 'column',
overflow: 'hidden !important',
height: '100%',
width: '100%',
minHeight: '400px',
borderRadius: theme.shape.borderRadius,
},
cardHeader: {
const StyledBox = styled(Box)(({theme}) => ({
...theme.mixins.panelBorder.all,
display: 'flex',
flexDirection: 'column',
overflow: 'hidden !important',
height: '100%',
width: '100%',
minHeight: '400px',
borderRadius: theme.shape.borderRadius,
'& .SectionContainer-cardHeader': {
backgroundColor: theme.otherVars.tableBg,
borderBottom: '1px solid',
borderBottomColor: theme.otherVars.borderColor,
display: 'flex',
alignItems: 'center',
'& .SectionContainer-cardTitle': {
padding: '0.25rem 0.5rem',
fontWeight: 'bold',
}
},
cardTitle: {
padding: '0.25rem 0.5rem',
fontWeight: 'bold',
}
}));
export default function SectionContainer({title, titleExtras, children, style}) {
const classes = useStyles();
return (
<Box className={classes.root} style={style}>
<Box className={classes.cardHeader} title={title}>
<div className={classes.cardTitle}>{title}</div>
<StyledBox style={style}>
<Box className='SectionContainer-cardHeader' title={title}>
<div className='SectionContainer-cardTitle'>{title}</div>
<div style={{marginLeft: 'auto'}}>
{titleExtras}
</div>
@ -50,7 +45,7 @@ export default function SectionContainer({title, titleExtras, children, style})
<Box height="100%" display="flex" flexDirection="column" minHeight={0}>
{children}
</Box>
</Box>
</StyledBox>
);
}

View File

@ -1,24 +1,28 @@
import { Box } from '@mui/material';
import { makeStyles } from '@mui/styles';
import { styled } from '@mui/material/styles';
import React from 'react';
import CloseIcon from '@mui/icons-material/CloseRounded';
import { DefaultButton, PgIconButton } from '../../../../static/js/components/Buttons';
import clsx from 'clsx';
import DescriptionOutlinedIcon from '@mui/icons-material/DescriptionOutlined';
import { BgProcessManagerProcessState } from './BgProcessConstants';
import PropTypes from 'prop-types';
import gettext from 'sources/gettext';
import pgAdmin from 'sources/pgadmin';
const useStyles = makeStyles((theme)=>({
container: {
borderRadius: theme.shape.borderRadius,
padding: '0.25rem 1rem 1rem',
minWidth: '325px',
...theme.mixins.panelBorder.all,
const StyledBox = styled(Box)(({theme}) => ({
borderRadius: theme.shape.borderRadius,
padding: '0.25rem 1rem 1rem',
minWidth: '325px',
...theme.mixins.panelBorder.all,
'&.BgProcessNotify-containerSuccess': {
borderColor: theme.palette.success.main,
backgroundColor: theme.palette.success.light,
},
containerHeader: {
'&.BgProcessNotify-containerError': {
borderColor: theme.palette.error.main,
backgroundColor: theme.palette.error.light,
},
'& .BgProcessNotify-containerHeader': {
height: '32px',
display: 'flex',
justifyContent: 'space-between',
@ -26,36 +30,27 @@ const useStyles = makeStyles((theme)=>({
alignItems: 'center',
borderTopLeftRadius: 'inherit',
borderTopRightRadius: 'inherit',
'& .BgProcessNotify-iconSuccess': {
color: theme.palette.success.main,
},
'& .BgProcessNotify-iconError': {
color: theme.palette.error.main,
}
},
containerBody: {
'&.BgProcessNotify-containerBody': {
marginTop: '1rem',
overflowWrap: 'break-word',
},
containerSuccess: {
borderColor: theme.palette.success.main,
backgroundColor: theme.palette.success.light,
},
iconSuccess: {
color: theme.palette.success.main,
},
containerError: {
borderColor: theme.palette.error.main,
backgroundColor: theme.palette.error.light,
},
iconError: {
color: theme.palette.error.main,
},
}));
function ProcessNotifyMessage({title, desc, onClose, onViewProcess, success=true, dataTestSuffix=''}) {
const classes = useStyles();
return (
<Box className={clsx(classes.container, (success ? classes.containerSuccess : classes.containerError))} data-test={'process-popup-' + dataTestSuffix}>
<Box display="flex" justifyContent="space-between" className={classes.containerHeader}>
<StyledBox className={(success ? 'BgProcessNotify-containerSuccess' : 'BgProcessNotify-containerError')} data-test={'process-popup-' + dataTestSuffix}>
<Box display="flex" justifyContent="space-between" className='BgProcessNotify-containerHeader'>
<Box marginRight={'1rem'}>{title}</Box>
<PgIconButton size="xs" noBorder icon={<CloseIcon />} onClick={onClose} title={'Close'} className={success ? classes.iconSuccess : classes.iconError} />
<PgIconButton size="xs" noBorder icon={<CloseIcon />} onClick={onClose} title={'Close'} className={success ? 'BgProcessNotify-iconSuccess' : 'BgProcessNotify-iconError'} />
</Box>
<Box className={classes.containerBody}>
<Box className='BgProcessNotify-containerBody'>
<Box>{desc}</Box>
<Box marginTop={'1rem'} display="flex">
<DefaultButton startIcon={<DescriptionOutlinedIcon />} onClick={()=>{
@ -64,7 +59,7 @@ function ProcessNotifyMessage({title, desc, onClose, onViewProcess, success=true
}}>View Processes</DefaultButton>
</Box>
</Box>
</Box>
</StyledBox>
);
}
ProcessNotifyMessage.propTypes = {

View File

@ -8,10 +8,10 @@
//////////////////////////////////////////////////////////////
import React, { useState, useMemo } from 'react';
import { styled } from '@mui/material/styles';
import gettext from 'sources/gettext';
import url_for from 'sources/url_for';
import { Box } from '@mui/material';
import { makeStyles } from '@mui/styles';
import PropTypes from 'prop-types';
import { MESSAGE_TYPE, NotifierMessage } from '../../../../static/js/components/FormComponents';
import { BgProcessManagerProcessState } from './BgProcessConstants';
@ -24,16 +24,14 @@ import pgAdmin from 'sources/pgadmin';
import FolderSharedRoundedIcon from '@mui/icons-material/FolderSharedRounded';
const useStyles = makeStyles((theme)=>({
container: {
backgroundColor: theme.palette.background.default,
height: '100%',
display: 'flex',
flexDirection: 'column',
padding: '8px',
userSelect: 'text',
},
cmd: {
const StyledBox = styled(Box)(({theme}) => ({
backgroundColor: theme.palette.background.default,
height: '100%',
display: 'flex',
flexDirection: 'column',
padding: '8px',
userSelect: 'text',
'& .ProcessDetails-cmd': {
...theme.mixins.panelBorder.all,
borderRadius: theme.shape.borderRadius,
backgroundColor: theme.otherVars.inputDisabledBg,
@ -41,19 +39,7 @@ const useStyles = makeStyles((theme)=>({
margin: '8px 0px',
padding: '4px',
},
logs: {
flexGrow: 1,
borderRadius: theme.shape.borderRadius,
padding: '4px',
overflow: 'auto',
textOverflow: 'wrap-text',
margin: '8px 0px',
...theme.mixins.panelBorder.all,
},
logErr: {
color: theme.palette.error.main,
},
terminateBtn: {
'& .ProcessDetails-terminateBtn': {
backgroundColor: theme.palette.error.main,
color: theme.palette.error.contrastText,
border: 0,
@ -65,7 +51,20 @@ const useStyles = makeStyles((theme)=>({
color: theme.palette.error.contrastText + ' !important',
border: 0,
}
}
},
'& .ProcessDetails-logs': {
flexGrow: 1,
borderRadius: theme.shape.borderRadius,
padding: '4px',
overflow: 'auto',
textOverflow: 'wrap-text',
margin: '8px 0px',
...theme.mixins.panelBorder.all,
'& .ProcessDetails-logErr': {
color: theme.palette.error.main,
},
},
}));
async function getDetailedStatus(api, jobId, out, err) {
@ -80,7 +79,6 @@ async function getDetailedStatus(api, jobId, out, err) {
}
export default function ProcessDetails({data}) {
const classes = useStyles();
const api = useMemo(()=>getApiInstance());
const [logs, setLogs] = useState(null);
const [completed, setCompleted] = useState(false);
@ -149,15 +147,15 @@ export default function ProcessDetails({data}) {
const errRe = new RegExp(': (' + gettext('error') + '|' + gettext('fatal') + '):', 'i');
return (
<Box display="flex" flexDirection="column" className={classes.container} data-test="process-details">
<StyledBox display="flex" flexDirection="column" data-test="process-details">
<Box data-test="process-message">{data.details?.message}</Box>
{data.details?.cmd && <>
<Box>{gettext('Running command')}:</Box>
<Box data-test="process-cmd" className={classes.cmd}>{data.details.cmd}</Box>
<Box data-test="process-cmd" className='ProcessDetails-cmd'>{data.details.cmd}</Box>
</>}
{data.details?.query && <>
<Box>{gettext('Running query')}:</Box>
<Box data-test="process-cmd" className={classes.cmd}>{data.details.query}</Box>
<Box data-test="process-cmd" className='ProcessDetails-cmd'>{data.details.query}</Box>
</>}
<Box display="flex" justifyContent="space-between" alignItems="center" flexWrap="wrap">
<Box><span><AccessTimeRoundedIcon /> {gettext('Start time')}: {new Date(data.stime).toString()}</span></Box>
@ -167,12 +165,12 @@ export default function ProcessDetails({data}) {
pgAdmin.Tools.FileManager.openStorageManager(data.current_storage_dir);
}} style={{marginRight: '4px'}} />}
<DefaultButton disabled={process_state != BgProcessManagerProcessState.PROCESS_STARTED || data.server_id != null}
startIcon={<HighlightOffRoundedIcon />} className={classes.terminateBtn} onClick={onStopProcess}>
startIcon={<HighlightOffRoundedIcon />} className='ProcessDetails-terminateBtn' onClick={onStopProcess}>
Stop Process
</DefaultButton>
</Box>
</Box>
<Box flexGrow={1} className={classes.logs}>
<Box flexGrow={1} className='ProcessDetails-logs'>
{logs == null && <span data-test="loading-logs">{gettext('Loading process logs...')}</span>}
{logs?.length == 0 && gettext('No logs available.')}
{logs?.map((log, i)=>{
@ -181,14 +179,14 @@ export default function ProcessDetails({data}) {
if(i==logs.length-1) {
el?.scrollIntoView();
}
}} key={id} className={errRe.test(log) ? classes.logErr : ''}>{log}</div>;
}} key={id} className={errRe.test(log) ? 'ProcessDetails-logErr' : ''}>{log}</div>;
})}
</Box>
<Box display="flex" alignItems="center">
<NotifierMessage type={notifyType} message={notifyText} closable={false} textCenter={true} style={{flexGrow: 1, marginRight: '8px'}} />
<Box>{gettext('Execution time')}: {timeTaken} {gettext('seconds')}</Box>
</Box>
</Box>
</StyledBox>
);
}

View File

@ -8,10 +8,10 @@
//////////////////////////////////////////////////////////////
import React, { useCallback, useEffect, useMemo } from 'react';
import { styled } from '@mui/material/styles';
import PgTable from 'sources/components/PgTable';
import gettext from 'sources/gettext';
import PropTypes from 'prop-types';
import { makeStyles } from '@mui/styles';
import { BgProcessManagerEvents, BgProcessManagerProcessState } from './BgProcessConstants';
import { PgButtonGroup, PgIconButton } from '../../../../static/js/components/Buttons';
import CancelIcon from '@mui/icons-material/Cancel';
@ -26,67 +26,36 @@ import ErrorBoundary from '../../../../static/js/helpers/ErrorBoundary';
import ProcessDetails from './ProcessDetails';
const useStyles = makeStyles((theme) => ({
stopButton: {
const Root = styled('div')(({theme}) => ({
height: '100%',
'& .Processes-stopButton': {
color: theme.palette.error.main
},
buttonClick: {
backgroundColor: theme.palette.grey[400]
},
emptyPanel: {
minHeight: '100%',
minWidth: '100%',
background: theme.otherVars.emptySpaceBg,
overflow: 'auto',
padding: '8px',
display: 'flex',
},
panelIcon: {
width: '80%',
margin: '0 auto',
marginTop: '25px !important',
position: 'relative',
textAlign: 'center',
},
panelMessage: {
marginLeft: '0.5rem',
fontSize: '0.875rem',
},
autoResizer: {
height: '100% !important',
width: '100% !important',
background: theme.palette.grey[400],
padding: '7.5px',
overflow: 'auto !important',
minHeight: '100%',
minWidth: '100%',
},
noPadding: {
padding: 0,
},
bgSucess: {
backgroundColor: theme.palette.success.light,
height: '100%',
padding: '4px',
},
bgFailed: {
backgroundColor: theme.palette.error.light,
height: '100%',
padding: '4px',
},
bgTerm: {
backgroundColor: theme.palette.warning.light,
height: '100%',
padding: '4px',
},
bgRunning: {
backgroundColor: theme.palette.primary.light,
height: '100%',
padding: '4px',
'& .Processes-noPadding': {
padding: '0 !important',
'& .Processes-bgSucess': {
backgroundColor: theme.palette.success.light,
height: '100%',
padding: '4px',
},
'& .Processes-bgFailed': {
backgroundColor: theme.palette.error.light,
height: '100%',
padding: '4px',
},
'& .Processes-bgTerm': {
backgroundColor: theme.palette.warning.light,
height: '100%',
padding: '4px',
},
'& .Processes-bgRunning': {
backgroundColor: theme.palette.primary.light,
height: '100%',
padding: '4px',
}
},
}));
const ProcessStateTextAndColor = {
[BgProcessManagerProcessState.PROCESS_NOT_STARTED]: [gettext('Not started'), 'bgRunning'],
[BgProcessManagerProcessState.PROCESS_STARTED]: [gettext('Running'), 'bgRunning'],
@ -96,7 +65,7 @@ const ProcessStateTextAndColor = {
[BgProcessManagerProcessState.PROCESS_FAILED]: [gettext('Failed'), 'bgFailed'],
};
export default function Processes() {
const classes = useStyles();
const pgAdmin = usePgAdmin();
const [tableData, setTableData] = React.useState([]);
const [selectedRows, setSelectedRows] = React.useState({});
@ -130,7 +99,7 @@ export default function Processes() {
size="xs"
noBorder
icon={<CancelIcon />}
className={classes.stopButton}
className='Processes-stopButton'
disabled={row.original.process_state != BgProcessManagerProcessState.PROCESS_STARTED
|| row.original.server_id != null}
onClick={(e) => {
@ -165,7 +134,7 @@ export default function Processes() {
const StatusCell = ({row})=>{
const [text, bgcolor] = ProcessStateTextAndColor[row.original.process_state];
return <Box className={classes[bgcolor]}>{text}</Box>;
return <Box className={'Processes-'+bgcolor}>{text}</Box>;
};
StatusCell.displayName = 'StatusCell';
StatusCell.propTypes = cellPropTypes;
@ -247,7 +216,7 @@ export default function Processes() {
width: 120,
minWidth: 120,
accessorFn: (row)=>ProcessStateTextAndColor[row.process_state][0],
dataClassName: classes.noPadding,
dataClassName: 'Processes-noPadding',
cell: StatusCell,
},
{
@ -274,48 +243,48 @@ export default function Processes() {
}, []);
return (
<PgTable
data-test="processes"
className={classes.autoResizer}
columns={columns}
data={tableData}
sortOptions={[{id: 'stime', desc: true}]}
selectedRows={selectedRows}
setSelectedRows={setSelectedRows}
hasSelectRow={true}
tableProps={{
getRowId: (row)=>{
return row.id;
}
}}
CustomHeader={()=>{
return (
<Box>
<PgButtonGroup>
<PgIconButton
icon={<DeleteIcon style={{height: '1.4rem'}}/>}
aria-label="Acknowledge and Remove"
title={gettext('Acknowledge and Remove')}
onClick={() => {
pgAdmin.Browser.notifier.confirm(gettext('Remove Processes'), gettext('Are you sure you want to remove the selected processes?'), ()=>{
pgAdmin.Browser.BgProcessManager.acknowledge(selectedRowIDs);
setSelectedRows({});
});
}}
disabled={selectedRowIDs.length <= 0}
></PgIconButton>
<PgIconButton
icon={<HelpIcon style={{height: '1.4rem'}}/>}
aria-label="Help"
title={gettext('Help')}
onClick={() => {
window.open(url_for('help.static', {'filename': 'processes.html'}));
}}
></PgIconButton>
</PgButtonGroup>
</Box>
);
}}
></PgTable>
<Root>
<PgTable
data-test="processes"
columns={columns}
data={tableData}
sortOptions={[{id: 'stime', desc: true}]}
selectedRows={selectedRows}
setSelectedRows={setSelectedRows}
hasSelectRow={true}
tableProps={{
getRowId: (row)=>{
return row.id;
}
}}
CustomHeader={()=>{
return (
<Box>
<PgButtonGroup>
<PgIconButton
icon={<DeleteIcon style={{height: '1.4rem'}}/>}
aria-label="Acknowledge and Remove"
title={gettext('Acknowledge and Remove')}
onClick={() => {
pgAdmin.Browser.notifier.confirm(gettext('Remove Processes'), gettext('Are you sure you want to remove the selected processes?'), ()=>{
pgAdmin.Browser.BgProcessManager.acknowledge(selectedRowIDs);
setSelectedRows({});
});
}}
disabled={selectedRowIDs.length <= 0}
></PgIconButton>
<PgIconButton
icon={<HelpIcon style={{height: '1.4rem'}}/>}
aria-label="Help"
title={gettext('Help')}
onClick={() => {
window.open(url_for('help.static', {'filename': 'processes.html'}));
}}
></PgIconButton>
</PgButtonGroup>
</Box>
);
}}
></PgTable></Root>
);
}

View File

@ -11,7 +11,6 @@ import gettext from 'sources/gettext';
import url_for from 'sources/url_for';
import React from 'react';
import { Box, Paper } from '@mui/material';
import { makeStyles } from '@mui/styles';
import Wizard from '../../../../static/js/helpers/wizard/Wizard';
import WizardStep from '../../../../static/js/helpers/wizard/WizardStep';
import {FormFooterMessage, MESSAGE_TYPE } from '../../../../static/js/components/FormComponents';
@ -30,42 +29,9 @@ import EventBus from '../../../../static/js/helpers/EventBus';
import { CLOUD_PROVIDERS, CLOUD_PROVIDERS_LABELS } from './cloud_constants';
import { LAYOUT_EVENTS } from '../../../../static/js/helpers/Layout';
const useStyles = makeStyles(() =>
({
messageBox: {
marginBottom: '1em',
display: 'flex',
},
messagePadding: {
paddingTop: '10px',
flex: 2.5,
},
buttonMarginEDB: {
position: 'relative',
top: '20%',
},
toggleButton: {
height: '100px',
},
summaryContainer: {
flexGrow: 1,
minHeight: 0,
overflow: 'auto',
},
boxText: {
paddingBottom: '5px'
},
authButton: {
marginLeft: '12em'
}
}),
);
export const CloudWizardEventsContext = React.createContext();
export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanelId}) {
const classes = useStyles();
const eventBus = React.useRef(new EventBus());
let steps = [gettext('Cloud Provider'), gettext('Credentials'), gettext('Cluster Type'),
@ -416,10 +382,10 @@ export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanelId}
});
let cloud_providers = [
{label: gettext(CLOUD_PROVIDERS_LABELS.AWS), value: CLOUD_PROVIDERS.AWS, icon: <AWSIcon className={classes.icon} />},
{label: gettext(CLOUD_PROVIDERS_LABELS.BIGANIMAL), value: CLOUD_PROVIDERS.BIGANIMAL, icon: <BigAnimalIcon className={classes.icon} />},
{label: gettext(CLOUD_PROVIDERS_LABELS.AZURE), value: CLOUD_PROVIDERS.AZURE, icon: <AzureIcon className={classes.icon} /> },
{label: gettext(CLOUD_PROVIDERS_LABELS.GOOGLE), value: CLOUD_PROVIDERS.GOOGLE, icon: <GoogleCloudIcon className={classes.icon} /> }];
{label: gettext(CLOUD_PROVIDERS_LABELS.AWS), value: CLOUD_PROVIDERS.AWS, icon: <AWSIcon />},
{label: gettext(CLOUD_PROVIDERS_LABELS.BIGANIMAL), value: CLOUD_PROVIDERS.BIGANIMAL, icon: <BigAnimalIcon />},
{label: gettext(CLOUD_PROVIDERS_LABELS.AZURE), value: CLOUD_PROVIDERS.AZURE, icon: <AzureIcon /> },
{label: gettext(CLOUD_PROVIDERS_LABELS.GOOGLE), value: CLOUD_PROVIDERS.GOOGLE, icon: <GoogleCloudIcon /> }];
return (
<CloudWizardEventsContext.Provider value={eventBus.current}>
@ -433,10 +399,10 @@ export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanelId}
beforeNext={onBeforeNext}
beforeBack={onBeforeBack}>
<WizardStep stepId={0}>
<Box className={classes.messageBox}>
<Box className={classes.messagePadding}>{gettext('Select a cloud provider for PostgreSQL database.')}</Box>
<Box sx={{ marginBottom: '1em', display: 'flex'}}>
<Box sx={{paddingTop: '10px', flex: 2.5}}>{gettext('Select a cloud provider for PostgreSQL database.')}</Box>
</Box>
<Box className={classes.messageBox}>
<Box sx={{ marginBottom: '1em', display: 'flex'}}>
<ToggleButtons cloudProvider={cloudProvider} setCloudProvider={setCloudProvider}
options={cloud_providers}
></ToggleButtons>
@ -444,8 +410,8 @@ export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanelId}
<FormFooterMessage type={errMsg[0]} message={errMsg[1]} onClose={onErrClose} />
</WizardStep>
<WizardStep stepId={1} >
<Box className={classes.buttonMarginEDB}>
{cloudProvider == CLOUD_PROVIDERS.BIGANIMAL && <Box className={classes.messageBox}>
<Box sx={{ position: 'relative',top: '20%'}}>
{cloudProvider == CLOUD_PROVIDERS.BIGANIMAL && <Box sx={{ marginBottom: '1em', display: 'flex'}}>
<Box>{gettext('The verification code to authenticate the pgAdmin to EDB BigAnimal is: ')} <strong>{verificationCode}</strong>
<br/>{gettext('By clicking the below button, you will be redirected to the EDB BigAnimal authentication page in a new tab.')}
</Box>
@ -453,7 +419,7 @@ export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanelId}
{cloudProvider == CLOUD_PROVIDERS.BIGANIMAL && <PrimaryButton onClick={authenticateBigAnimal} disabled={verificationIntiated}>
{gettext('Click here to authenticate yourself to EDB BigAnimal')}
</PrimaryButton>}
{cloudProvider == CLOUD_PROVIDERS.BIGANIMAL && <Box className={classes.messageBox}>
{cloudProvider == CLOUD_PROVIDERS.BIGANIMAL && <Box sx={{ marginBottom: '1em', display: 'flex'}}>
<Box ></Box>
</Box>}
</Box>
@ -542,8 +508,8 @@ export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanelId}
}
</WizardStep>
<WizardStep stepId={5} >
<Box className={classes.boxText}>{gettext('Please review the details before creating the cloud instance.')}</Box>
<Paper variant="outlined" elevation={0} className={classes.summaryContainer}>
<Box sx={{ paddingBottom: '5px'}}>{gettext('Please review the details before creating the cloud instance.')}</Box>
<Paper variant="outlined" elevation={0} sx={{ flexGrow: 1, minHeight: 0, overflow: 'auto'}}>
{cloudProvider == CLOUD_PROVIDERS.AWS && callRDSAPI == 5 && <FinalSummary
cloudProvider={cloudProvider}
instanceData={cloudInstanceDetails}

View File

@ -8,6 +8,7 @@
//////////////////////////////////////////////////////////////
import React from 'react';
import { styled } from '@mui/material/styles';
import pgAdmin from 'sources/pgadmin';
import { getNodeAjaxOptions, getNodeListById } from 'pgbrowser/node_ajax';
import {CloudInstanceDetailsSchema, CloudDBCredSchema, DatabaseSchema} from './aws_schema.ui';
@ -16,16 +17,14 @@ import url_for from 'sources/url_for';
import getApiInstance from '../../../../static/js/api_instance';
import { isEmptyString } from 'sources/validators';
import PropTypes from 'prop-types';
import { makeStyles } from '@mui/styles';
import gettext from 'sources/gettext';
const useStyles = makeStyles(() =>
const StyledSchemaView= styled(SchemaView)(() =>
({
formClass: {
'& .aws-formClass': {
overflow: 'auto',
}
}),
);
}));
// AWS credentials
export function AwsCredentials(props) {
@ -66,7 +65,7 @@ AwsCredentials.propTypes = {
// AWS Instance Details
export function AwsInstanceDetails(props) {
const [cloudInstanceDetailsInstance, setCloudInstanceDetailsInstance] = React.useState();
const classes = useStyles();
React.useMemo(() => {
const cloudDBInstanceSchema = new CloudInstanceDetailsSchema({
@ -114,7 +113,7 @@ export function AwsInstanceDetails(props) {
}, [props.cloudProvider]);
return <SchemaView
return <StyledSchemaView
formType={'dialog'}
getInitData={() => { /*This is intentional (SonarQube)*/ }}
viewHelperProps={{ mode: 'create' }}
@ -124,7 +123,7 @@ export function AwsInstanceDetails(props) {
onDataChange={(isChanged, changedData) => {
props.setCloudInstanceDetails(changedData);
}}
formClassName={classes.formClass}
formClassName='aws-formClass'
/>;
}
AwsInstanceDetails.propTypes = {

View File

@ -18,15 +18,6 @@ import getApiInstance from '../../../../static/js/api_instance';
import { CloudWizardEventsContext } from './CloudWizard';
import {MESSAGE_TYPE } from '../../../../static/js/components/FormComponents';
import gettext from 'sources/gettext';
import { makeStyles } from '@mui/styles';
const useStyles = makeStyles(() =>
({
formClass: {
overflow: 'auto',
}
}),
);
// Azure credentials
export function AzureCredentials(props) {
@ -111,7 +102,6 @@ AzureCredentials.propTypes = {
// Azure Instance
export function AzureInstanceDetails(props) {
const [azureInstanceSchema, setAzureInstanceSchema] = React.useState();
const classes = useStyles();
React.useMemo(() => {
const AzureSchema = new AzureClusterSchema({
@ -195,7 +185,6 @@ export function AzureInstanceDetails(props) {
onDataChange={(isChanged, changedData) => {
props.setAzureInstanceData(changedData);
}}
formClassName={classes.formClass}
/>;
}
AzureInstanceDetails.propTypes = {
@ -211,7 +200,6 @@ AzureInstanceDetails.propTypes = {
// Azure Database Details
export function AzureDatabaseDetails(props) {
const [azureDBInstance, setAzureDBInstance] = React.useState();
const classes = useStyles();
React.useMemo(() => {
const azureDBSchema = new AzureDatabaseSchema({
@ -235,7 +223,6 @@ export function AzureDatabaseDetails(props) {
onDataChange={(isChanged, changedData) => {
props.setAzureDatabaseData(changedData);
}}
formClassName={classes.formClass}
/>;
}
AzureDatabaseDetails.propTypes = {

View File

@ -11,38 +11,18 @@ import React from 'react';
import { ToggleButton, ToggleButtonGroup } from '@mui/material';
import CheckRoundedIcon from '@mui/icons-material/CheckRounded';
import { DefaultButton, PrimaryButton } from '../../../../static/js/components/Buttons';
import { makeStyles } from '@mui/styles';
import PropTypes from 'prop-types';
import { getAWSSummary } from './aws';
import {getAzureSummary} from './azure';
import { getBigAnimalSummary } from './biganimal';
import { commonTableStyles } from '../../../../static/js/Theme';
import { Table, TableBody, TableCell, TableHead, TableRow } from '@mui/material';
import clsx from 'clsx';
import { TableBody, TableCell, TableHead, TableRow } from '@mui/material';
import gettext from 'sources/gettext';
import { getGoogleSummary } from './google';
import { CLOUD_PROVIDERS_LABELS } from './cloud_constants';
const useStyles = makeStyles(() =>
({
toggleButtonGroup: {
height: '100px',
flexGrow: '1'
},
toggleButtonMargin:{
marginTop: '0px !important',
padding: '12px'
},
gcpiconpadding:{
paddingLeft: '1.5rem'
}
}),
);
import Table from '../../../../static/js/components/Table';
export function ToggleButtons(props) {
const classes = useStyles();
const handleCloudProvider = (event, provider) => {
if (provider) props.setCloudProvider(provider);
@ -53,12 +33,12 @@ export function ToggleButtons(props) {
color="primary"
value={props.cloudProvider}
onChange={handleCloudProvider}
className={classes.toggleButtonGroup}
sx={{ height: '100px', flexGrow: '1'}}
orientation="vertical"
exclusive>
{
(props.options||[]).map((option)=>{
return (<ToggleButton value={option.value} key={option.label} aria-label={option.label} className={clsx(classes.toggleButtonMargin, option.label==gettext(CLOUD_PROVIDERS_LABELS.GOOGLE) ? classes.gcpiconpadding : null )} component={props.cloudProvider == option.value ? PrimaryButton : DefaultButton}>
return (<ToggleButton value={option.value} key={option.label} aria-label={option.label} sx={{marginTop: '0px !important',padding: '12px'}} className={( option.label==gettext(CLOUD_PROVIDERS_LABELS.GOOGLE) ? 'paddingLeft: 1.5rem' : null )} component={props.cloudProvider == option.value ? PrimaryButton : DefaultButton}>
<CheckRoundedIcon style={{visibility: props.cloudProvider == option.value ? 'visible': 'hidden'}}/>&nbsp;
{option.icon}&nbsp;&nbsp;&nbsp;&nbsp;{option.label}
</ToggleButton>);
@ -75,7 +55,6 @@ ToggleButtons.propTypes = {
export function FinalSummary(props) {
const tableClasses = commonTableStyles();
let summary = [],
summaryHeader = ['Cloud Details', 'Version and Instance Details', 'Storage Details', 'Database Details'];
@ -104,7 +83,7 @@ export function FinalSummary(props) {
return summary.map((item, index) => {
return (
<Table key={summaryHeader[index]} className={clsx(tableClasses.table)}>
<Table key={summaryHeader[index]}>
<TableHead>
<TableRow>
<TableCell colSpan={2}>{gettext(summaryHeader[index])}</TableCell>

View File

@ -18,15 +18,6 @@ import getApiInstance from '../../../../static/js/api_instance';
import { CloudWizardEventsContext } from './CloudWizard';
import {MESSAGE_TYPE } from '../../../../static/js/components/FormComponents';
import gettext from 'sources/gettext';
import { makeStyles } from '@mui/styles';
const useStyles = makeStyles(() =>
({
formClass: {
overflow: 'auto',
}
}),
);
export function GoogleCredentials(props) {
@ -126,7 +117,6 @@ GoogleCredentials.propTypes = {
// Google Instance
export function GoogleInstanceDetails(props) {
const [googleInstanceSchema, setGoogleInstanceSchema] = React.useState();
const classes = useStyles();
React.useMemo(() => {
const GoogleClusterSchemaObj = new GoogleClusterSchema({
@ -186,7 +176,6 @@ export function GoogleInstanceDetails(props) {
onDataChange={(isChanged, changedData) => {
props.setGoogleInstanceData(changedData);
}}
formClassName={classes.formClass}
/>;
}
GoogleInstanceDetails.propTypes = {
@ -202,7 +191,6 @@ GoogleInstanceDetails.propTypes = {
// Google Database Details
export function GoogleDatabaseDetails(props) {
const [googleDBInstance, setGoogleDBInstance] = React.useState();
const classes = useStyles();
React.useMemo(() => {
const googleDBSchema = new GoogleDatabaseSchema({
@ -226,7 +214,6 @@ export function GoogleDatabaseDetails(props) {
onDataChange={(isChanged, changedData) => {
props.setGoogleDatabaseData(changedData);
}}
formClassName={classes.formClass}
/>;
}
GoogleDatabaseDetails.propTypes = {

View File

@ -8,12 +8,12 @@
//////////////////////////////////////////////////////////////
import _ from 'lodash';
import { styled } from '@mui/material/styles';
import React, { useEffect } from 'react';
import PgTable from 'sources/components/PgTable';
import gettext from 'sources/gettext';
import PropTypes from 'prop-types';
import getApiInstance from 'sources/api_instance';
import { makeStyles } from '@mui/styles';
import { getURL } from '../../../static/utils/utils';
import Loader from 'sources/components/Loader';
import EmptyPanelMessage from '../../../../static/js/components/EmptyPanelMessage';
@ -22,8 +22,9 @@ import withStandardTabInfo from '../../../../static/js/helpers/withStandardTabIn
import { BROWSER_PANELS } from '../../../../browser/static/js/constants';
import { usePgAdmin } from '../../../../static/js/BrowserComponent';
const useStyles = makeStyles((theme) => ({
emptyPanel: {
const Root = styled('div')(({theme}) => ({
height : '100%',
'& .Dependencies-emptyPanel': {
minHeight: '100%',
minWidth: '100%',
background: theme.otherVars.emptySpaceBg,
@ -31,26 +32,6 @@ const useStyles = makeStyles((theme) => ({
padding: '8px',
display: 'flex',
},
panelIcon: {
width: '80%',
margin: '0 auto',
marginTop: '25px !important',
position: 'relative',
textAlign: 'center',
},
panelMessage: {
marginLeft: '0.5rem',
fontSize: '0.875rem',
},
autoResizer: {
height: '100% !important',
width: '100% !important',
background: theme.palette.grey[400],
padding: '7.5px',
overflow: 'auto !important',
minHeight: '100%',
minWidth: '100%',
},
}));
function parseData(data, node) {
@ -75,7 +56,7 @@ function parseData(data, node) {
}
function Dependencies({ nodeData, nodeItem, node, treeNodeInfo, isActive, isStale, setIsStale }) {
const classes = useStyles();
const [tableData, setTableData] = React.useState([]);
const [loaderText, setLoaderText] = React.useState('');
const [msg, setMsg] = React.useState('');
@ -164,23 +145,22 @@ function Dependencies({ nodeData, nodeItem, node, treeNodeInfo, isActive, isStal
}, [isActive, isStale]);
return (
<>
(<Root>
{tableData.length > 0 ? (
<PgTable
className={classes.autoResizer}
columns={columns}
data={tableData}
msg={msg}
type={gettext('panel')}
></PgTable>
) : (
<div className={classes.emptyPanel}>
<div className='Dependencies-emptyPanel'>
{loaderText ? (<Loader message={loaderText}/>) :
<EmptyPanelMessage text={gettext(msg)}/>
}
</div>
)}
</>
</Root>)
);
}

View File

@ -8,12 +8,12 @@
//////////////////////////////////////////////////////////////
import _ from 'lodash';
import { styled } from '@mui/material/styles';
import React, { useEffect } from 'react';
import PgTable from 'sources/components/PgTable';
import gettext from 'sources/gettext';
import PropTypes from 'prop-types';
import getApiInstance from 'sources/api_instance';
import { makeStyles } from '@mui/styles';
import { getURL } from '../../../static/utils/utils';
import Loader from 'sources/components/Loader';
import EmptyPanelMessage from '../../../../static/js/components/EmptyPanelMessage';
@ -22,8 +22,9 @@ import withStandardTabInfo from '../../../../static/js/helpers/withStandardTabIn
import { BROWSER_PANELS } from '../../../../browser/static/js/constants';
import { usePgAdmin } from '../../../../static/js/BrowserComponent';
const useStyles = makeStyles((theme) => ({
emptyPanel: {
const Root = styled('div')(({theme}) => ({
height : '100%',
'& .Dependents-emptyPanel': {
minHeight: '100%',
minWidth: '100%',
background: theme.otherVars.emptySpaceBg,
@ -31,26 +32,6 @@ const useStyles = makeStyles((theme) => ({
padding: '8px',
display: 'flex',
},
panelIcon: {
width: '80%',
margin: '0 auto',
marginTop: '25px !important',
position: 'relative',
textAlign: 'center',
},
panelMessage: {
marginLeft: '0.5rem',
fontSize: '0.875rem',
},
autoResizer: {
height: '100% !important',
width: '100% !important',
background: theme.palette.grey[400],
padding: '7.5px',
overflow: 'auto !important',
minHeight: '100%',
minWidth: '100%',
},
}));
function parseData(data, node) {
@ -75,7 +56,7 @@ function parseData(data, node) {
}
function Dependents({ nodeData, nodeItem, node, treeNodeInfo, isActive, isStale, setIsStale }) {
const classes = useStyles();
const [tableData, setTableData] = React.useState([]);
const [loaderText, setLoaderText] = React.useState('');
const [msg, setMsg] = React.useState('');
@ -164,24 +145,23 @@ function Dependents({ nodeData, nodeItem, node, treeNodeInfo, isActive, isStale,
}, [isActive, isStale]);
return (
<>
(<Root>
{tableData.length > 0 ? (
<PgTable
className={classes.autoResizer}
columns={columns}
data={tableData}
msg={msg}
type={gettext('panel')}
></PgTable>
) : (
<div className={classes.emptyPanel}>
<div className='Dependents-emptyPanel'>
{loaderText ? (<Loader message={loaderText}/>) :
<EmptyPanelMessage text={gettext(msg)}/>
}
</div>
)}
</>
</Root>)
);
}

View File

@ -7,10 +7,9 @@
//
//////////////////////////////////////////////////////////////
import { Box } from '@mui/material';
import { makeStyles } from '@mui/styles';
import { styled } from '@mui/material/styles';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { DefaultButton, PgButtonGroup, PgIconButton, PrimaryButton } from '../../../../../static/js/components/Buttons';
import { useModalStyles } from '../../../../../static/js/helpers/ModalProvider';
import CloseIcon from '@mui/icons-material/CloseRounded';
import FolderSharedIcon from '@mui/icons-material/FolderShared';
import FolderIcon from '@mui/icons-material/Folder';
@ -23,7 +22,6 @@ import SyncRoundedIcon from '@mui/icons-material/SyncRounded';
import CreateNewFolderRoundedIcon from '@mui/icons-material/CreateNewFolderRounded';
import GetAppRoundedIcon from '@mui/icons-material/GetAppRounded';
import gettext from 'sources/gettext';
import clsx from 'clsx';
import { FormFooterMessage, InputSelectNonSearch, InputText, MESSAGE_TYPE } from '../../../../../static/js/components/FormComponents';
import ListView from './ListView';
import { PgMenu, PgMenuDivider, PgMenuItem, usePgMenuGroup } from '../../../../../static/js/components/Menu';
@ -39,40 +37,59 @@ import ErrorBoundary from '../../../../../static/js/helpers/ErrorBoundary';
import { MY_STORAGE } from './FileManagerConstants';
import _ from 'lodash';
const useStyles = makeStyles((theme)=>({
footerSaveAs: {
const StyledBox = styled(Box)(({theme}) => ({
backgroundColor: theme.palette.background.default,
'& .FileManager-toolbar': {
padding: '4px',
display: 'flex',
...theme.mixins.panelBorder?.bottom,
'& .FileManager-sharedStorage': {
width: '3rem !important',
},
'& .FileManager-inputFilename': {
lineHeight: 1,
width: '100%',
},
'& .FileManager-inputSearch': {
marginLeft: '4px',
lineHeight: 1,
width: '130px',
},
'& .FileManager-storageName': {
paddingLeft: '0.2rem'
},
'& .FileManager-sharedIcon': {
width: '1.3rem'
}
},
'& .FileManager-footer': {
display: 'flex',
justifyContent: 'flex-end',
padding: '0.5rem',
...theme.mixins.panelBorder?.top,
'& .FileManager-margin': {
marginLeft: '0.25rem',
},
},
'& .FileManager-footer1': {
justifyContent: 'space-between',
padding: '4px 8px',
display: 'flex',
alignItems: 'center',
'& .FileManager-formatSelect': {
'& .MuiSelect-select': {
paddingTop: '4px',
paddingBottom: '4px',
}
},
},
'& .FileManager-footerSaveAs': {
justifyContent: 'initial',
padding: '4px 8px',
display: 'flex',
alignItems: 'center',
},
footer1: {
justifyContent: 'space-between',
padding: '4px 8px',
display: 'flex',
alignItems: 'center',
},
toolbar: {
padding: '4px',
display: 'flex',
...theme.mixins.panelBorder?.bottom,
},
inputFilename: {
lineHeight: 1,
width: '100%',
},
inputSearch: {
marginLeft: '4px',
lineHeight: 1,
width: '130px',
},
formatSelect: {
'& .MuiSelect-select': {
paddingTop: '4px',
paddingBottom: '4px',
}
},
replaceOverlay: {
'& .FileManager-replaceOverlay': {
position: 'absolute',
top: 0,
bottom: 0,
@ -82,7 +99,7 @@ const useStyles = makeStyles((theme)=>({
zIndex: 2,
display: 'flex',
},
replaceDialog: {
'& .FileManager-replaceDialog': {
margin: 'auto',
marginLeft: '1rem',
marginRight: '1rem',
@ -91,15 +108,6 @@ const useStyles = makeStyles((theme)=>({
width: '100%',
...theme.mixins.panelBorder.all,
},
sharedStorage: {
width: '3rem !important',
},
storageName: {
paddingLeft: '0.2rem'
},
sharedIcon: {
width: '1.3rem'
}
}));
export function getComparator(sortColumn) {
@ -383,15 +391,13 @@ export class FileManagerUtils {
}
function ConfirmFile({text, onYes, onNo}) {
const classes = useStyles();
const modalClasses = useModalStyles();
return (
<Box className={classes.replaceOverlay}>
<Box margin={'8px'} className={classes.replaceDialog}>
<Box className='FileManager-replaceOverlay'>
<Box margin={'8px'} className='FileManager-replaceDialog'>
<Box padding={'1rem'}>{text}{}</Box>
<Box className={modalClasses.footer}>
<Box className='FileManager-footer'>
<DefaultButton data-test="no" startIcon={<CloseIcon />} onClick={onNo} >{gettext('No')}</DefaultButton>
<PrimaryButton data-test="yes" className={modalClasses.margin} startIcon={<CheckRoundedIcon />}
<PrimaryButton data-test="yes" className='FileManager-margin' startIcon={<CheckRoundedIcon />}
onClick={onYes} autoFocus>{gettext('Yes')}</PrimaryButton>
</Box>
</Box>
@ -405,8 +411,8 @@ ConfirmFile.propTypes = {
};
export default function FileManager({params, closeModal, onOK, onCancel, sharedStorages=[], restrictedSharedStorage=[]}) {
const classes = useStyles();
const modalClasses = useModalStyles();
const apiObj = useMemo(()=>getApiInstance(), []);
const fmUtilsObj = useMemo(()=>new FileManagerUtils(apiObj, params), []);
@ -694,15 +700,15 @@ export default function FileManager({params, closeModal, onOK, onCancel, sharedS
return (
<ErrorBoundary>
<Box display="flex" flexDirection="column" height="100%" className={modalClasses.container}>
<StyledBox display="flex" flexDirection="column" height="100%">
<Box flexGrow="1" display="flex" flexDirection="column" position="relative" overflow="hidden">
<Loader message={loaderText} />
{Boolean(confirmText) && <ConfirmFile text={confirmText} onNo={()=>setConfirmFile([null, null])} onYes={onConfirmYes}/>}
<Box className={classes.toolbar}>
<Box className='FileManager-toolbar'>
<PgButtonGroup size="small" style={{flexGrow: 1}}>
{ sharedStorages.length > 0 &&
<PgIconButton title={ selectedSS == MY_STORAGE ? gettext('My Storage') :gettext(selectedSS)} icon={ selectedSS == MY_STORAGE ? <><FolderIcon/><KeyboardArrowDownIcon style={{marginLeft: '-10px'}} /></> : <><FolderSharedIcon /><KeyboardArrowDownIcon style={{marginLeft: '-10px'}} /></>} splitButton
name="menu-shared-storage" ref={sharedSRef} onClick={toggleMenu} className={classes.sharedStorage}/>
name="menu-shared-storage" ref={sharedSRef} onClick={toggleMenu} className='FileManager-sharedStorage'/>
}
<PgIconButton title={gettext('Home')} onClick={async ()=>{
await openDir(fmUtilsObj.config?.options?.homedir, selectedSS);
@ -710,7 +716,7 @@ export default function FileManager({params, closeModal, onOK, onCancel, sharedS
<PgIconButton title={gettext('Go Back')} onClick={async ()=>{
await openDir(fmUtilsObj.dirname(fmUtilsObj.currPath), selectedSS);
}} icon={<ArrowUpwardRoundedIcon />} disabled={!fmUtilsObj.dirname(fmUtilsObj.currPath) || showUploader} />
<InputText size="small" className={classes.inputFilename}
<InputText size="small" className='FileManager-inputFilename'
data-label="file-path"
controlProps={{maxLength: null}}
onKeyDown={async (e)=>{
@ -724,7 +730,7 @@ export default function FileManager({params, closeModal, onOK, onCancel, sharedS
await openDir(path, selectedSS);
}} icon={<SyncRoundedIcon />} disabled={showUploader} />
</PgButtonGroup>
<InputText type="search" className={classes.inputSearch} data-label="search" placeholder={gettext('Search')} value={search} onChange={setSearch} />
<InputText type="search" className='FileManager-inputSearch' data-label="search" placeholder={gettext('Search')} value={search} onChange={setSearch} />
<PgButtonGroup size="small" style={{marginLeft: '4px'}}>
{params.dialog_type == 'storage_dialog' &&
<PgIconButton title={gettext('Download')} icon={<GetAppRoundedIcon />}
@ -777,7 +783,7 @@ export default function FileManager({params, closeModal, onOK, onCancel, sharedS
onClick={async (option)=> {
option.keepOpen = false;
await changeDir(option.value);
}}><FolderIcon className={classes.sharedIcon}/><Box className={classes.storageName}>{gettext('My Storage')}</Box></PgMenuItem>
}}><FolderIcon className='FileManager-sharedIcon'/><Box className='FileManager-storageName'>{gettext('My Storage')}</Box></PgMenuItem>
{
sharedStorages.map((ss)=> {
@ -786,7 +792,7 @@ export default function FileManager({params, closeModal, onOK, onCancel, sharedS
onClick={async(option)=> {
option.keepOpen = false;
await changeDir(option.value);
}}><FolderSharedIcon className={classes.sharedIcon}/><Box className={classes.storageName}>{gettext(ss)}</Box></PgMenuItem>);
}}><FolderSharedIcon className='FileManager-sharedIcon'/><Box className='FileManager-storageName'>{gettext(ss)}</Box></PgMenuItem>);
})
}
@ -810,16 +816,16 @@ export default function FileManager({params, closeModal, onOK, onCancel, sharedS
onItemSelect={onItemSelect} />}
<FormFooterMessage type={MESSAGE_TYPE.ERROR} message={_.escape(errorMsg)} closable onClose={()=>setErrorMsg('')} />
{params.dialog_type == 'create_file' &&
<Box className={clsx(modalClasses.footer, classes.footerSaveAs)}>
<Box className={'FileManager-footer ' + 'FileManager-footerSaveAs'}>
<span style={{whiteSpace: 'nowrap', marginRight: '4px'}}>Save As</span>
<InputText inputRef={saveAsRef} autoFocus style={{height: '28px'}} value={saveAs} onChange={setSaveAs} />
</Box>}
{params.dialog_type != 'select_folder' &&
<Box className={clsx(modalClasses.footer, classes.footer1)}>
<Box className={'FileManager-footer ' + 'FileManager-footer1'}>
<Box>{itemsText}</Box>
<Box>
<span style={{marginRight: '8px'}}>File Format</span>
<InputSelectNonSearch value={fileType} className={classes.formatSelect}
<InputSelectNonSearch value={fileType} className='FileManager-formatSelect'
onChange={(e)=>{
let val = e.target.value;
fmUtilsObj.setFileType(val);
@ -833,7 +839,7 @@ export default function FileManager({params, closeModal, onOK, onCancel, sharedS
</Box>}
</Box>
</Box>
<Box className={modalClasses.footer}>
<Box className='FileManager-footer'>
<PgButtonGroup style={{flexGrow: 1}}>
</PgButtonGroup>
<DefaultButton data-test="close" startIcon={<CloseIcon />} onClick={()=>{
@ -841,10 +847,10 @@ export default function FileManager({params, closeModal, onOK, onCancel, sharedS
closeModal();
}} >{gettext('Cancel')}</DefaultButton>
{params.dialog_type != 'storage_dialog' &&
<PrimaryButton data-test="save" className={modalClasses.margin} startIcon={<CheckRoundedIcon />}
<PrimaryButton data-test="save" className='FileManager-margin' startIcon={<CheckRoundedIcon />}
onClick={onOkClick} disabled={okBtnDisable || showUploader}>{okBtnText}</PrimaryButton>}
</Box>
</Box>
</StyledBox>
</ErrorBoundary>
);
}

View File

@ -7,7 +7,7 @@
//
//////////////////////////////////////////////////////////////
import { Box } from '@mui/material';
import { makeStyles } from '@mui/styles';
import { styled } from '@mui/material/styles';
import React, {useState, useEffect, useRef, useLayoutEffect} from 'react';
import FolderIcon from '@mui/icons-material/Folder';
import DescriptionIcon from '@mui/icons-material/Description';
@ -16,49 +16,47 @@ import StorageRoundedIcon from '@mui/icons-material/StorageRounded';
import PropTypes from 'prop-types';
import gettext from 'sources/gettext';
const useStyles = makeStyles((theme)=>({
grid: {
const StyledBox = styled(Box)(({theme}) => ({
'& .GridView-grid': {
display: 'flex',
fontSize: '13px',
flexWrap: 'wrap',
overflow: 'hidden',
},
gridItem: {
width: '100px',
margin: '4px',
textAlign: 'center',
position: 'relative',
border: '1px solid transparent',
cursor: 'pointer',
'&[aria-selected=true]': {
backgroundColor: theme.palette.primary.light,
color: theme.otherVars.qtDatagridSelectFg,
borderColor: theme.palette.primary.main,
'& .GridView-gridItem': {
width: '100px',
margin: '4px',
textAlign: 'center',
position: 'relative',
border: '1px solid transparent',
cursor: 'pointer',
'&[aria-selected=true]': {
backgroundColor: theme.palette.primary.light,
color: theme.otherVars.qtDatagridSelectFg,
borderColor: theme.palette.primary.main,
},
'& .GridView-gridItemContent': {
padding: '4px',
'& .GridView-gridFilename': {
overflowWrap: 'break-word',
},
'& .GridView-gridItemEdit': {
border: `1px solid ${theme.otherVars.inputBorderColor}`,
backgroundColor: theme.palette.background.default,
},
'& .GridView-protected': {
height: '1.25rem',
width: '1.25rem',
position: 'absolute',
left: '52px',
color: theme.palette.error.main,
backgroundColor: 'inherit',
}
},
},
},
gridItemContent: {
padding: '4px',
},
gridFilename: {
overflowWrap: 'break-word',
},
gridItemEdit: {
border: `1px solid ${theme.otherVars.inputBorderColor}`,
backgroundColor: theme.palette.background.default,
},
protected: {
height: '1.25rem',
width: '1.25rem',
position: 'absolute',
left: '52px',
color: theme.palette.error.main,
backgroundColor: 'inherit',
}
}));
export function ItemView({idx, row, selected, onItemSelect, onItemEnter, onEditComplete}) {
const classes = useStyles();
const editMode = Boolean(onEditComplete);
const fileNameRef = useRef();
@ -98,14 +96,14 @@ export function ItemView({idx, row, selected, onItemSelect, onItemEnter, onEditC
}
return (
<div tabIndex="-1" className={classes.gridItem} aria-selected={selected} onClick={()=>onItemSelect(idx)} onDoubleClick={()=>onItemEnter(row)} onKeyDown={handleItemKeyDown} role="gridcell">
<div className={classes.gridItemContent}>
<div tabIndex="-1" className='GridView-gridItem' aria-selected={selected} onClick={()=>onItemSelect(idx)} onDoubleClick={()=>onItemEnter(row)} onKeyDown={handleItemKeyDown} role="gridcell">
<div className='GridView-gridItemContent'>
<div>
{icon}
{Boolean(row.Protected) && <LockRoundedIcon className={classes.protected}/>}
{Boolean(row.Protected) && <LockRoundedIcon className='GridView-protected'/>}
</div>
<div tabIndex="-1" ref={fileNameRef} onKeyDown={handleEditKeyDown} onBlur={()=>onEditComplete?.(row)}
className={editMode ? classes.gridItemEdit : classes.gridFilename} suppressContentEditableWarning={true}
className={editMode ? 'GridView-gridItemEdit' : 'GridView-gridFilename'} suppressContentEditableWarning={true}
contentEditable={editMode} data-test="filename-div" role={editMode ? 'textbox' : 'none'}>{row['Filename']}</div>
</div>
</div>
@ -121,7 +119,7 @@ ItemView.propTypes = {
};
export default function GridView({items, operation, onItemSelect, onItemEnter}) {
const classes = useStyles();
const [selectedIdx, setSelectedIdx] = useState(null);
const gridRef = useRef();
@ -138,15 +136,15 @@ export default function GridView({items, operation, onItemSelect, onItemEnter})
}
return (
<Box flexGrow={1} overflow="hidden auto" id="grid">
<div ref={gridRef} className={classes.grid}>
<StyledBox flexGrow={1} overflow="hidden auto" id="grid">
<div ref={gridRef} className='GridView-grid'>
{items.map((item, i)=>(
<ItemView key={item.Filename} idx={i} row={item} selected={selectedIdx==i} onItemSelect={setSelectedIdx}
onItemEnter={onItemEnter} onEditComplete={operation.idx==i ? onEditComplete : null} />)
)}
</div>
{items.length == 0 && <Box textAlign="center" p={1}>{gettext('No files/folders found')}</Box>}
</Box>
</StyledBox>
);
}

View File

@ -1,12 +1,4 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2024, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import { makeStyles } from '@mui/styles';
import { styled } from '@mui/material/styles';
import React, { useRef, useEffect } from 'react';
import PgReactDataGrid from '../../../../../static/js/components/PgReactDataGrid';
import FolderIcon from '@mui/icons-material/Folder';
@ -16,8 +8,9 @@ import LockRoundedIcon from '@mui/icons-material/LockRounded';
import PropTypes from 'prop-types';
import gettext from 'sources/gettext';
const useStyles = makeStyles((theme)=>({
grid: {
const StyledPgReactDataGrid = styled(PgReactDataGrid)(({theme}) => ({
'&.Grid-grid': {
fontSize: '13px',
'& .rdg-header-row': {
'& .rdg-cell': {
@ -31,39 +24,39 @@ const useStyles = makeStyles((theme)=>({
'&.rdg-editor-container': {
padding: '0px',
},
},
'& .Grid-input': {
appearance: 'none',
width: '100%',
height: '100%',
verticalAlign: 'top',
outline: 'none',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
border: 0,
boxShadow: 'inset 0 0 0 1.5px '+theme.palette.primary.main,
padding: '0 2px',
'::selection': {
background: theme.palette.primary.light,
}
},
'& .Grid-protected': {
height: '0.75rem',
width: '0.75rem',
position: 'absolute',
left: '14px',
top: '5px',
color: theme.palette.error.main,
backgroundColor: 'inherit',
}
}
},
input: {
appearance: 'none',
width: '100%',
height: '100%',
verticalAlign: 'top',
outline: 'none',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
border: 0,
boxShadow: 'inset 0 0 0 1.5px '+theme.palette.primary.main,
padding: '0 2px',
'::selection': {
background: theme.palette.primary.light,
}
},
protected: {
height: '0.75rem',
width: '0.75rem',
position: 'absolute',
left: '14px',
top: '5px',
color: theme.palette.error.main,
backgroundColor: 'inherit',
}
}));
export const GridContextUtils = React.createContext();
export function FileNameEditor({row, column, onRowChange, onClose}) {
const classes = useStyles();
const value = row[column.key] ?? '';
const [localVal, setLocalVal] = React.useState(value);
const localValRef = useRef(localVal);
@ -84,7 +77,7 @@ export function FileNameEditor({row, column, onRowChange, onClose}) {
};
return (
<input
className={classes.input}
className='Grid-input'
value={localVal}
onChange={(e)=>{
setLocalVal(e.target.value);
@ -102,7 +95,7 @@ FileNameEditor.propTypes = {
onClose: PropTypes.func,
};
function FileNameFormatter({row}) {
const classes = useStyles();
let icon = <DescriptionIcon style={{fontSize: '1.2rem'}} />;
if(row.file_type == 'dir') {
icon = <FolderIcon style={{fontSize: '1.2rem'}} />;
@ -111,7 +104,7 @@ function FileNameFormatter({row}) {
}
return <>
{icon}
{Boolean(row.Protected) && <LockRoundedIcon className={classes.protected}/>}
{Boolean(row.Protected) && <LockRoundedIcon className='Grid-protected'/>}
<span style={{marginLeft: '4px'}}>{row['Filename']}</span>
</>;
}
@ -142,7 +135,7 @@ const columns = [
export default function ListView({items, operation, ...props}) {
const classes = useStyles();
const gridRef = useRef();
useEffect(()=>{
@ -157,10 +150,10 @@ export default function ListView({items, operation, ...props}) {
}, [gridRef.current?.element]);
return (
<PgReactDataGrid
<StyledPgReactDataGrid
gridRef={gridRef}
id="list"
className={classes.grid}
className='Grid-grid'
hasSelectColumn={false}
columns={columns}
rows={items}

View File

@ -7,8 +7,8 @@
//
//////////////////////////////////////////////////////////////
import React, { useCallback, useReducer, useEffect, useMemo } from 'react';
import { styled } from '@mui/material/styles';
import { Box, List, ListItem } from '@mui/material';
import { makeStyles } from '@mui/styles';
import CloseIcon from '@mui/icons-material/CloseRounded';
import { PgIconButton } from '../../../../../static/js/components/Buttons';
import gettext from 'sources/gettext';
@ -18,20 +18,19 @@ import convert from 'convert-units';
import _ from 'lodash';
import PropTypes from 'prop-types';
const useStyles = makeStyles((theme)=>({
root: {
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0,
zIndex: 1,
backgroundColor: theme.palette.background.default,
display: 'flex',
flexDirection: 'column',
padding: '4px',
},
uploadArea: {
const StyledBox = styled(Box)(({theme}) => ({
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0,
zIndex: 1,
backgroundColor: theme.palette.background.default,
display: 'flex',
flexDirection: 'column',
padding: '4px',
'& .Uploader-uploadArea': {
border: `1px dashed ${theme.palette.grey[600]}`,
display: 'flex',
alignItems: 'center',
@ -42,19 +41,12 @@ const useStyles = makeStyles((theme)=>({
textAlign: 'center',
padding: '4px',
},
uploadFilesRoot: {
'& .Uploader-uploadFilesRoot': {
width: '350px',
border: `1px dashed ${theme.palette.grey[600]}`,
borderLeft: 'none',
overflowX: 'hidden',
overflowY: 'auto'
},
uploadProgress: {
position: 'unset',
padding: 0,
},
uploadPending: {
}
}));
@ -131,7 +123,7 @@ UploadedFile.propTypes = {
export default function Uploader({fmUtilsObj, onClose}) {
const classes = useStyles();
const [files, dispatchFileAction] = useReducer(filesReducer, []);
const onDrop = useCallback(acceptedFiles => {
dispatchFileAction({
@ -173,18 +165,18 @@ export default function Uploader({fmUtilsObj, onClose}) {
}, [files.length]);
return (
<Box className={classes.root}>
<StyledBox>
<Box display="flex" justifyContent="flex-end">
<PgIconButton title={gettext('Close')} icon={<CloseIcon />} size="xs" noBorder onClick={onClose} />
</Box>
<Box display="flex" flexGrow={1} overflow="hidden">
<Box className={classes.uploadArea} {...getRootProps()}>
<Box className='Uploader-uploadArea' {...getRootProps()}>
<input {...getInputProps()} />
<Box>{gettext('Drop files here, or click to select files.')}</Box>
<Box>{gettext('The file size limit (per file) is %s MB.', fmUtilsObj.config?.upload?.fileSizeLimit)}</Box>
</Box>
{files.length > 0 &&
<Box className={classes.uploadFilesRoot}>
<Box className='Uploader-uploadFilesRoot'>
<List>
{files.map((upfile)=>(
<UploadedFile key={upfile.id} upfile={upfile} removeFile={async ()=>{
@ -197,7 +189,7 @@ export default function Uploader({fmUtilsObj, onClose}) {
</List>
</Box>}
</Box>
</Box>
</StyledBox>
);
}
Uploader.propTypes = {

View File

@ -7,13 +7,12 @@
//
//////////////////////////////////////////////////////////////
import React from 'react';
import { styled } from '@mui/material/styles';
import getApiInstance from 'sources/api_instance';
import { makeStyles } from '@mui/styles';
import { Box } from '@mui/material';
import { generateCollectionURL } from '../../browser/static/js/node_ajax';
import gettext from 'sources/gettext';
import PgTable from 'sources/components/PgTable';
import Theme from 'sources/Theme';
import PropTypes from 'prop-types';
import { PgButtonGroup, PgIconButton } from '../../static/js/components/Buttons';
import DeleteIcon from '@mui/icons-material/Delete';
@ -25,46 +24,16 @@ import { evalFunc } from '../../static/js/utils';
import { usePgAdmin } from '../../static/js/BrowserComponent';
import { getSwitchCell } from '../../static/js/components/PgReactTableStyled';
const useStyles = makeStyles((theme) => ({
emptyPanel: {
const StyledBox = styled(Box)(({theme}) => ({
height: '100%',
'&.CollectionNodeProperties-emptyPanel': {
minHeight: '100%',
minWidth: '100%',
background: theme.otherVars.emptySpaceBg,
overflow: 'auto',
padding: '8px',
display: 'flex',
},
panelIcon: {
width: '80%',
margin: '0 auto',
marginTop: '25px !important',
position: 'relative',
textAlign: 'center',
},
panelMessage: {
marginLeft: '0.5rem',
fontSize: '0.875rem',
},
searchPadding: {
flex: 2.5
},
searchInput: {
flex: 1,
margin: '4 0 4 0',
borderLeft: 'none',
paddingLeft: 5
},
propertiesPanel: {
height: '100%'
},
autoResizer: {
height: '100% !important',
width: '100% !important',
background: theme.palette.grey[400],
padding: '8px',
overflow: 'hidden !important',
overflowX: 'auto !important'
},
}
}));
export default function CollectionNodeProperties({
@ -76,9 +45,7 @@ export default function CollectionNodeProperties({
isStale,
setIsStale
}) {
const classes = useStyles();
const pgAdmin = usePgAdmin();
const [data, setData] = React.useState([]);
const [infoMsg, setInfoMsg] = React.useState('Please select an object in the tree view.');
const [selectedObject, setSelectedObject] = React.useState({});
@ -193,7 +160,6 @@ export default function CollectionNodeProperties({
}
setLoaderText(gettext('Loading...'));
if (!_.isUndefined(nodeObj.getSchema)) {
schemaRef.current = nodeObj.getSchema?.(treeNodeInfo, nodeData);
schemaRef.current?.fields.forEach((field) => {
@ -232,7 +198,6 @@ export default function CollectionNodeProperties({
});
}
api({
url: url,
type: 'GET',
@ -307,15 +272,14 @@ export default function CollectionNodeProperties({
};
return (
<Theme className='obj_properties'>
<>
<Loader message={loaderText}/>
<Box className={classes.propertiesPanel}>
<StyledBox>
{data.length > 0 ?
(
<PgTable
hasSelectRow={!('catalog' in treeNodeInfo) && (nodeData.label !== 'Catalogs') && _.isUndefined(node?.canSelect)}
CustomHeader={CustomHeader}
className={classes.autoResizer}
columns={pgTableColumns}
data={data}
type={'panel'}
@ -326,13 +290,13 @@ export default function CollectionNodeProperties({
)
:
(
<div className={classes.emptyPanel}>
<div className='CollectionNodeProperties-emptyPanel'>
<EmptyPanelMessage text={gettext(infoMsg)}/>
</div>
)
}
</Box>
</Theme>
</StyledBox>
</>
);
}

View File

@ -8,6 +8,7 @@
//////////////////////////////////////////////////////////////
import React from 'react';
import { styled } from '@mui/material/styles';
import CollectionNodeProperties from './CollectionNodeProperties';
import ErrorBoundary from '../../static/js/helpers/ErrorBoundary';
import withStandardTabInfo from '../../static/js/helpers/withStandardTabInfo';
@ -16,23 +17,19 @@ import ObjectNodeProperties from './ObjectNodeProperties';
import EmptyPanelMessage from '../../static/js/components/EmptyPanelMessage';
import gettext from 'sources/gettext';
import { Box } from '@mui/material';
import { makeStyles } from '@mui/styles';
import { usePgAdmin } from '../../static/js/BrowserComponent';
import PropTypes from 'prop-types';
import _ from 'lodash';
const useStyles = makeStyles((theme) => ({
root: {
height: '100%',
background: theme.otherVars.emptySpaceBg,
display: 'flex',
flexDirection: 'column'
},
const StyledBox = styled(Box)(({theme}) => ({
height: '100%',
background: theme.otherVars.emptySpaceBg,
display: 'flex',
flexDirection: 'column'
}));
function Properties(props) {
const isCollection = props.nodeData?._type?.startsWith('coll-') || props.nodeData?._type == 'dbms_job_scheduler';
const classes = useStyles();
const pgAdmin = usePgAdmin();
let noPropertyMsg = '';
@ -44,27 +41,27 @@ function Properties(props) {
if(noPropertyMsg) {
return (
<Box className={classes.root}>
<StyledBox>
<Box margin={'4px auto'}>
<EmptyPanelMessage text={noPropertyMsg} />
</Box>
</Box>
</StyledBox>
);
}
if(isCollection) {
return (
<Box className={classes.root}>
<StyledBox>
<ErrorBoundary>
<CollectionNodeProperties
{...props}
/>
</ErrorBoundary>
</Box>
</StyledBox>
);
} else {
return (
<Box className={classes.root}>
<StyledBox>
<ErrorBoundary>
<ObjectNodeProperties
{...props}
@ -77,7 +74,7 @@ function Properties(props) {
}}
/>
</ErrorBoundary>
</Box>
</StyledBox>
);
}
}

View File

@ -8,29 +8,29 @@
//////////////////////////////////////////////////////////////
import React, { useEffect } from 'react';
import { styled } from '@mui/material/styles';
import { generateNodeUrl } from '../../../../browser/static/js/node_ajax';
import gettext from 'sources/gettext';
import PropTypes from 'prop-types';
import getApiInstance from 'sources/api_instance';
import { makeStyles } from '@mui/styles';
import CodeMirror from '../../../../static/js/components/ReactCodeMirror';
import Loader from 'sources/components/Loader';
import withStandardTabInfo from '../../../../static/js/helpers/withStandardTabInfo';
import { BROWSER_PANELS } from '../../../../browser/static/js/constants';
import { usePgAdmin } from '../../../../static/js/BrowserComponent';
const useStyles = makeStyles((theme) => ({
textArea: {
const Root = styled('div')(({theme}) => ({
'& .SQL-textArea': {
height: '100% !important',
width: '100% !important',
background: theme.palette.grey[400],
minHeight: '100%',
minWidth: '100%',
},
}
}));
function SQL({nodeData, node, treeNodeInfo, isActive, isStale, setIsStale}) {
const classes = useStyles();
const did = ((!_.isUndefined(treeNodeInfo)) && (!_.isUndefined(treeNodeInfo['database']))) ? treeNodeInfo['database']._id: 0;
const dbConnected = !_.isUndefined(treeNodeInfo) && !_.isUndefined(treeNodeInfo['database']) ? treeNodeInfo.database.connected: false;
const [nodeSQL, setNodeSQL] = React.useState('');
@ -92,15 +92,15 @@ function SQL({nodeData, node, treeNodeInfo, isActive, isStale, setIsStale}) {
}, [isStale, isActive, nodeData?.id]);
return (
<>
(<Root style={{height: '100%'}} >
<Loader message={loaderText}/>
<CodeMirror
className={classes.textArea}
className='SQL-textArea'
value={nodeSQL}
readonly={true}
showCopyBtn
/>
</>
</Root>)
);
}

View File

@ -8,12 +8,12 @@
//////////////////////////////////////////////////////////////
import _ from 'lodash';
import { styled } from '@mui/material/styles';
import React, { useEffect } from 'react';
import PgTable from 'sources/components/PgTable';
import gettext from 'sources/gettext';
import PropTypes from 'prop-types';
import getApiInstance from 'sources/api_instance';
import { makeStyles } from '@mui/styles';
import { getURL } from '../../../static/utils/utils';
import Loader from 'sources/components/Loader';
import EmptyPanelMessage from '../../../../static/js/components/EmptyPanelMessage';
@ -22,36 +22,16 @@ import withStandardTabInfo from '../../../../static/js/helpers/withStandardTabIn
import { BROWSER_PANELS } from '../../../../browser/static/js/constants';
import { usePgAdmin } from '../../../../static/js/BrowserComponent';
const useStyles = makeStyles((theme) => ({
emptyPanel: {
const Root = styled('div')(({theme}) => ({
height : '100%',
'& .Statistics-emptyPanel': {
minHeight: '100%',
minWidth: '100%',
background: theme.otherVars.emptySpaceBg,
overflow: 'auto',
padding: '8px',
display: 'flex',
},
panelIcon: {
width: '80%',
margin: '0 auto',
marginTop: '25px !important',
position: 'relative',
textAlign: 'center',
},
panelMessage: {
marginLeft: '0.5rem',
fontSize: '0.875rem',
},
autoResizer: {
height: '100% !important',
width: '100% !important',
background: theme.palette.grey[400],
padding: '7.5px',
overflowX: 'auto !important',
overflowY: 'hidden !important',
minHeight: '100%',
minWidth: '100%',
},
}
}));
function getColumn(data, singleLineStatistics, prettifyFields=[]) {
@ -146,9 +126,7 @@ function createSingleLineStatistics(data, prettifyFields) {
}
function Statistics({ nodeData, nodeItem, node, treeNodeInfo, isActive, isStale, setIsStale }) {
const classes = useStyles();
const [tableData, setTableData] = React.useState([]);
const [msg, setMsg] = React.useState('');
const [loaderText, setLoaderText] = React.useState('');
const [columns, setColumns] = React.useState([
@ -232,22 +210,21 @@ function Statistics({ nodeData, nodeItem, node, treeNodeInfo, isActive, isStale,
}, [isStale, isActive, nodeData?.id]);
return (
<>
<Root>
{tableData.length > 0 ? (
<PgTable
className={classes.autoResizer}
columns={columns}
data={tableData}
msg={msg}
type={'panel'}
></PgTable>
) : (
<div className={classes.emptyPanel}>
<div className='Statistics-emptyPanel'>
<Loader message={loaderText} />
<EmptyPanelMessage text={gettext(msg)}/>
</div>
)}
</>
</Root>
);
}

View File

@ -8,25 +8,81 @@
//////////////////////////////////////////////////////////////
import gettext from 'sources/gettext';
import { styled } from '@mui/material/styles';
import _ from 'lodash';
import url_for from 'sources/url_for';
import React, { useEffect, useMemo } from 'react';
import { FileType } from 'react-aspen';
import { Box } from '@mui/material';
import PropTypes from 'prop-types';
import { makeStyles } from '@mui/styles';
import SchemaView from '../../../../static/js/SchemaView';
import getApiInstance from '../../../../static/js/api_instance';
import CloseSharpIcon from '@mui/icons-material/CloseSharp';
import HelpIcon from '@mui/icons-material/HelpRounded';
import SaveSharpIcon from '@mui/icons-material/SaveSharp';
import clsx from 'clsx';
import pgAdmin from 'sources/pgadmin';
import { DefaultButton, PgIconButton, PrimaryButton } from '../../../../static/js/components/Buttons';
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
import { getBinaryPathSchema } from '../../../../browser/server_groups/servers/static/js/binary_path.ui';
import usePreferences from '../store';
const StyledBox = styled(Box)(({theme}) => ({
'& .PreferencesComponent-root': {
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
height: '100%',
backgroundColor: theme.palette.background.default,
overflow: 'hidden',
'&$disabled': {
color: '#ddd',
},
'& .PreferencesComponent-body': {
borderColor: theme.otherVars.borderColor,
display: 'flex',
flexGrow: 1,
height: '100%',
minHeight: 0,
overflow: 'hidden',
'& .PreferencesComponent-treeContainer': {
flexBasis: '25%',
alignItems: 'flex-start',
paddingLeft: '5px',
minHeight: 0,
flexGrow: 1,
'& .PreferencesComponent-tree': {
height: '100%',
flexGrow: 1
},
},
'& .PreferencesComponent-preferencesContainer': {
flexBasis: '75%',
padding: '5px',
borderColor: theme.otherVars.borderColor + '!important',
borderLeft: '1px solid',
position: 'relative',
height: '100%',
paddingTop: '5px',
overflow: 'auto',
},
},
'& .PreferencesComponent-footer': {
borderTop: `1px solid ${theme.otherVars.inputBorderColor} !important`,
padding: '0.5rem',
display: 'flex',
width: '100%',
background: theme.otherVars.headerBg,
'& .PreferencesComponent-actionBtn': {
alignItems: 'flex-start',
},
'& .PreferencesComponent-buttonMargin': {
marginLeft: '0.5em'
},
},
},
}));
class PreferencesSchema extends BaseUISchema {
constructor(initValues = {}, schemaFields = []) {
super({
@ -49,81 +105,6 @@ class PreferencesSchema extends BaseUISchema {
}
}
const useStyles = makeStyles((theme) =>
({
root: {
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
height: '100%',
backgroundColor: theme.palette.background.default,
overflow: 'hidden',
'&$disabled': {
color: '#ddd',
}
},
body: {
display: 'flex',
flexDirection: 'column',
height: '100%',
minHeight: 0,
},
preferences: {
borderColor: theme.otherVars.borderColor,
display: 'flex',
flexGrow: 1,
height: '100%',
minHeight: 0,
overflow: 'hidden'
},
treeContainer: {
flexBasis: '25%',
alignItems: 'flex-start',
paddingLeft: '5px',
minHeight: 0,
flexGrow: 1
},
tree: {
height: '100%',
flexGrow: 1
},
preferencesContainer: {
flexBasis: '75%',
padding: '5px',
borderColor: theme.otherVars.borderColor + '!important',
borderLeft: '1px solid',
position: 'relative',
height: '100%',
paddingTop: '5px',
overflow: 'auto'
},
actionBtn: {
alignItems: 'flex-start',
},
buttonMargin: {
marginLeft: '0.5em'
},
footer: {
borderTop: `1px solid ${theme.otherVars.inputBorderColor} !important`,
padding: '0.5rem',
display: 'flex',
width: '100%',
background: theme.otherVars.headerBg,
},
customTreeClass: {
'& .react-checkbox-tree': {
height: '100% !important',
border: 'none !important',
},
},
preferencesTree: {
height: 'calc(100% - 50px)',
minHeight: 0
}
}),
);
function RightPanel({ schema, ...props }) {
let initData = () => new Promise((resolve, reject) => {
@ -157,7 +138,7 @@ RightPanel.propTypes = {
export default function PreferencesComponent({ ...props }) {
const classes = useStyles();
const [disableSave, setDisableSave] = React.useState(true);
const prefSchema = React.useRef(new PreferencesSchema({}, []));
const prefChangedData = React.useRef({});
@ -594,18 +575,17 @@ export default function PreferencesComponent({ ...props }) {
};
return (
<Box height={'100%'}>
<Box className={classes.root}>
<Box className={clsx(classes.preferences)}>
<Box className={clsx(classes.treeContainer)} >
<Box className={clsx(classes.tree)} id={'treeContainer'} tabIndex={0}>
<StyledBox height={'100%'}>
<Box className='PreferencesComponent-root'>
<Box className='PreferencesComponent-body'>
<Box className='PreferencesComponent-treeContainer' >
<Box className='PreferencesComponent-tree' id={'treeContainer'} tabIndex={0}>
{
useMemo(() => (prefTreeData && props.renderTree(prefTreeData)), [prefTreeData])
}
</Box>
</Box>
<Box className={clsx(classes.preferencesContainer)}>
<Box className='PreferencesComponent-preferencesContainer'>
{
prefSchema.current && loadTree > 0 &&
<RightPanel schema={prefSchema.current} initValues={initValues} onDataChange={(changedData) => {
@ -615,21 +595,21 @@ export default function PreferencesComponent({ ...props }) {
}
</Box>
</Box>
<Box className={classes.footer}>
<Box className='PreferencesComponent-footer'>
<Box>
<PgIconButton data-test="dialog-help" onClick={onDialogHelp} icon={<HelpIcon />} title={gettext('Help for this dialog.')} />
</Box>
<Box className={classes.actionBtn} marginLeft="auto">
<DefaultButton className={classes.buttonMargin} onClick={() => { props.closeModal();}} startIcon={<CloseSharpIcon onClick={() => { props.closeModal();}} />}>
<Box className='PreferencesComponent-actionBtn' marginLeft="auto">
<DefaultButton className='PreferencesComponent-buttonMargin' onClick={() => { props.closeModal();}} startIcon={<CloseSharpIcon onClick={() => { props.closeModal();}} />}>
{gettext('Cancel')}
</DefaultButton>
<PrimaryButton className={classes.buttonMargin} startIcon={<SaveSharpIcon />} disabled={disableSave} onClick={() => { savePreferences(prefChangedData, initValues); }}>
<PrimaryButton className='PreferencesComponent-buttonMargin' startIcon={<SaveSharpIcon />} disabled={disableSave} onClick={() => { savePreferences(prefChangedData, initValues); }}>
{gettext('Save')}
</PrimaryButton>
</Box>
</Box>
</Box >
</Box>
</StyledBox>
);
}

View File

@ -7,7 +7,7 @@
//
//////////////////////////////////////////////////////////////
import { Box } from '@mui/material';
import { makeStyles } from '@mui/styles';
import { styled } from '@mui/material/styles';
import React, { useState, useEffect } from 'react';
import { PrimaryButton } from './components/Buttons';
import { PgMenu, PgMenuDivider, PgMenuItem, PgSubMenu } from './components/Menu';
@ -15,51 +15,50 @@ import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
import AccountCircleRoundedIcon from '@mui/icons-material/AccountCircleRounded';
import { usePgAdmin } from '../../static/js/BrowserComponent';
const useStyles = makeStyles((theme)=>({
root: {
height: '30px',
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
padding: '0 0.5rem',
display: 'flex',
alignItems: 'center',
},
logo: {
const StyledBox = styled(Box)(({theme}) => ({
height: '30px',
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
padding: '0 0.5rem',
display: 'flex',
alignItems: 'center',
'& .AppMenuBar-logo': {
width: '96px',
height: '100%',
/*
* Using the SVG postgresql logo, modified to set the background color as #FFF
* https://wiki.postgresql.org/images/9/90/PostgreSQL_logo.1color_blue.svg
* background: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='utf-8'%3F%3E%3C!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --%3E%3Csvg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 42 42' style='enable-background:new 0 0 42 42;' xml:space='preserve'%3E%3Cstyle type='text/css'%3E .st0%7Bstroke:%23000000;stroke-width:3.3022;%7D .st1%7Bfill:%23336791;%7D .st2%7Bfill:none;stroke:%23FFFFFF;stroke-width:1.1007;stroke-linecap:round;stroke-linejoin:round;%7D .st3%7Bfill:none;stroke:%23FFFFFF;stroke-width:1.1007;stroke-linecap:round;stroke-linejoin:bevel;%7D .st4%7Bfill:%23FFFFFF;stroke:%23FFFFFF;stroke-width:0.3669;%7D .st5%7Bfill:%23FFFFFF;stroke:%23FFFFFF;stroke-width:0.1835;%7D .st6%7Bfill:none;stroke:%23FFFFFF;stroke-width:0.2649;stroke-linecap:round;stroke-linejoin:round;%7D%0A%3C/style%3E%3Cg id='orginal'%3E%3C/g%3E%3Cg id='Layer_x0020_3'%3E%3Cpath class='st0' d='M31.3,30c0.3-2.1,0.2-2.4,1.7-2.1l0.4,0c1.2,0.1,2.8-0.2,3.7-0.6c2-0.9,3.1-2.4,1.2-2 c-4.4,0.9-4.7-0.6-4.7-0.6c4.7-7,6.7-15.8,5-18c-4.6-5.9-12.6-3.1-12.7-3l0,0c-0.9-0.2-1.9-0.3-3-0.3c-2,0-3.5,0.5-4.7,1.4 c0,0-14.3-5.9-13.6,7.4c0.1,2.8,4,21.3,8.7,15.7c1.7-2,3.3-3.8,3.3-3.8c0.8,0.5,1.8,0.8,2.8,0.7l0.1-0.1c0,0.3,0,0.5,0,0.8 c-1.2,1.3-0.8,1.6-3.2,2.1c-2.4,0.5-1,1.4-0.1,1.6c1.1,0.3,3.7,0.7,5.5-1.8l-0.1,0.3c0.5,0.4,0.4,2.7,0.5,4.4 c0.1,1.7,0.2,3.2,0.5,4.1c0.3,0.9,0.7,3.3,3.9,2.6C29.1,38.3,31.1,37.5,31.3,30'/%3E%3Cpath class='st1' d='M38.3,25.3c-4.4,0.9-4.7-0.6-4.7-0.6c4.7-7,6.7-15.8,5-18c-4.6-5.9-12.6-3.1-12.7-3l0,0 c-0.9-0.2-1.9-0.3-3-0.3c-2,0-3.5,0.5-4.7,1.4c0,0-14.3-5.9-13.6,7.4c0.1,2.8,4,21.3,8.7,15.7c1.7-2,3.3-3.8,3.3-3.8 c0.8,0.5,1.8,0.8,2.8,0.7l0.1-0.1c0,0.3,0,0.5,0,0.8c-1.2,1.3-0.8,1.6-3.2,2.1c-2.4,0.5-1,1.4-0.1,1.6c1.1,0.3,3.7,0.7,5.5-1.8 l-0.1,0.3c0.5,0.4,0.8,2.4,0.7,4.3c-0.1,1.9-0.1,3.2,0.3,4.2c0.4,1,0.7,3.3,3.9,2.6c2.6-0.6,4-2,4.2-4.5c0.1-1.7,0.4-1.5,0.5-3 l0.2-0.7c0.3-2.3,0-3.1,1.7-2.8l0.4,0c1.2,0.1,2.8-0.2,3.7-0.6C39,26.4,40.2,24.9,38.3,25.3L38.3,25.3z'/%3E%3Cpath class='st2' d='M21.8,26.6c-0.1,4.4,0,8.8,0.5,9.8c0.4,1.1,1.3,3.2,4.5,2.5c2.6-0.6,3.6-1.7,4-4.1c0.3-1.8,0.9-6.7,1-7.7'/%3E%3Cpath class='st2' d='M18,4.7c0,0-14.3-5.8-13.6,7.4c0.1,2.8,4,21.3,8.7,15.7c1.7-2,3.2-3.7,3.2-3.7'/%3E%3Cpath class='st2' d='M25.7,3.6c-0.5,0.2,7.9-3.1,12.7,3c1.7,2.2-0.3,11-5,18'/%3E%3Cpath class='st3' d='M33.5,24.6c0,0,0.3,1.5,4.7,0.6c1.9-0.4,0.8,1.1-1.2,2c-1.6,0.8-5.3,0.9-5.3-0.1 C31.6,24.5,33.6,25.3,33.5,24.6c-0.1-0.6-1.1-1.2-1.7-2.7c-0.5-1.3-7.3-11.2,1.9-9.7c0.3-0.1-2.4-8.7-11-8.9 c-8.6-0.1-8.3,10.6-8.3,10.6'/%3E%3Cpath class='st2' d='M19.4,25.6c-1.2,1.3-0.8,1.6-3.2,2.1c-2.4,0.5-1,1.4-0.1,1.6c1.1,0.3,3.7,0.7,5.5-1.8c0.5-0.8,0-2-0.7-2.3 C20.5,25.1,20,24.9,19.4,25.6L19.4,25.6z'/%3E%3Cpath class='st2' d='M19.3,25.5c-0.1-0.8,0.3-1.7,0.7-2.8c0.6-1.6,2-3.3,0.9-8.5c-0.8-3.9-6.5-0.8-6.5-0.3c0,0.5,0.3,2.7-0.1,5.2 c-0.5,3.3,2.1,6,5,5.7'/%3E%3Cpath class='st4' d='M18,13.8c0,0.2,0.3,0.7,0.8,0.7c0.5,0.1,0.9-0.3,0.9-0.5c0-0.2-0.3-0.4-0.8-0.4C18.4,13.6,18,13.7,18,13.8 L18,13.8z'/%3E%3Cpath class='st5' d='M32,13.5c0,0.2-0.3,0.7-0.8,0.7c-0.5,0.1-0.9-0.3-0.9-0.5c0-0.2,0.3-0.4,0.8-0.4C31.6,13.2,32,13.3,32,13.5 L32,13.5z'/%3E%3Cpath class='st2' d='M33.7,12.2c0.1,1.4-0.3,2.4-0.4,3.9c-0.1,2.2,1,4.7-0.6,7.2'/%3E%3Cpath class='st6' d='M2.7,6.6'/%3E%3C/g%3E%3C/svg%3E%0A") 0 0 no-repeat;
*/
* Using the SVG postgresql logo, modified to set the background color as #FFF
* https://wiki.postgresql.org/images/9/90/PostgreSQL_logo.1color_blue.svg
* background: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='utf-8'%3F%3E%3C!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --%3E%3Csvg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 42 42' style='enable-background:new 0 0 42 42;' xml:space='preserve'%3E%3Cstyle type='text/css'%3E .st0%7Bstroke:%23000000;stroke-width:3.3022;%7D .st1%7Bfill:%23336791;%7D .st2%7Bfill:none;stroke:%23FFFFFF;stroke-width:1.1007;stroke-linecap:round;stroke-linejoin:round;%7D .st3%7Bfill:none;stroke:%23FFFFFF;stroke-width:1.1007;stroke-linecap:round;stroke-linejoin:bevel;%7D .st4%7Bfill:%23FFFFFF;stroke:%23FFFFFF;stroke-width:0.3669;%7D .st5%7Bfill:%23FFFFFF;stroke:%23FFFFFF;stroke-width:0.1835;%7D .st6%7Bfill:none;stroke:%23FFFFFF;stroke-width:0.2649;stroke-linecap:round;stroke-linejoin:round;%7D%0A%3C/style%3E%3Cg id='orginal'%3E%3C/g%3E%3Cg id='Layer_x0020_3'%3E%3Cpath class='st0' d='M31.3,30c0.3-2.1,0.2-2.4,1.7-2.1l0.4,0c1.2,0.1,2.8-0.2,3.7-0.6c2-0.9,3.1-2.4,1.2-2 c-4.4,0.9-4.7-0.6-4.7-0.6c4.7-7,6.7-15.8,5-18c-4.6-5.9-12.6-3.1-12.7-3l0,0c-0.9-0.2-1.9-0.3-3-0.3c-2,0-3.5,0.5-4.7,1.4 c0,0-14.3-5.9-13.6,7.4c0.1,2.8,4,21.3,8.7,15.7c1.7-2,3.3-3.8,3.3-3.8c0.8,0.5,1.8,0.8,2.8,0.7l0.1-0.1c0,0.3,0,0.5,0,0.8 c-1.2,1.3-0.8,1.6-3.2,2.1c-2.4,0.5-1,1.4-0.1,1.6c1.1,0.3,3.7,0.7,5.5-1.8l-0.1,0.3c0.5,0.4,0.4,2.7,0.5,4.4 c0.1,1.7,0.2,3.2,0.5,4.1c0.3,0.9,0.7,3.3,3.9,2.6C29.1,38.3,31.1,37.5,31.3,30'/%3E%3Cpath class='st1' d='M38.3,25.3c-4.4,0.9-4.7-0.6-4.7-0.6c4.7-7,6.7-15.8,5-18c-4.6-5.9-12.6-3.1-12.7-3l0,0 c-0.9-0.2-1.9-0.3-3-0.3c-2,0-3.5,0.5-4.7,1.4c0,0-14.3-5.9-13.6,7.4c0.1,2.8,4,21.3,8.7,15.7c1.7-2,3.3-3.8,3.3-3.8 c0.8,0.5,1.8,0.8,2.8,0.7l0.1-0.1c0,0.3,0,0.5,0,0.8c-1.2,1.3-0.8,1.6-3.2,2.1c-2.4,0.5-1,1.4-0.1,1.6c1.1,0.3,3.7,0.7,5.5-1.8 l-0.1,0.3c0.5,0.4,0.8,2.4,0.7,4.3c-0.1,1.9-0.1,3.2,0.3,4.2c0.4,1,0.7,3.3,3.9,2.6c2.6-0.6,4-2,4.2-4.5c0.1-1.7,0.4-1.5,0.5-3 l0.2-0.7c0.3-2.3,0-3.1,1.7-2.8l0.4,0c1.2,0.1,2.8-0.2,3.7-0.6C39,26.4,40.2,24.9,38.3,25.3L38.3,25.3z'/%3E%3Cpath class='st2' d='M21.8,26.6c-0.1,4.4,0,8.8,0.5,9.8c0.4,1.1,1.3,3.2,4.5,2.5c2.6-0.6,3.6-1.7,4-4.1c0.3-1.8,0.9-6.7,1-7.7'/%3E%3Cpath class='st2' d='M18,4.7c0,0-14.3-5.8-13.6,7.4c0.1,2.8,4,21.3,8.7,15.7c1.7-2,3.2-3.7,3.2-3.7'/%3E%3Cpath class='st2' d='M25.7,3.6c-0.5,0.2,7.9-3.1,12.7,3c1.7,2.2-0.3,11-5,18'/%3E%3Cpath class='st3' d='M33.5,24.6c0,0,0.3,1.5,4.7,0.6c1.9-0.4,0.8,1.1-1.2,2c-1.6,0.8-5.3,0.9-5.3-0.1 C31.6,24.5,33.6,25.3,33.5,24.6c-0.1-0.6-1.1-1.2-1.7-2.7c-0.5-1.3-7.3-11.2,1.9-9.7c0.3-0.1-2.4-8.7-11-8.9 c-8.6-0.1-8.3,10.6-8.3,10.6'/%3E%3Cpath class='st2' d='M19.4,25.6c-1.2,1.3-0.8,1.6-3.2,2.1c-2.4,0.5-1,1.4-0.1,1.6c1.1,0.3,3.7,0.7,5.5-1.8c0.5-0.8,0-2-0.7-2.3 C20.5,25.1,20,24.9,19.4,25.6L19.4,25.6z'/%3E%3Cpath class='st2' d='M19.3,25.5c-0.1-0.8,0.3-1.7,0.7-2.8c0.6-1.6,2-3.3,0.9-8.5c-0.8-3.9-6.5-0.8-6.5-0.3c0,0.5,0.3,2.7-0.1,5.2 c-0.5,3.3,2.1,6,5,5.7'/%3E%3Cpath class='st4' d='M18,13.8c0,0.2,0.3,0.7,0.8,0.7c0.5,0.1,0.9-0.3,0.9-0.5c0-0.2-0.3-0.4-0.8-0.4C18.4,13.6,18,13.7,18,13.8 L18,13.8z'/%3E%3Cpath class='st5' d='M32,13.5c0,0.2-0.3,0.7-0.8,0.7c-0.5,0.1-0.9-0.3-0.9-0.5c0-0.2,0.3-0.4,0.8-0.4C31.6,13.2,32,13.3,32,13.5 L32,13.5z'/%3E%3Cpath class='st2' d='M33.7,12.2c0.1,1.4-0.3,2.4-0.4,3.9c-0.1,2.2,1,4.7-0.6,7.2'/%3E%3Cpath class='st6' d='M2.7,6.6'/%3E%3C/g%3E%3C/svg%3E%0A") 0 0 no-repeat;
*/
background: 'url(data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMDUgNTAiPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDojZmZmO30uY2xzLTJ7ZmlsbDojMzI2ODkzO308L3N0eWxlPjwvZGVmcz48dGl0bGU+cGdBZG1pbjwvdGl0bGU+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNNTguOTQsNDEuNGEyLjQ4LDIuNDgsMCwwLDEtMi4yNy0zLjQ5TDY0LDIxLjI5VjZhNiw2LDAsMCwwLTYtNkg2QTYsNiwwLDAsMCwwLDZWNDRhNiw2LDAsMCwwLDYsNkg1OGE2LDYsMCwwLDAsNi02VjQxLjRaIi8+PHBhdGggY2xhc3M9ImNscy0yIiBkPSJNMjkuMjUsMzAuMTdhMTMuMTMsMTMuMTMsMCwwLDEtMS44Mi02LjkzLDEzLDEzLDAsMCwxLDEuODItNi44OCwxMi41LDEyLjUsMCwwLDEsMS40OC0xLjk1LDEwLjQ0LDEwLjQ0LDAsMCwwLTMuMjUtMi44OSwxMS4xNiwxMS4xNiwwLDAsMC01LjY1LTEuNDVxLTQuNDgsMC02LjcyLDIuNjRWMTAuNDRINy41MVY0MC4zNmExLDEsMCwwLDAsMSwxaDZhMSwxLDAsMCwwLDEtMVYzMS4xOWE4LjQ3LDguNDcsMCwwLDAsNi4zNCwyLjQsMTEuMjYsMTEuMjYsMCwwLDAsNS42NS0xLjQ1LDEwLjUzLDEwLjUzLDAsMCwwLDIuMDYtMS41NkMyOS40NCwzMC40NCwyOS4zNCwzMC4zMSwyOS4yNSwzMC4xN1pNMjMuNiwyNS44YTQuNTIsNC41MiwwLDAsMS0zLjQ1LDEuNDQsNC40OCw0LjQ4LDAsMCwxLTMuNDQtMS40NCw1LjYsNS42LDAsMCwxLTEuMzUtNCw1LjU5LDUuNTksMCwwLDEsMS4zNS00LDQuNDYsNC40NiwwLDAsMSwzLjQ0LTEuNDUsNC40OSw0LjQ5LDAsMCwxLDMuNDUsMS40NSw1LjYzLDUuNjMsMCwwLDEsMS4zNCw0QTUuNjQsNS42NCwwLDAsMSwyMy42LDI1LjhaIi8+PHBhdGggY2xhc3M9ImNscy0yIiBkPSJNNTYuNDksMTIuNjNWMzEuMjRxMCw2LjM1LTMuNDQsOS41MXQtOS45MiwzLjE3YTI1LjQyLDI1LjQyLDAsMCwxLTYuMy0uNzUsMTUsMTUsMCwwLDEtNS0yLjIzbDIuODktNS41OWExMC4xNywxMC4xNywwLDAsMCwzLjUxLDEuNzksMTQuMzcsMTQuMzcsMCwwLDAsNC4xOC42NUE2LjUzLDYuNTMsMCwwLDAsNDcsMzYuNGE1LjM3LDUuMzcsMCwwLDAsMS40Ny00LjExdi0uNzZjLTEuNTQsMS44LTMuNzksMi42OS02Ljc2LDIuNjlhMTEuNywxMS43LDAsMCwxLTUuNTktMS4zNkExMC4zNywxMC4zNywwLDAsMSwzMi4wOSwyOWExMC44OSwxMC44OSwwLDAsMS0xLjUxLTUuNzcsMTAuODYsMTAuODYsMCwwLDEsMS41MS01Ljc0LDEwLjQyLDEwLjQyLDAsMCwxLDQuMDctMy44NiwxMS43MSwxMS43MSwwLDAsMSw1LjU5LTEuMzdjMy4yNSwwLDUuNjMsMS4wNiw3LjE0LDMuMTVWMTIuNjNabS05LjMsMTMuOTVhNC40LDQuNCwwLDAsMCwxLjQtMy4zNiw0LjM0LDQuMzQsMCwwLDAtMS4zOC0zLjM0LDUuNjUsNS42NSwwLDAsMC03LjE2LDAsNC4zLDQuMywwLDAsMC0xLjQxLDMuMzQsNC4zNSw0LjM1LDAsMCwwLDEuNDMsMy4zNiw1LjA4LDUuMDgsMCwwLDAsMy41NywxLjNBNSw1LDAsMCwwLDQ3LjE5LDI2LjU4WiIvPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTgzLjQzLDMyLjg5SDcxbC0yLDUuMDlhMSwxLDAsMCwxLS45My42Mkg2MS43M2ExLDEsMCwwLDEtLjkxLTEuNEw3Mi45MSw5LjhhMSwxLDAsMCwxLC45Mi0uNmg2Ljg5YTEsMSwwLDAsMSwuOTEuNkw5My43NywzNy4yYTEsMSwwLDAsMS0uOTIsMS40SDg2LjQxYTEsMSwwLDAsMS0uOTMtLjYyWk04MSwyNi43NmwtMy43OC05LjQxLTMuNzgsOS40MVoiLz48cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik0xMjAuNDQsOC40NFYzNy42YTEsMSwwLDAsMS0xLDFoLTUuNmExLDEsMCwwLDEtMS0xVjM2LjMzUTExMC42MiwzOSwxMDYuMTYsMzlhMTEuMjksMTEuMjksMCwwLDEtNS42Ny0xLjQ1LDEwLjU0LDEwLjU0LDAsMCwxLTQtNC4xNEExMi42MiwxMi42MiwwLDAsMSw5NSwyNy4xOCwxMi41MywxMi41MywwLDAsMSw5Ni40NCwyMWExMC4zNSwxMC4zNSwwLDAsMSw0LTQuMDksMTEuNDgsMTEuNDgsMCwwLDEsNS42Ny0xLjQzLDguMjQsOC4yNCwwLDAsMSw2LjMsMi4zNVY4LjQ0YTEsMSwwLDAsMSwxLTFoNkExLDEsMCwwLDEsMTIwLjQ0LDguNDRabS05LjE5LDIyLjc1YTUuNzEsNS43MSwwLDAsMCwxLjM0LTQsNS42LDUuNiwwLDAsMC0xLjMyLTMuOTUsNC40Nyw0LjQ3LDAsMCwwLTMuNDMtMS40Myw0LjUzLDQuNTMsMCwwLDAtMy40NCwxLjQzLDUuNTEsNS41MSwwLDAsMC0xLjM0LDMuOTUsNS42Nyw1LjY3LDAsMCwwLDEuMzQsNCw0Ljc3LDQuNzcsMCwwLDAsNi44NSwwWiIvPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTE2MSwxOGMxLjY2LDEuNjgsMi41LDQuMjEsMi41LDcuNnYxMmExLDEsMCwwLDEtMSwxaC02YTEsMSwwLDAsMS0xLTFWMjYuODhhNS42Nyw1LjY3LDAsMCwwLS45LTMuNTMsMy4wOSwzLjA5LDAsMCwwLTIuNTUtMS4xMywzLjYyLDMuNjIsMCwwLDAtMi44OSwxLjI2LDUuNzEsNS43MSwwLDAsMC0xLjEsMy44MlYzNy42YTEsMSwwLDAsMS0xLDFoLTZhMSwxLDAsMCwxLTEtMVYyNi44OGMwLTMuMTEtMS4xNC00LjY2LTMuNDQtNC42NmEzLjcsMy43LDAsMCwwLTIuOTQsMS4yNiw1LjcxLDUuNzEsMCwwLDAtMS4wOSwzLjgyVjM3LjZhMSwxLDAsMCwxLTEsMWgtNmExLDEsMCwwLDEtMS0xVjE2Ljg0YTEsMSwwLDAsMSwxLTFoNS42YTEsMSwwLDAsMSwxLDF2MS4zOWE4LDgsMCwwLDEsMy0yLjA4LDEwLjIzLDEwLjIzLDAsMCwxLDMuOC0uNjksMTAsMTAsMCwwLDEsNC4yOS44OEE3LjI4LDcuMjgsMCwwLDEsMTQ2LjQyLDE5YTguODUsOC44NSwwLDAsMSwzLjQxLTIuNjUsMTAuOTMsMTAuOTMsMCwwLDEsNC40OS0uOTJBOSw5LDAsMCwxLDE2MSwxOFoiLz48cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik0xNjguMTIsMTIuMWEzLjkxLDMuOTEsMCwwLDEtMS4zNC0yLjc5QTQuMTYsNC4xNiwwLDAsMSwxNjgsNi4xOWE1LDUsMCwwLDEsMy42Ny0xLjM2QTUuMjUsNS4yNSwwLDAsMSwxNzUuMTgsNmEzLjc1LDMuNzUsMCwwLDEsMS4zNCwzLDQuMSw0LjEsMCwwLDEtMS4zNCwzLjEzLDUuNjgsNS42OCwwLDAsMS03LjA2LDBabS41NCwzLjc0aDZhMSwxLDAsMCwxLDEsMVYzNy42YTEsMSwwLDAsMS0xLDFoLTZhMSwxLDAsMCwxLTEtMVYxNi44NEExLDEsMCwwLDEsMTY4LjY2LDE1Ljg0WiIvPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTIwMS41NSwxOHEyLjU5LDIuNTIsMi41OSw3LjZ2MTJhMSwxLDAsMCwxLTEsMWgtNmExLDEsMCwwLDEtMS0xVjI2Ljg4cTAtNC42Ni0zLjc0LTQuNjZhNC4zLDQuMywwLDAsMC0zLjMsMS4zNCw1LjgzLDUuODMsMCwwLDAtMS4yNCw0djEwYTEsMSwwLDAsMS0xLDFoLTZhMSwxLDAsMCwxLTEtMVYxNi44NGExLDEsMCwwLDEsMS0xaDUuNjFhMSwxLDAsMCwxLDEsMXYxLjQ3YTkuMDUsOS4wNSwwLDAsMSwzLjE5LTIuMTIsMTAuNzgsMTAuNzgsMCwwLDEsNC0uNzNBOS4zNCw5LjM0LDAsMCwxLDIwMS41NSwxOFoiLz48L3N2Zz4=) 0 0 no-repeat',
backgroundPositionY: 'center',
},
menus: {
'& .AppMenuBar-menus': {
display: 'flex',
alignItems: 'center',
gap: '2px',
marginLeft: '16px',
'& .MuiButton-containedPrimary': {
padding: '1px 8px',
}
},
userMenu: {
'& .AppMenuBar-userMenu': {
marginLeft: 'auto',
'& .MuiButton-containedPrimary': {
fontSize: '0.825rem',
},
'& .AppMenuBar-gravatar': {
marginRight: '4px',
}
},
gravatar: {
marginRight: '4px',
}
}));
export default function AppMenuBar() {
const classes = useStyles();
const [,setRefresh] = useState(false);
const pgAdmin = usePgAdmin();
@ -97,10 +96,10 @@ export default function AppMenuBar() {
const userMenuInfo = pgAdmin.Browser.utils.userMenuInfo;
return(
<Box className={classes.root} data-test="app-menu-bar">
<div className={classes.logo} />
<div className={classes.menus}>
return (
<StyledBox data-test="app-menu-bar">
<div className='AppMenuBar-logo' />
<div className='AppMenuBar-menus'>
{pgAdmin.Browser.MainMenus?.map((menu)=>{
return (
<PgMenu
@ -124,11 +123,11 @@ export default function AppMenuBar() {
})}
</div>
{userMenuInfo &&
<div className={classes.userMenu}>
<div className='AppMenuBar-userMenu'>
<PgMenu
menuButton={
<PrimaryButton data-test="loggedin-username">
<div className={classes.gravatar}>
<div className='AppMenuBar-gravatar'>
{userMenuInfo.gravatar &&
<img src={userMenuInfo.gravatar} width = "18" height = "18"
alt ={`Gravatar for ${ userMenuInfo.username }`} />}
@ -146,6 +145,6 @@ export default function AppMenuBar() {
})}
</PgMenu>
</div>}
</Box>
</StyledBox>
);
}

View File

@ -1,13 +1,4 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2024, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import { makeStyles } from '@mui/styles';
import { styled } from '@mui/material/styles';
import React from 'react';
import PropTypes from 'prop-types';
import gettext from 'sources/gettext';
@ -15,6 +6,13 @@ import BaseUISchema from '../SchemaView/base_schema.ui';
import SchemaView from '../SchemaView';
import { isEmptyString } from 'sources/validators';
const StyledSchemaView = styled(SchemaView)(({theme}) => ({
'& .ChangeOwnershipContent-root': {
...theme.mixins.tabPanel,
}
}));
class ChangeOwnershipSchema extends BaseUISchema {
constructor(deletedUser, adminUserList, noOfSharedServers) {
super({
@ -50,17 +48,11 @@ class ChangeOwnershipSchema extends BaseUISchema {
}
}
const useStyles = makeStyles((theme)=>({
root: {
...theme.mixins.tabPanel,
},
}));
export default function ChangeOwnershipContent({onSave, onClose, deletedUser, userList, noOfSharedServers}) {
const classes = useStyles();
const objChangeOwnership = new ChangeOwnershipSchema(deletedUser, userList, noOfSharedServers);
return<SchemaView
return <StyledSchemaView
formType={'dialog'}
getInitData={() => { /*This is intentional (SonarQube)*/ }}
schema={objChangeOwnership}
@ -74,7 +66,7 @@ export default function ChangeOwnershipContent({onSave, onClose, deletedUser, us
disableSqlHelp={true}
disableDialogHelp={true}
isTabView={false}
formClassName={classes.root}
formClassName='ChangeOwnershipContent-root'
/>;
}
ChangeOwnershipContent.propTypes = {

View File

@ -1,19 +1,16 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2024, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import { makeStyles } from '@mui/styles';
import { styled } from '@mui/material/styles';
import React from 'react';
import PropTypes from 'prop-types';
import gettext from 'sources/gettext';
import BaseUISchema from '../SchemaView/base_schema.ui';
import SchemaView from '../SchemaView';
const StyledSchemaView = styled(SchemaView )(({theme}) => ({
'& .ChangePasswordContent-root': {
...theme.mixins.tabPanel,
}
}));
class ChangePasswordSchema extends BaseUISchema {
constructor(user, isPgpassFileUsed, hasCsrfToken=false, showUser=true) {
super({
@ -73,17 +70,9 @@ class ChangePasswordSchema extends BaseUISchema {
}
}
const useStyles = makeStyles((theme)=>({
root: {
...theme.mixins.tabPanel,
},
}));
export default function ChangePasswordContent({getInitData=() => { /*This is intentional (SonarQube)*/ },
onSave, onClose, hasCsrfToken=false, showUser=true}) {
const classes = useStyles();
return<SchemaView
return <StyledSchemaView
formType={'dialog'}
getInitData={getInitData}
schema={new ChangePasswordSchema('', false, hasCsrfToken, showUser)}
@ -97,7 +86,7 @@ export default function ChangePasswordContent({getInitData=() => { /*This is int
disableSqlHelp={true}
disableDialogHelp={true}
isTabView={false}
formClassName={classes.root}
formClassName='ChangePasswordContent-root'
/>;
}
ChangePasswordContent.propTypes = {

View File

@ -1,5 +1,5 @@
import React from 'react';
import { useModalStyles } from '../helpers/ModalProvider';
import { ModalContent, ModalFooter } from '../../../static/js/components/ModalContent';
import gettext from 'sources/gettext';
import { Box } from '@mui/material';
import { DefaultButton, PrimaryButton } from '../components/Buttons';
@ -10,24 +10,23 @@ import HTMLReactParser from 'html-react-parser';
import PropTypes from 'prop-types';
export default function ConfirmSaveContent({closeModal, text, onDontSave, onSave}) {
const classes = useModalStyles();
return (
<Box display="flex" flexDirection="column" height="100%">
<ModalContent>
<Box flexGrow="1" p={2}>{typeof(text) == 'string' ? HTMLReactParser(text) : text}</Box>
<Box className={classes.footer}>
<ModalFooter>
<DefaultButton data-test="close" startIcon={<CloseIcon />} onClick={()=>{
closeModal();
}} >{gettext('Cancel')}</DefaultButton>
<DefaultButton data-test="dont-save" className={classes.margin} startIcon={<DeleteRoundedIcon />} onClick={()=>{
<DefaultButton data-test="dont-save" startIcon={<DeleteRoundedIcon />} onClick={()=>{
onDontSave?.();
closeModal();
}} >{gettext('Don\'t save')}</DefaultButton>
<PrimaryButton data-test="save" className={classes.margin} startIcon={<CheckRoundedIcon />} onClick={()=>{
<PrimaryButton data-test="save" startIcon={<CheckRoundedIcon />} onClick={()=>{
onSave?.();
closeModal();
}} autoFocus={true} >{gettext('Save')}</PrimaryButton>
</Box>
</Box>
</ModalFooter>
</ModalContent>
);
}

View File

@ -14,11 +14,11 @@ import { DefaultButton, PrimaryButton } from '../components/Buttons';
import CloseIcon from '@mui/icons-material/CloseRounded';
import CheckRoundedIcon from '@mui/icons-material/CheckRounded';
import PropTypes from 'prop-types';
import { useModalStyles } from '../helpers/ModalProvider';
import { FormFooterMessage, InputCheckbox, InputText, MESSAGE_TYPE } from '../components/FormComponents';
import { ModalContent, ModalFooter } from '../../../static/js/components/ModalContent';
export default function ConnectServerContent({closeModal, data, onOK, setHeight}) {
const classes = useModalStyles();
const containerRef = useRef();
const firstEleRef = useRef();
const okBtnRef = useRef();
@ -58,7 +58,7 @@ export default function ConnectServerContent({closeModal, data, onOK, setHeight}
}
return (
<Box display="flex" flexDirection="column" className={classes.container} ref={containerRef}>
<ModalContent ref={containerRef}>
<Box flexGrow="1" p={2}>
{data.prompt_tunnel_password && <>
<Box>
@ -105,11 +105,11 @@ export default function ConnectServerContent({closeModal, data, onOK, setHeight}
position: 'unset', padding: '12px 0px 0px'
}}/>
</Box>
<Box className={classes.footer}>
<ModalFooter>
<DefaultButton data-test="close" startIcon={<CloseIcon />} onClick={()=>{
closeModal();
}} >{gettext('Cancel')}</DefaultButton>
<PrimaryButton ref={okBtnRef} data-test="save" className={classes.margin} startIcon={<CheckRoundedIcon />} onClick={()=>{
<PrimaryButton ref={okBtnRef} data-test="save" startIcon={<CheckRoundedIcon />} onClick={()=>{
let postFormData = new FormData();
if(data.prompt_tunnel_password) {
postFormData.append('tunnel_password', formData.tunnel_password);
@ -124,8 +124,8 @@ export default function ConnectServerContent({closeModal, data, onOK, setHeight}
onOK?.(postFormData);
closeModal();
}} >{gettext('OK')}</PrimaryButton>
</Box>
</Box>
</ModalFooter>
</ModalContent>
);
}

View File

@ -11,19 +11,16 @@ import React, { useState, useRef, useEffect } from 'react';
import PropTypes from 'prop-types';
import gettext from 'sources/gettext';
import url_for from 'sources/url_for';
import { Box } from '@mui/material';
import CloseIcon from '@mui/icons-material/CloseRounded';
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
import CheckRoundedIcon from '@mui/icons-material/CheckRounded';
import HelpIcon from '@mui/icons-material/Help';
import { DefaultButton, PrimaryButton, PgIconButton } from '../components/Buttons';
import { useModalStyles } from '../helpers/ModalProvider';
import { FormFooterMessage, FormNote, InputText, MESSAGE_TYPE } from '../components/FormComponents';
import { ModalContent, ModalFooter } from '../../../static/js/components/ModalContent';
export default function MasterPasswordContent({ closeModal, onResetPassowrd, onOK, onCancel, setHeight, isPWDPresent, data, keyringName}) {
const classes = useModalStyles();
const containerRef = useRef();
const firstEleRef = useRef();
const okBtnRef = useRef();
@ -57,9 +54,8 @@ export default function MasterPasswordContent({ closeModal, onResetPassowrd, onO
setHeight?.(containerRef.current?.offsetHeight);
}, [containerRef.current]);
return (
<Box display="flex" flexDirection="column" className={classes.container} ref={containerRef}>
<ModalContent ref={containerRef}>
{isKeyring ?
<Box flexGrow="1" p={2}>
<Box>
@ -98,7 +94,7 @@ export default function MasterPasswordContent({ closeModal, onResetPassowrd, onO
}} />
</Box>
}
<Box className={classes.footer}>
<ModalFooter>
<Box style={{ marginRight: 'auto' }}>
<PgIconButton data-test="help-masterpassword" title={gettext('Help')} style={{ padding: '0.3rem', paddingLeft: '0.7rem' }} startIcon={<HelpIcon />} onClick={() => {
let _url = url_for('help.static', {
@ -120,7 +116,7 @@ export default function MasterPasswordContent({ closeModal, onResetPassowrd, onO
closeModal();
}} >{gettext('Cancel')}</DefaultButton>
}
<PrimaryButton ref={okBtnRef} data-test="save" className={classes.margin} startIcon={<CheckRoundedIcon />}
<PrimaryButton ref={okBtnRef} data-test="save" startIcon={<CheckRoundedIcon />}
disabled={formData.password.length == 0}
onClick={() => {
let postFormData = new FormData();
@ -132,8 +128,8 @@ export default function MasterPasswordContent({ closeModal, onResetPassowrd, onO
>
{gettext('OK')}
</PrimaryButton>
</Box>
</Box>);
</ModalFooter>
</ModalContent>);
}
MasterPasswordContent.propTypes = {

View File

@ -14,12 +14,11 @@ import { DefaultButton, PrimaryButton } from '../components/Buttons';
import CloseIcon from '@mui/icons-material/CloseRounded';
import CheckRoundedIcon from '@mui/icons-material/CheckRounded';
import PropTypes from 'prop-types';
import { useModalStyles } from '../helpers/ModalProvider';
import { ModalContent, ModalFooter } from '../../../static/js/components/ModalContent';
import { InputText } from '../components/FormComponents';
import { isEmptyString } from '../../../static/js/validators';
export default function NamedRestoreContent({closeModal, onOK, setHeight}) {
const classes = useModalStyles();
const containerRef = useRef();
const firstEleRef = useRef();
const okBtnRef = useRef();
@ -55,7 +54,7 @@ export default function NamedRestoreContent({closeModal, onOK, setHeight}) {
const isOKDisabled = isEmptyString(formData.namedRestorePoint);
return (
<Box display="flex" flexDirection="column" className={classes.container} ref={containerRef}>
<ModalContent ref={containerRef}>
<Box flexGrow="1" p={2}>
<Box>
<span style={{fontWeight: 'bold'}}>
@ -67,18 +66,18 @@ export default function NamedRestoreContent({closeModal, onOK, setHeight}) {
onChange={(e)=>onTextChange(e, 'namedRestorePoint')} onKeyDown={(e)=>onKeyDown(e)}/>
</Box>
</Box>
<Box className={classes.footer}>
<ModalFooter>
<DefaultButton data-test="close" startIcon={<CloseIcon />} onClick={()=>{
closeModal();
}} >{gettext('Cancel')}</DefaultButton>
<PrimaryButton ref={okBtnRef} data-test="save" disabled={isOKDisabled} className={classes.margin} startIcon={<CheckRoundedIcon />} onClick={()=>{
<PrimaryButton ref={okBtnRef} data-test="save" disabled={isOKDisabled} startIcon={<CheckRoundedIcon />} onClick={()=>{
let postFormData = new FormData();
postFormData.append('value', formData.namedRestorePoint);
onOK?.(postFormData);
closeModal();
}} >{gettext('OK')}</PrimaryButton>
</Box>
</Box>
</ModalFooter>
</ModalContent>
);
}

View File

@ -18,11 +18,10 @@ import CheckRoundedIcon from '@mui/icons-material/CheckRounded';
import gettext from 'sources/gettext';
import { DefaultButton, PrimaryButton } from '../components/Buttons';
import { useModalStyles } from '../helpers/ModalProvider';
import { ModalContent, ModalFooter } from '../../../static/js/components/ModalContent';
import { FormFooterMessage, InputText, MESSAGE_TYPE } from '../components/FormComponents';
export default function RenameTabContent({ panelId, panelDocker, closeModal}) {
const classes = useModalStyles();
const containerRef = useRef();
const okBtnRef = useRef();
const panelData = useMemo(()=>panelDocker.find(panelId));
@ -63,7 +62,7 @@ export default function RenameTabContent({ panelId, panelDocker, closeModal}) {
const isValid = formData['title'].length != 0;
return (
<Box display="flex" flexDirection="column" className={classes.container} ref={containerRef}>
<ModalContent ref={containerRef}>
<Box padding="8px">
<Box marginBottom="4px">Current: {initialTitle}</Box>
<InputText type="text" value={formData['title']} controlProps={{ maxLength: null }}
@ -71,15 +70,15 @@ export default function RenameTabContent({ panelId, panelDocker, closeModal}) {
</Box>
<FormFooterMessage type={MESSAGE_TYPE.ERROR} message={!isValid ? gettext('Title cannot be empty') : ''}
closable={false} style={{position: 'initial'}} />
<Box className={classes.footer}>
<ModalFooter>
<DefaultButton data-test="close" startIcon={<CloseIcon />} onClick={() => {
closeModal();
}} >{gettext('Cancel')}</DefaultButton>
<PrimaryButton data-test="save" startIcon={<CheckRoundedIcon />} onClick={onOkClick} className={classes.margin} disabled={!isValid}>
<PrimaryButton data-test="save" startIcon={<CheckRoundedIcon />} onClick={onOkClick} disabled={!isValid}>
{gettext('OK')}
</PrimaryButton>
</Box>
</Box>
</ModalFooter>
</ModalContent>
);
}

View File

@ -17,19 +17,17 @@ import CloseIcon from '@mui/icons-material/CloseRounded';
import HelpIcon from '@mui/icons-material/Help';
import { DefaultButton, PgIconButton } from '../components/Buttons';
import { useModalStyles } from '../helpers/ModalProvider';
import { ModalContent, ModalFooter }from '../../../static/js/components/ModalContent';
export default function UrlDialogContent({ url, helpFile, onClose }) {
const classes = useModalStyles();
return (
<Box display="flex" flexDirection="column" height="100%" className={classes.container}>
<ModalContent>
<Box flexGrow="1">
<iframe src={url} width="100%" height="100%" onLoad={(e)=>{
e.target?.contentWindow?.focus();
}}/>
</Box>
<Box className={classes.footer}>
<ModalFooter>
<Box style={{ marginRight: 'auto' }}>
<PgIconButton data-test={'help-'+helpFile} title={gettext('Help')} icon={<HelpIcon />} onClick={() => {
let _url = url_for('help.static', {
@ -42,8 +40,8 @@ export default function UrlDialogContent({ url, helpFile, onClose }) {
<DefaultButton data-test="close" startIcon={<CloseIcon />} onClick={() => {
onClose();
}} >{gettext('Close')}</DefaultButton>
</Box>
</Box>
</ModalFooter>
</ModalContent>
);
}

View File

@ -1,53 +1,45 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2024, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import { makeStyles } from '@mui/styles';
import { styled } from '@mui/material/styles';
import React from 'react';
import clsx from 'clsx';
import _ from 'lodash';
import ArrowRightAltIcon from '@mui/icons-material/ArrowRightAlt';
import FiberManualRecordIcon from '@mui/icons-material/FiberManualRecord';
import HTMLReactParse from 'html-react-parser';
import { commonTableStyles } from '../Theme';
import PropTypes from 'prop-types';
import gettext from 'sources/gettext';
import Table from '../components/Table';
const useStyles = makeStyles((theme)=>({
collapsible: {
const StyledTable = styled(Table)(({theme}) => ({
'& .Analysis-collapsible': {
cursor: 'pointer',
},
collapseParent: {
'& .Analysis-collapseParent': {
borderBottom: '2px dashed '+theme.palette.primary.main,
},
level2: {
'& .Analysis-textRight': {
textAlign: 'right',
},
'& .Analysis-level2': {
backgroundColor: theme.otherVars.explain.sev2.bg,
color: theme.otherVars.explain.sev2.color,
},
level3: {
'& .Analysis-level3': {
backgroundColor: theme.otherVars.explain.sev3.bg,
color: theme.otherVars.explain.sev3.color,
},
level4: {
'& .Analysis-level4': {
backgroundColor: theme.otherVars.explain.sev4.bg,
color: theme.otherVars.explain.sev4.color,
},
textRight: {
textAlign: 'right',
},
}));
function getRowClassname(classes, data, collapseParent) {
function getRowClassname(data, collapseParent) {
let className = [];
if(data['Plans']?.length > 0) {
className.push(classes.collapsible);
className.push('Analysis-collapsible');
}
if(collapseParent) {
className.push(classes.collapseParent);
className.push('Analysis-collapseParent');
}
return className;
}
@ -70,11 +62,11 @@ NodeText.propTypes = {
function ExplainRow({row, show, activeExId, setActiveExId, collapsedExId, toggleCollapseExId}) {
let data = row['data'];
const classes = useStyles();
const exId = `pga_ex_${data['level'].join('_')}`;
const parentExId = `pga_ex_${data['parent_node']}`;
const collapsed = collapsedExId.findIndex((v)=>parentExId.startsWith(v)) > -1;
const className = getRowClassname(classes, data, collapsedExId.indexOf(exId) > -1);
const className = getRowClassname(data, collapsedExId.indexOf(exId) > -1);
let onRowClick = (e)=>{
toggleCollapseExId(e.currentTarget.getAttribute('data-ex-id'), data['Plans']?.length);
};
@ -82,35 +74,35 @@ function ExplainRow({row, show, activeExId, setActiveExId, collapsedExId, toggle
return (
<tr onMouseEnter={(e)=>{setActiveExId(e.currentTarget.getAttribute('data-ex-id'));}}
onMouseLeave={()=>{setActiveExId(null);}}
className={clsx(className)} data-parent={parentExId}
className={className} data-parent={parentExId}
data-ex-id={`pga_ex_${data['level'].join('_')}`}
style={collapsed ? {display: 'none'} : {}}
onClick={onRowClick}>
<td>
<FiberManualRecordIcon fontSize="small" style={{visibility: activeExId==parentExId ? 'visible' : 'hidden'}} />
</td>
<td className={classes.textRight}>{data['_serial']}.</td>
<td className='Analysis-textRight'>{data['_serial']}.</td>
<td style={{paddingLeft: data['level'].length*30+'px'}} title={row['tooltip_text']}>
<NodeText displayText={row['display_text']} extraInfo={row['node_extra_info']} />
</td>
<td className={clsx(classes.textRight, classes['level'+data['exclusive_flag']])} style={show.show_timings ? {} : {display: 'none'}}>
<td className={'Analysis-textRight ' + 'Analysis-' +['level'+data['exclusive_flag']]} style={show.show_timings ? {} : {display: 'none'}}>
{data['exclusive'] && (data['exclusive']+' ms')}
</td>
<td className={clsx(classes.textRight, classes['level'+data['inclusive_flag']])} style={show.show_timings ? {} : {display: 'none'}}>
<td className={'Analysis-textRight ' + 'Analysis-' +['level'+data['inclusive_flag']]} style={show.show_timings ? {} : {display: 'none'}}>
{data['inclusive'] && (data['inclusive']+' ms')}
</td>
<td className={clsx(classes.textRight, classes['level'+data['rowsx_flag']])} style={show.show_rowsx ? {} : {display: 'none'}}>
<td className={'Analysis-textRight ' + 'Analysis-' +['level'+data['rowsx_flag']]} style={show.show_rowsx ? {} : {display: 'none'}}>
{!_.isUndefined(data['rowsx_flag'])
&& (data['rowsx_direction'] == 'positive' ? <>&uarr;</> : <>&darr;</>)
}&nbsp;{data['rowsx']}
</td>
<td className={classes.textRight} style={(show.show_rowsx || show.show_rows) ? {} : {display: 'none'}}>
<td className='Analysis-textRight' style={(show.show_rowsx || show.show_rows) ? {} : {display: 'none'}}>
{data['Actual Rows']}
</td>
<td className={classes.textRight} style={(show.show_rowsx || show.show_plan_rows) ? {} : {display: 'none'}}>
<td className='Analysis-textRight' style={(show.show_rowsx || show.show_plan_rows) ? {} : {display: 'none'}}>
{data['Plan Rows']}
</td>
<td className={classes.textRight} style={(show.show_rowsx || show.show_rows) ? {} : {display: 'none'}}>
<td className='Analysis-textRight' style={(show.show_rowsx || show.show_rows) ? {} : {display: 'none'}}>
{data['Actual Loops']}
</td>
</tr>
@ -151,7 +143,6 @@ ExplainRow.propTypes = {
};
export default function Analysis({explainTable}) {
const tableClasses = commonTableStyles();
const [activeExId, setActiveExId] = React.useState();
const [collapsedExId, setCollapsedExId] = React.useState([]);
@ -165,46 +156,48 @@ export default function Analysis({explainTable}) {
});
}
};
return <table className={clsx(tableClasses.table, tableClasses.noBorder, tableClasses.borderBottom)}>
<thead>
<tr>
<th rowSpan="2" style={{width: '30px'}}></th>
<th rowSpan="2"><button disabled="">#</button></th>
<th rowSpan="2"><button disabled="">Node</button></th>
<th colSpan="2" style={explainTable.show_timings ? {} : {display: 'none'}}>
<button disabled="">{gettext('Timings')}</button>
</th>
<th style={(explainTable.show_rowsx || explainTable.show_rows || explainTable.show_plan_rows) ? {} : {display: 'none'}}
colSpan={(explainTable.show_rowsx) ? '3' : '1'}>
<button disabled="">{gettext('Rows')}</button>
</th>
<th style={(explainTable.show_rowsx || explainTable.show_rows) ? {} : {display: 'none'}} rowSpan="2">
<button disabled="">{gettext('Loops')}</button>
</th>
</tr>
<tr>
<th style={explainTable.show_timings ? {} : {display: 'none'}}>
<button disabled="">{gettext('Exclusive')}</button>
</th>
<th style={explainTable.show_timings ? {} : {display: 'none'}}>
<button disabled="">{gettext('Inclusive')}</button>
</th>
<th style={explainTable.show_rowsx ? {} : {display: 'none'}}><button disabled="">{gettext('Rows X')}</button></th>
<th style={(explainTable.show_rowsx || explainTable.show_rows) ? {} : {display: 'none'}}><button disabled="">{gettext('Actual')}</button></th>
<th style={(explainTable.show_rowsx || explainTable.show_plan_rows) ? {} : {display: 'none'}}><button disabled="">{gettext('Plan')}</button></th>
</tr>
</thead>
<tbody>
{_.sortBy(explainTable.rows,(r)=>r['data']['_serial']).map((row)=>{
return <ExplainRow key={row?.data?.arr_id} row={row} show={{
show_timings: explainTable.show_timings,
show_rowsx: explainTable.show_rowsx,
show_rows: explainTable.show_rows,
show_plan_rows: explainTable.show_plan_rows,
}} activeExId={activeExId} setActiveExId={setActiveExId} collapsedExId={collapsedExId} toggleCollapseExId={toggleCollapseExId} />;
})}
</tbody>
</table>;
return (
<StyledTable>
<thead>
<tr>
<th rowSpan="2" style={{width: '30px'}}></th>
<th rowSpan="2"><button disabled="">#</button></th>
<th rowSpan="2"><button disabled="">Node</button></th>
<th colSpan="2" style={explainTable.show_timings ? {} : {display: 'none'}}>
<button disabled="">{gettext('Timings')}</button>
</th>
<th style={(explainTable.show_rowsx || explainTable.show_rows || explainTable.show_plan_rows) ? {} : {display: 'none'}}
colSpan={(explainTable.show_rowsx) ? '3' : '1'}>
<button disabled="">{gettext('Rows')}</button>
</th>
<th style={(explainTable.show_rowsx || explainTable.show_rows) ? {} : {display: 'none'}} rowSpan="2">
<button disabled="">{gettext('Loops')}</button>
</th>
</tr>
<tr>
<th style={explainTable.show_timings ? {} : {display: 'none'}}>
<button disabled="">{gettext('Exclusive')}</button>
</th>
<th style={explainTable.show_timings ? {} : {display: 'none'}}>
<button disabled="">{gettext('Inclusive')}</button>
</th>
<th style={explainTable.show_rowsx ? {} : {display: 'none'}}><button disabled="">{gettext('Rows X')}</button></th>
<th style={(explainTable.show_rowsx || explainTable.show_rows) ? {} : {display: 'none'}}><button disabled="">{gettext('Actual')}</button></th>
<th style={(explainTable.show_rowsx || explainTable.show_plan_rows) ? {} : {display: 'none'}}><button disabled="">{gettext('Plan')}</button></th>
</tr>
</thead>
<tbody>
{_.sortBy(explainTable.rows,(r)=>r['data']['_serial']).map((row)=>{
return <ExplainRow key={row?.data?.arr_id} row={row} show={{
show_timings: explainTable.show_timings,
show_rowsx: explainTable.show_rowsx,
show_rows: explainTable.show_rows,
show_plan_rows: explainTable.show_plan_rows,
}} activeExId={activeExId} setActiveExId={setActiveExId} collapsedExId={collapsedExId} toggleCollapseExId={toggleCollapseExId} />;
})}
</tbody>
</StyledTable>
);
}
Analysis.propTypes = {

View File

@ -7,43 +7,42 @@
//
//////////////////////////////////////////////////////////////
import React from 'react';
import { styled } from '@mui/material/styles';
import { Box, Grid } from '@mui/material';
import { makeStyles } from '@mui/styles';
import gettext from 'sources/gettext';
import { commonTableStyles } from '../Theme';
import clsx from 'clsx';
import _ from 'lodash';
import PropTypes from 'prop-types';
import Table from '../components/Table';
const useStyles = makeStyles((theme)=>({
title: {
const StyledBox = styled(Box)(({theme}) => ({
'& .ExplainStatistics-title': {
fontWeight: 'bold',
padding: '4px',
backgroundColor: theme.otherVars.cardHeaderBg,
borderTopLeftRadius: theme.shape.borderRadius,
borderTopRightRadius: theme.shape.borderRadius,
},
tableRow: {
backgroundColor: theme.palette.grey[200]
},
tableName:{
fontWeight: 'bold',
},
nodeName: {
paddingLeft: '30px',
'& .ExplainStatistics-tableRow': {
backgroundColor: theme.palette.grey[200],
'& .ExplainStatistics-tableName': {
fontWeight: 'bold',
'& .ExplainStatistics-nodeName': {
paddingLeft: '30px',
}
},
},
}));
export default function ExplainStatistics({explainTable}) {
// _renderStatisticsTable
const classes = useStyles();
const tableClasses = commonTableStyles();
return (
<Box p={1}>
<StyledBox p={1}>
<Grid container spacing={1}>
<Grid item lg={6} md={12}>
<div className={classes.title}>{gettext('Statistics per Node Type')}</div>
<table className={clsx(tableClasses.table)}>
<div className='ExplainStatistics-title'>{gettext('Statistics per Node Type')}</div>
<Table >
<thead>
<tr>
<th>{gettext('Node type')}</th>
@ -67,11 +66,11 @@ export default function ExplainStatistics({explainTable}) {
</tr>;
})}
</tbody>
</table>
</Table>
</Grid>
<Grid item lg={6} md={12}>
<div className={classes.title}>{gettext('Statistics per Relation')}</div>
<table className={clsx(tableClasses.table)}>
<div className='ExplainStatistics-title'>{gettext('Statistics per Relation')}</div>
<Table>
<thead>
<tr>
<th>{gettext('Relation name')}</th>
@ -95,8 +94,8 @@ export default function ExplainStatistics({explainTable}) {
let table = explainTable.statistics.tables[key];
table.sum_of_times = _.sumBy(Object.values(table.nodes), 'sum_of_times');
return <React.Fragment key={i}>
<tr className={classes.tableRow}>
<td className={classes.tableName}>{table.name}</td>
<tr className='ExplainStatistics-tableRow'>
<td className='ExplainStatistics-tableName'>{table.name}</td>
<td>{table.count}</td>
{explainTable.show_timings && <>
<td>{Math.ceil10(table.sum_of_times, -3) + ' ms'}</td>
@ -106,7 +105,7 @@ export default function ExplainStatistics({explainTable}) {
{_.sortBy(Object.keys(table.nodes)).map((nodeKey, j)=>{
let node = table.nodes[nodeKey];
return <tr key={j}>
<td><div className={classes.nodeName}>{node.name}</div></td>
<td><div className='ExplainStatistics-nodeName'>{node.name}</div></td>
<td>{node.count}</td>
{explainTable.show_timings && <>
<td>{Math.ceil10(node.sum_of_times, -3) + ' ms'}</td>
@ -117,10 +116,10 @@ export default function ExplainStatistics({explainTable}) {
</React.Fragment>;
})}
</tbody>
</table>
</Table>
</Grid>
</Grid>
</Box>
</StyledBox>
);
}

View File

@ -7,7 +7,7 @@
//
//////////////////////////////////////////////////////////////
import { Box, Card, CardContent, CardHeader, useTheme } from '@mui/material';
import { makeStyles } from '@mui/styles';
import { styled } from '@mui/material/styles';
import React, {useEffect} from 'react';
import _ from 'lodash';
import { PgButtonGroup, PgIconButton } from '../components/Buttons';
@ -20,9 +20,40 @@ import ReactDOMServer from 'react-dom/server';
import url_for from 'sources/url_for';
import { downloadSvg } from './svg_download';
import CloseIcon from '@mui/icons-material/CloseRounded';
import { commonTableStyles } from '../Theme';
import clsx from 'clsx';
import PropTypes from 'prop-types';
import Table from '../components/Table';
const StyledBox = styled(Box)(({theme}) => ({
'& .Graphical-explainDetails': {
minWidth: '200px',
maxWidth: '300px',
position: 'absolute',
top: '0.25rem',
bottom: '0.25rem',
right: '0.25rem',
borderColor: theme.otherVars.borderColor,
// box-shadow: 0 0.125rem 0.5rem rgb(132 142 160 / 28%);
wordBreak: 'break-all',
display: 'flex',
flexDirection: 'column',
zIndex: 99,
'& .Graphical-explainContent': {
height: '100%',
overflow: 'auto',
'& .Graphical-tableBorderBottom':{
'& tbody tr:last-of-type td': {
borderBottom: '1px solid '+theme.otherVars.borderColor,
},
},
'& .Graphical-tablewrapTd': {
'& tbody td': {
whiteSpace: 'pre-wrap',
}
},
},
},
}
));
// Some predefined constants used to calculate image location and its border
let pWIDTH = 100;
@ -338,30 +369,8 @@ PlanSVG.propTypes = {
};
const useStyles = makeStyles((theme)=>({
explainDetails: {
minWidth: '200px',
maxWidth: '300px',
position: 'absolute',
top: '0.25rem',
bottom: '0.25rem',
right: '0.25rem',
borderColor: theme.otherVars.borderColor,
// box-shadow: 0 0.125rem 0.5rem rgb(132 142 160 / 28%);
wordBreak: 'break-all',
display: 'flex',
flexDirection: 'column',
zIndex: 99,
},
explainContent: {
height: '100%',
overflow: 'auto',
}
}));
export default function Graphical({planData, ctx}) {
const tableStyles = commonTableStyles();
const classes = useStyles();
const graphContainerRef = React.useRef();
const [zoomFactor, setZoomFactor] = React.useState(INIT_ZOOM_FACTOR);
const [[explainPlanTitle, explainPlanDetails], setExplainPlanDetails] = React.useState([null, null]);
@ -406,7 +415,7 @@ export default function Graphical({planData, ctx}) {
}, []);
return (
<Box ref={graphContainerRef} height="100%" width="100%" overflow="auto">
<StyledBox ref={graphContainerRef} height="100%" width="100%" overflow="auto">
<Box position="absolute" top="4px" left="4px" gap="4px" display="flex">
<PgButtonGroup size="small">
<PgIconButton title={gettext('Zoom in')} icon={<ZoomInIcon />} onClick={()=>onCmdClick('in')}/>
@ -421,22 +430,22 @@ export default function Graphical({planData, ctx}) {
onNodeClick={onNodeClick}
/>
{Boolean(explainPlanDetails) &&
<Card className={classes.explainDetails} data-label="explain-details">
<Card className='Graphical-explainDetails' data-label="explain-details">
<CardHeader title={<Box display="flex">
{explainPlanTitle}
<Box marginLeft="auto">
<PgIconButton title={gettext('Close')} icon={<CloseIcon />} size="xs" noBorder onClick={()=>setExplainPlanDetails([null, null])}/>
</Box>
</Box>} />
<CardContent className={classes.explainContent}>
<table className={clsx(tableStyles.table, tableStyles.borderBottom, tableStyles.wrapTd)}>
<CardContent className='Graphical-explainContent'>
<Table classNameRoot={'Graphical-tableBorderBottom Graphical-tablewrapTd'}>
<tbody>
<NodeDetails download={false} plan={explainPlanDetails} />
</tbody>
</table>
</Table>
</CardContent>
</Card>}
</Box>
</StyledBox>
);
}

View File

@ -7,23 +7,23 @@
//
//////////////////////////////////////////////////////////////
import { Box, Tab, Tabs } from '@mui/material';
import { styled } from '@mui/material/styles';
import React from 'react';
import _ from 'lodash';
import Graphical from './Graphical';
import TabPanel from '../components/TabPanel';
import gettext from 'sources/gettext';
import ImageMapper from './ImageMapper';
import { makeStyles } from '@mui/styles';
import Analysis from './Analysis';
import ExplainStatistics from './ExplainStatistics';
import PropTypes from 'prop-types';
import EmptyPanelMessage from '../components/EmptyPanelMessage';
const useStyles = makeStyles((theme)=>({
tabPanel: {
padding: 0,
backgroundColor: theme.palette.background.default,
},
const StyledBox = styled(Box)(({theme}) => ({
'& .Explain-tabPanel': {
padding: '0 !important',
backgroundColor: theme.palette.background.default + ' !important',
}
}));
// Some predefined constants used to calculate image location and its border
@ -466,7 +466,7 @@ function parsePlanData(data, ctx) {
}
export default function Explain({plans=[]}) {
const classes = useStyles();
const [tabValue, setTabValue] = React.useState(0);
let ctx = React.useRef({});
@ -488,12 +488,14 @@ export default function Explain({plans=[]}) {
}, [plans]);
if(_.isEmpty(plans)) {
return <Box height="100%" display="flex" flexDirection="column">
<EmptyPanelMessage text={gettext('Use Explain/Explain analyze button to generate the plan for a query. Alternatively, you can also execute "EXPLAIN (FORMAT JSON) [QUERY]".')} />
</Box>;
return (
<StyledBox height="100%" display="flex" flexDirection="column">
<EmptyPanelMessage text={gettext('Use Explain/Explain analyze button to generate the plan for a query. Alternatively, you can also execute "EXPLAIN (FORMAT JSON) [QUERY]".')} />
</StyledBox>
);
}
return (
<Box height="100%" display="flex" flexDirection="column">
<StyledBox height="100%" display="flex" flexDirection="column">
<Box>
<Tabs
value={tabValue}
@ -510,16 +512,16 @@ export default function Explain({plans=[]}) {
<Tab label="Statistics" />
</Tabs>
</Box>
<TabPanel value={tabValue} index={0} classNameRoot={classes.tabPanel}>
<TabPanel value={tabValue} index={0} classNameRoot='Explain-tabPanel'>
<Graphical planData={planData} ctx={ctx.current}/>
</TabPanel>
<TabPanel value={tabValue} index={1} classNameRoot={classes.tabPanel}>
<TabPanel value={tabValue} index={1} classNameRoot='Explain-tabPanel'>
<Analysis explainTable={ctx.current.explainTable} />
</TabPanel>
<TabPanel value={tabValue} index={2} classNameRoot={classes.tabPanel}>
<TabPanel value={tabValue} index={2} classNameRoot='Explain-tabPanel'>
<ExplainStatistics explainTable={ctx.current.explainTable} />
</TabPanel>
</Box>
</StyledBox>
);
}

View File

@ -1,6 +1,5 @@
import { Checkbox } from '@mui/material';
import { makeStyles } from '@mui/styles';
import clsx from 'clsx';
import { styled } from '@mui/material/styles';
import gettext from 'sources/gettext';
import React, { useEffect, useRef } from 'react';
import { Tree } from 'react-arborist';
@ -13,35 +12,34 @@ import EmptyPanelMessage from '../components/EmptyPanelMessage';
import CheckBoxIcon from '@mui/icons-material/CheckBox';
const useStyles = makeStyles((theme) => ({
node: {
display: 'inline-block',
paddingLeft: '1.5rem',
height: '100%',
},
checkboxStyle: {
fill: theme.palette.primary.main
},
tree: {
const Root = styled('div')(({theme}) => ({
height: '100%',
'& .PgTree-tree': {
background: theme.palette.background.default,
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
flex: 1,
},
focusedNode: {
background: theme.palette.primary.light,
},
leafNode: {
marginLeft: '1.5rem'
'& .PgTree-leafNode': {
marginLeft: '1.5rem'
},
'& .PgTree-node': {
display: 'inline-block',
paddingLeft: '1.5rem',
height: '100%',
},
'& .PgTree-focusedNode': {
background: theme.palette.primary.light,
},
},
}));
export const PgTreeSelectionContext = React.createContext();
export default function PgTreeView({ data = [], hasCheckbox = false, selectionChange = null}) {
let classes = useStyles();
let treeData = data;
const treeObj = useRef();
const treeContainerRef = useRef();
@ -69,10 +67,10 @@ export default function PgTreeView({ data = [], hasCheckbox = false, selectionCh
selectionChange?.(selectedChNodes);
};
return (<>
return (<Root>
{ treeData.length > 0 ?
<PgTreeSelectionContext.Provider value={selectedCheckBoxNodes}>
<div ref={(containerRef) => treeContainerRef.current = containerRef} className={clsx(classes.tree)}>
<div ref={(containerRef) => treeContainerRef.current = containerRef} className={'PgTree-tree'}>
<AutoSizer>
{({ width, height }) => (
<Tree
@ -97,7 +95,7 @@ export default function PgTreeView({ data = [], hasCheckbox = false, selectionCh
:
<EmptyPanelMessage text={gettext('No objects are found to display')}/>
}
</>
</Root>
);
}
@ -108,7 +106,7 @@ PgTreeView.propTypes = {
};
function Node({ node, style, tree, hasCheckbox, onNodeSelectionChange}) {
const classes = useStyles();
const pgTreeSelCtx = React.useContext(PgTreeSelectionContext);
const [isSelected, setIsSelected] = React.useState(pgTreeSelCtx.includes(node.id) || node.data?.isSelected);
const [isIndeterminate, setIsIndeterminate] = React.useState(node?.parent.level==0);
@ -173,16 +171,16 @@ function Node({ node, style, tree, hasCheckbox, onNodeSelectionChange}) {
};
return (
<div style={style} className={clsx(node.isFocused ? classes.focusedNode : '')} onClick={onSelect} onKeyDown={onKeyDown}>
<div style={style} className={node.isFocused ? 'PgTree-focusedNode' : ''} onClick={onSelect} onKeyDown={onKeyDown}>
<CollectionArrow node={node} tree={tree} selectedNodeIds={pgTreeSelCtx} />
{
hasCheckbox ? <Checkbox style={{ padding: 0 }} color="primary" className={clsx(!node.isInternal ? classes.leafNode: null)}
hasCheckbox ? <Checkbox style={{ padding: 0 }} color="primary" className={!node.isInternal ? 'PgTree-leafNode': null}
checked={isSelected}
checkedIcon={isIndeterminate ? <IndeterminateCheckBoxIcon style={{height: '1.4rem'}} />: <CheckBoxIcon style={{height: '1.4rem'}} />}
onChange={onCheckboxSelection}/> :
<span className={clsx(node.data.icon)}></span>
<span className={node.data.icon}></span>
}
<div className={clsx(node.data.icon, classes.node)}>{node.data.name}</div>
<div className={node.data.icon + ' PgTree-node'}>{node.data.name}</div>
</div>
);
}

View File

@ -7,9 +7,8 @@
//
//////////////////////////////////////////////////////////////
import React, {useRef,useState, useEffect} from 'react';
import { styled } from '@mui/material/styles';
import { CircularProgress, Typography } from '@mui/material';
import { makeStyles } from '@mui/styles';
import clsx from 'clsx';
import {useDelayDebounce} from 'sources/custom_hooks';
import {onlineHelpSearch} from './online_help';
import {menuSearch} from './menuitems_help';
@ -18,8 +17,19 @@ import PropTypes from 'prop-types';
import { InputText } from '../components/FormComponents';
import EmptyPanelMessage from '../components/EmptyPanelMessage';
const useStyles = makeStyles((theme)=>({
helpGroup: {
const StyledDiv = styled('div')(({theme}) => ({
'& .QuickSearch-loaderRoot': {
display: 'flex',
alignItems: 'center',
padding: '8px',
justifyContent: 'center',
'& .QuickSearch-loader': {
height: '25px !important',
width: '25px !important',
marginRight: '8px',
}
},
'& .QuickSearch-helpGroup': {
backgroundColor: theme.palette.grey[400],
padding: '6px',
fontSize: '0.85em',
@ -27,7 +37,7 @@ const useStyles = makeStyles((theme)=>({
display: 'flex',
alignItems: 'center',
},
searchItem: {
'& .QuickSearch-searchItem': {
display: 'flex',
flexDirection: 'column',
padding: '4px 8px',
@ -44,30 +54,18 @@ const useStyles = makeStyles((theme)=>({
pointerEvents: 'none',
}
},
showAll: {
'& .QuickSearch-showAll': {
marginLeft: 'auto',
color: 'inherit',
textDecoration: 'none'
},
loaderRoot: {
display: 'flex',
alignItems: 'center',
padding: '8px',
justifyContent: 'center',
},
loader: {
height: '25px !important',
width: '25px !important',
marginRight: '8px',
},
}));
function SearchLoader({loading=false}) {
const classes = useStyles();
if(loading) {
return (
<div className={classes.loaderRoot}>
<CircularProgress className={classes.loader} />
<div className='QuickSearch-loaderRoot'>
<CircularProgress className='QuickSearch-loader' />
<Typography>{gettext('Searching...')}</Typography>
</div>
);
@ -79,10 +77,10 @@ SearchLoader.propTypes = {
};
function HelpArticleContents({isHelpLoading, isMenuLoading, helpSearchResult}) {
const classes = useStyles();
return (isHelpLoading && !(isMenuLoading??true)) ? (
<div>
<div className={classes.helpGroup}>
<>
<div className='QuickSearch-helpGroup'>
<span className='fa fa-question-circle'></span>
&nbsp;HELP ARTICLES&nbsp;
{Object.keys(helpSearchResult.data).length > 10
@ -95,7 +93,7 @@ function HelpArticleContents({isHelpLoading, isMenuLoading, helpSearchResult}) {
}
</div>
<SearchLoader loading={true} />
</div>) : <></>;
</>) : <></>;
}
HelpArticleContents.propTypes = {
@ -105,7 +103,7 @@ HelpArticleContents.propTypes = {
};
export default function QuickSearch({closeModal}) {
const classes = useStyles();
const wrapperRef = useRef(null);
const [searchTerm, setSearchTerm] = useState('');
const [isShowMinLengthMsg, setIsShowMinLengthMsg] = useState(false);
@ -192,7 +190,7 @@ export default function QuickSearch({closeModal}) {
let menuItemsHtmlElement = [];
items.forEach((i) => {
menuItemsHtmlElement.push(
<div key={ 'li-menu-' + i.label }><a tabIndex={i.isDisabled ? '-1' : '0'} id={ 'li-menu-' + i.label } href={'#'} className={ (i.isDisabled ? clsx(classes.searchItem, 'disabled'):classes.searchItem)} onClick={
<div key={ 'li-menu-' + i.label }><a tabIndex={i.isDisabled ? '-1' : '0'} id={ 'li-menu-' + i.label } href={'#'} className={ (i.isDisabled ? 'QuickSearch-searchItem disabled':'QuickSearch-searchItem')} onClick={
() => {
closeModal();
i.callback();
@ -261,7 +259,7 @@ export default function QuickSearch({closeModal}) {
return (
<div id='quick-search-container' onClick={setSearchTerm} onKeyDown={()=>{/* no need */}}></div>,
<div id='quick-search-container' ref={wrapperRef} role="menu">
<div>
<StyledDiv>
<div>
<div style={{padding: '2px 2px 2px 2px'}}>
<InputText value={searchTerm} autoComplete='off' autoFocus
@ -276,7 +274,7 @@ export default function QuickSearch({closeModal}) {
<div >
{ (menuSearchResult.fetched && !(isMenuLoading??true) ) ?
<div>
<div className={classes.helpGroup}>
<div className='QuickSearch-helpGroup'>
<span className='fa fa-bars'></span> &nbsp;{gettext('MENU ITEMS')} ({menuSearchResult.data.length})
</div>
@ -293,16 +291,16 @@ export default function QuickSearch({closeModal}) {
{ (helpSearchResult.fetched && !(isHelpLoading??true)) ?
<div>
<div className={classes.helpGroup}>
<div className='QuickSearch-helpGroup'>
<span className='fa fa-question-circle'></span> &nbsp;{gettext('HELP ARTICLES')} {Object.keys(helpSearchResult.data).length > 10 ?
<span> (10 of {Object.keys(helpSearchResult.data).length})</span>:
'(' + Object.keys(helpSearchResult.data).length + ')'}&nbsp;
{ !helpSearchResult.clearedPooling ? <CircularProgress style={{height: '18px', width: '18px'}} /> :''}
{ Object.keys(helpSearchResult.data).length > 10 ? <a href={helpSearchResult.url} className={classes.showAll} target='_blank' rel='noreferrer'>{gettext('Show all')} &nbsp;<span className='fas fa-external-link-alt' ></span></a> : ''}
{ Object.keys(helpSearchResult.data).length > 10 ? <a href={helpSearchResult.url} className='QuickSearch-showAll' target='_blank' rel='noreferrer'>{gettext('Show all')} &nbsp;<span className='fas fa-external-link-alt' ></span></a> : ''}
</div>
{Object.keys(helpSearchResult.data).map( (value, index) => {
if(index <= 9) { return <div key={ 'li-help-' + value }><a tabIndex='0' href={helpSearchResult.data[value]} className={classes.searchItem} target='_blank' rel='noreferrer'>{value}</a></div>; }
if(index <= 9) { return <div key={ 'li-help-' + value }><a tabIndex='0' href={helpSearchResult.data[value]} className='QuickSearch-searchItem' target='_blank' rel='noreferrer'>{value}</a></div>; }
})}
{(Object.keys(helpSearchResult.data).length == 0) &&
@ -315,7 +313,7 @@ export default function QuickSearch({closeModal}) {
</div>
</div>
</div>
</div>
</StyledDiv>
<div id='quick-search-iframe-container' />
</div>
);

View File

@ -10,8 +10,8 @@
/* The DataGridView component is based on react-table component */
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { styled } from '@mui/material/styles';
import { Box } from '@mui/material';
import { makeStyles } from '@mui/styles';
import { PgIconButton } from '../components/Buttons';
import AddIcon from '@mui/icons-material/AddOutlined';
import { MappedCellControl } from './MappedControl';
@ -44,64 +44,58 @@ import { usePgAdmin } from '../BrowserComponent';
import { requestAnimationAndFocus } from '../utils';
import { PgReactTable, PgReactTableBody, PgReactTableCell, PgReactTableHeader, PgReactTableRow, PgReactTableRowContent, PgReactTableRowExpandContent } from '../components/PgReactTableStyled';
const useStyles = makeStyles((theme)=>({
grid: {
const StyledBox = styled(Box)(({theme}) => ({
'& .DataGridView-grid': {
...theme.mixins.panelBorder,
backgroundColor: theme.palette.background.default,
},
gridHeader: {
display: 'flex',
...theme.mixins.panelBorder.bottom,
backgroundColor: theme.otherVars.headerBg,
},
gridHeaderText: {
padding: theme.spacing(0.5, 1),
fontWeight: theme.typography.fontWeightBold,
},
gridControls: {
marginLeft: 'auto',
},
gridControlsButton: {
border: 0,
borderRadius: 0,
...theme.mixins.panelBorder.left,
},
gridRowButton: {
border: 0,
borderRadius: 0,
padding: 0,
minWidth: 0,
backgroundColor: 'inherit',
'&.Mui-disabled': {
border: 0,
'& .DataGridView-gridHeader': {
display: 'flex',
...theme.mixins.panelBorder.bottom,
backgroundColor: theme.otherVars.headerBg,
'& .DataGridView-gridHeaderText': {
padding: theme.spacing(0.5, 1),
fontWeight: theme.typography.fontWeightBold,
},
'& .DataGridView-gridControls': {
marginLeft: 'auto',
'& .DataGridView-gridControlsButton': {
border: 0,
borderRadius: 0,
...theme.mixins.panelBorder.left,
},
},
},
},
gridTableContainer: {
overflow: 'auto',
width: '100%',
},
table: {
'&.pgrt-table': {
'& .pgrt-body':{
'& .pgrt-row': {
position: 'unset',
backgroundColor: theme.otherVars.emptySpaceBg,
'& .pgrt-row-content':{
'& .pgrd-row-cell': {
height: 'auto',
padding: theme.spacing(0.5),
'&.btn-cell, &.expanded-icon-cell': {
padding: '2px 0px'
'& .DataGridView-table': {
'&.pgrt-table': {
'& .pgrt-body':{
'& .pgrt-row': {
position: 'unset',
backgroundColor: theme.otherVars.emptySpaceBg,
'& .pgrt-row-content':{
'& .pgrd-row-cell': {
height: 'auto',
padding: theme.spacing(0.5),
'&.btn-cell, &.expanded-icon-cell': {
padding: '2px 0px'
},
'& .DataGridView-gridRowButton': {
border: 0,
borderRadius: 0,
padding: 0,
minWidth: 0,
backgroundColor: 'inherit',
'&.Mui-disabled': {
border: 0,
},
},
}
}
},
},
}
}
}
}
},
},
tableRowHovered: {
'& .DataGridView-tableRowHovered': {
position: 'relative',
'& .hover-overlay': {
backgroundColor: theme.palette.primary.light,
@ -110,23 +104,11 @@ const useStyles = makeStyles((theme)=>({
opacity: 0.75,
}
},
tableCellHeader: {
fontWeight: theme.typography.fontWeightBold,
padding: theme.spacing(1, 0.5),
textAlign: 'left',
},
tableContentWidth: {
width: 'calc(100% - 3px)',
},
btnCell: {
padding: theme.spacing(0.5, 0),
textAlign: 'center',
},
btnReorder: {
'& .DataGridView-btnReorder': {
cursor: 'move',
padding: '4px 2px',
},
resizer: {
'& .DataGridView-resizer': {
display: 'inline-block',
width: '5px',
height: '100%',
@ -137,17 +119,17 @@ const useStyles = makeStyles((theme)=>({
zIndex: 1,
touchAction: 'none',
},
expandedForm: {
'& .DataGridView-expandedForm': {
border: '1px solid '+theme.palette.grey[400],
},
expandedIconCell: {
'& .DataGridView-expandedIconCell': {
backgroundColor: theme.palette.grey[400],
borderBottom: 'none',
}
}));
function DataTableRow({index, row, totalRows, isResizing, isHovered, schema, schemaRef, accessPath, moveRow, setHoverIndex, viewHelperProps}) {
const classes = useStyles();
const [key, setKey] = useState(false);
const depListener = useContext(DepListenerContext);
const rowRef = useRef(null);
@ -259,7 +241,7 @@ function DataTableRow({index, row, totalRows, isResizing, isHovered, schema, sch
drop(rowRef);
return useMemo(()=>
<PgReactTableRowContent ref={rowRef} row={row} data-handler-id={handlerId} className={isHovered ? classes.tableRowHovered : null} data-test='data-table-row' style={{position: 'initial'}}>
<PgReactTableRowContent ref={rowRef} row={row} data-handler-id={handlerId} className={isHovered ? 'DataGridView-tableRowHovered' : null} data-test='data-table-row' style={{position: 'initial'}}>
{row.getVisibleCells().map((cell) => {
let {modeSupported} = cell.column.field ? getFieldMetaData(cell.column.field, schemaRef.current, {}, viewHelperProps) : {modeSupported: true};
@ -280,16 +262,14 @@ function DataTableRow({index, row, totalRows, isResizing, isHovered, schema, sch
}
export function DataGridHeader({label, canAdd, onAddClick, canSearch, onSearchTextChange}) {
const classes = useStyles();
const [searchText, setSearchText] = useState('');
return (
<Box className={classes.gridHeader}>
<Box className='DataGridView-gridHeader'>
{ label &&
<Box className={classes.gridHeaderText}>{label}</Box>
<Box className='DataGridView-gridHeaderText'>{label}</Box>
}
{ canSearch &&
<Box className={classes.gridHeaderText} width={'100%'}>
<Box className='DataGridView-gridHeaderText' width={'100%'}>
<InputText value={searchText}
onChange={(value)=>{
onSearchTextChange(value);
@ -299,12 +279,12 @@ export function DataGridHeader({label, canAdd, onAddClick, canSearch, onSearchTe
</InputText>
</Box>
}
<Box className={classes.gridControls}>
<Box className='DataGridView-gridControls'>
{canAdd && <PgIconButton data-test="add-row" title={gettext('Add row')} onClick={()=>{
setSearchText('');
onSearchTextChange('');
onAddClick();
}} icon={<AddIcon />} className={classes.gridControlsButton} />}
}} icon={<AddIcon />} className='DataGridView-gridControlsButton' />}
</Box>
</Box>
);
@ -320,7 +300,7 @@ DataGridHeader.propTypes = {
export default function DataGridView({
value, viewHelperProps, schema, accessPath, dataDispatch, containerClassName,
fixedRows, ...props}) {
const classes = useStyles();
const stateUtils = useContext(StateUtilsContext);
const checkIsMounted = useIsMounted();
const [hoverIndex, setHoverIndex] = useState();
@ -345,7 +325,7 @@ export default function DataGridView({
maxSize: 26,
minSize: 26,
cell: ()=>{
return <div className={classes.btnReorder}>
return <div className='DataGridView-btnReorder'>
<DragIndicatorRoundedIcon fontSize="small" />
</div>;
}
@ -369,7 +349,7 @@ export default function DataGridView({
if(props.canEditRow) {
canEditRow = evalFunc(schemaRef.current, props.canEditRow, row || {});
}
return <PgIconButton data-test="expand-row" title={gettext('Edit row')} icon={<EditRoundedIcon fontSize="small" />} className={classes.gridRowButton}
return <PgIconButton data-test="expand-row" title={gettext('Edit row')} icon={<EditRoundedIcon fontSize="small" />} className='DataGridView-gridRowButton'
onClick={()=>{
row.toggleExpanded();
}} disabled={!canEditRow}
@ -423,7 +403,7 @@ export default function DataGridView({
}
);
}
}} className={classes.gridRowButton} disabled={!canDeleteRow} />
}} className='DataGridView-gridRowButton' disabled={!canDeleteRow} />
);
}
};
@ -607,8 +587,8 @@ export default function DataGridView({
}
return (
<Box className={containerClassName}>
<Box className={classes.grid}>
<StyledBox className={containerClassName}>
<Box className='DataGridView-grid'>
{(props.label || props.canAdd) && <DataGridHeader label={props.label} canAdd={props.canAdd} onAddClick={onAddClick}
canSearch={props.canSearch}
onSearchTextChange={(value)=>{
@ -616,7 +596,7 @@ export default function DataGridView({
}}
/>}
<DndProvider backend={HTML5Backend}>
<PgReactTable ref={tableRef} table={table} data-test="data-grid-view" tableClassName={classes.table}>
<PgReactTable ref={tableRef} table={table} data-test="data-grid-view" tableClassName='DataGridView-table'>
<PgReactTableHeader table={table} />
<PgReactTableBody>
{rows.map((row, i) => {
@ -628,7 +608,7 @@ export default function DataGridView({
{props.canEdit &&
<PgReactTableRowExpandContent row={row}>
<FormView value={row.original} viewHelperProps={viewHelperProps} dataDispatch={dataDispatch}
schema={schemaRef.current} accessPath={accessPath.concat([row.index])} isNested={true} className={classes.expandedForm}
schema={schemaRef.current} accessPath={accessPath.concat([row.index])} isNested={true} className='DataGridView-expandedForm'
isDataGridForm={true} firstEleRef={(ele)=>{
requestAnimationAndFocus(ele);
}}/>
@ -640,7 +620,7 @@ export default function DataGridView({
</PgReactTable>
</DndProvider>
</Box>
</Box>
</StyledBox>
);
}

View File

@ -8,11 +8,10 @@
//////////////////////////////////////////////////////////////
import React, { useContext, useEffect, useRef, useState } from 'react';
import { styled } from '@mui/material/styles';
import { Box, Tab, Tabs, Grid } from '@mui/material';
import { makeStyles } from '@mui/styles';
import _ from 'lodash';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import { MappedFormControl } from './MappedControl';
import TabPanel from '../components/TabPanel';
@ -26,44 +25,45 @@ import { useOnScreen } from '../custom_hooks';
import { DepListenerContext } from './DepListener';
import FieldSetView from './FieldSetView';
const useStyles = makeStyles((theme)=>({
fullSpace: {
padding: 0,
height: '100%',
overflow: 'hidden',
const StyledBox = styled(Box)(({theme}) => ({
'& .FormView-nestedControl': {
height: 'unset !important',
'& .FormView-controlRow': {
marginBottom: theme.spacing(1),
},
'& .FormView-nestedTabPanel': {
backgroundColor: theme.otherVars.headerBg,
}
},
controlRow: {
marginBottom: theme.spacing(1),
},
nestedTabPanel: {
backgroundColor: theme.otherVars.headerBg,
},
nestedControl: {
height: 'unset',
},
fullControl: {
display: 'flex',
flexDirection: 'column'
},
errorMargin: {
'& .FormView-errorMargin': {
/* Error footer space */
paddingBottom: '36px !important',
},
sqlTabInput: {
border: 0,
'& .FormView-fullSpace': {
padding: '0 !important',
height: '100%',
overflow: 'hidden',
'& .FormView-fullControl': {
display: 'flex',
flexDirection: 'column',
'& .FormView-sqlTabInput': {
border: 0,
},
}
},
nonTabPanel: {
padding: 0,
background: 'inherit',
'& .FormView-nonTabPanel': {
backgroundColor: 'inherit',
'& .FormView-nonTabPanelContent': {
height: 'unset',
'& .FormView-controlRow': {
marginBottom: theme.spacing(1),
},
}
},
nonTabPanelContent: {
height: 'unset'
}
}));
/* Optional SQL tab */
function SQLTab({active, getSQLValue}) {
const classes = useStyles();
const [sql, setSql] = useState('Loading...');
useEffect(()=>{
let unmounted = false;
@ -84,7 +84,7 @@ function SQLTab({active, getSQLValue}) {
readOnly: true,
}}
readonly={true}
className={classes.sqlTabInput}
className='FormView-sqlTabInput'
/>;
}
@ -167,7 +167,7 @@ export default function FormView({
let tabs = {};
let tabsClassname = {};
const [tabValue, setTabValue] = useState(0);
const classes = useStyles();
const firstEleID = useRef();
const formRef = useRef();
const onScreenTracker = useRef(false);
@ -272,7 +272,7 @@ export default function FormView({
tabs[group].push(
<FieldSetView key={`nested${tabs[group].length}`} value={value} viewHelperProps={viewHelperProps}
schema={field.schema} accessPath={accessPath} dataDispatch={dataDispatch} isNested={true} isDataGridForm={isDataGridForm}
controlClassName={classes.controlRow}
controlClassName='FormView-controlRow'
{...field} visible={visible}/>
);
} else if(field.type === 'collection') {
@ -293,7 +293,7 @@ export default function FormView({
key: field.id, ...field,
value: value[field.id] || [], viewHelperProps: viewHelperProps,
schema: field.schema, accessPath: accessPath.concat(field.id), dataDispatch: dataDispatch,
containerClassName: classes.controlRow,
containerClassName: 'FormView-controlRow',
canAdd: canAdd, canReorder: canReorder,
canEdit: canEdit, canDelete: canDelete,
visible: visible, canAddRow: canAddRow, onDelete: field.onDelete, canSearch: field.canSearch,
@ -315,7 +315,7 @@ export default function FormView({
* from there as well.
*/
if(field.isFullTab) {
tabsClassname[group] = classes.fullSpace;
tabsClassname[group] ='FormView-fullSpace';
fullTabs.push(group);
}
@ -353,7 +353,7 @@ export default function FormView({
});
}}
hasError={hasError}
className={classes.controlRow}
className='FormView-controlRow'
noLabel={field.isFullTab}
memoDeps={[
value[id],
@ -361,7 +361,7 @@ export default function FormView({
disabled,
visible,
hasError,
classes.controlRow,
'FormView-controlRow',
...(evalFunc(null, field.deps) || []).map((dep)=>value[dep]),
]}
/>;
@ -383,7 +383,7 @@ export default function FormView({
withContainer: false, controlGridBasis: 3
}));
tabs[group].push(
<Grid container spacing={0} key={`ic-${inlineComponents[0].key}`} className={classes.controlRow} rowGap="8px">
<Grid container spacing={0} key={`ic-${inlineComponents[0].key}`} className='FormView-controlRow' rowGap="8px">
{inlineComponents}
</Grid>
);
@ -398,7 +398,7 @@ export default function FormView({
if(inlineComponents?.length > 0) {
tabs[inlineCompGroup].push(
<Grid container spacing={0} key={`ic-${inlineComponents[0].key}`} className={classes.controlRow} rowGap="8px">
<Grid container spacing={0} key={`ic-${inlineComponents[0].key}`} className='FormView-controlRow' rowGap="8px">
{inlineComponents}
</Grid>
);
@ -415,7 +415,7 @@ export default function FormView({
finalTabs[sqlTabName] = [
<SQLTab key="sqltab" active={sqlTabActive} getSQLValue={getSQLValue} />,
];
tabsClassname[sqlTabName] = classes.fullSpace;
tabsClassname[sqlTabName] = 'FormView-fullSpace';
fullTabs.push(sqlTabName);
}
@ -430,7 +430,7 @@ export default function FormView({
if(isTabView) {
return (
<Box height="100%" display="flex" flexDirection="column" className={className} ref={formRef} data-test="form-view">
<StyledBox height="100%" display="flex" flexDirection="column" className={className} ref={formRef} data-test="form-view">
<Box>
<Tabs
value={tabValue}
@ -447,33 +447,34 @@ export default function FormView({
</Tabs>
</Box>
{Object.keys(finalTabs).map((tabName, i)=>{
let contentClassName = [stateUtils.formErr.message ? classes.errorMargin : null];
let contentClassName = [(stateUtils.formErr.message ? 'FormView-errorMargin': null)];
if(fullTabs.indexOf(tabName) == -1) {
contentClassName.push(classes.nestedControl);
contentClassName.push('FormView-nestedControl');
} else {
contentClassName.push(classes.fullControl);
contentClassName.push('FormView-fullControl');
}
return (
<TabPanel key={tabName} value={tabValue} index={i} classNameRoot={clsx(tabsClassname[tabName], isNested ? classes.nestedTabPanel : null)}
className={clsx(contentClassName)} data-testid={tabName}>
<TabPanel key={tabName} value={tabValue} index={i} classNameRoot={[tabsClassname[tabName], (isNested ? 'FormView-nestedTabPanel' : null)].join(' ')}
className={contentClassName.join(' ')} data-testid={tabName}>
{finalTabs[tabName]}
</TabPanel>
);
})}
</Box>);
</StyledBox>
);
} else {
let contentClassName = [classes.nonTabPanelContent, stateUtils.formErr.message ? classes.errorMargin : null];
let contentClassName = ['FormView-nonTabPanelContent', (stateUtils.formErr.message ? 'FormView-errorMargin' : null)];
return (
<Box height="100%" display="flex" flexDirection="column" className={clsx(className)} ref={formRef} data-test="form-view">
<TabPanel value={tabValue} index={0} classNameRoot={classes.nonTabPanel}
className={clsx(contentClassName)}>
<StyledBox height="100%" display="flex" flexDirection="column" className={className} ref={formRef} data-test="form-view">
<TabPanel value={tabValue} index={0} classNameRoot='FormView-nonTabPanel'
className={contentClassName.join(' ')}>
{Object.keys(finalTabs).map((tabName)=>{
return (
<React.Fragment key={tabName}>{finalTabs[tabName]}</React.Fragment>
);
})}
</TabPanel>
</Box>);
</StyledBox>);
}
}

View File

@ -9,7 +9,6 @@
import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react';
import { Box, Accordion, AccordionSummary, AccordionDetails} from '@mui/material';
import { makeStyles } from '@mui/styles';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import SaveIcon from '@mui/icons-material/Save';
import PublishIcon from '@mui/icons-material/Publish';
@ -21,7 +20,6 @@ import HelpIcon from '@mui/icons-material/HelpRounded';
import EditIcon from '@mui/icons-material/Edit';
import diffArray from 'diff-arrays-of-objects';
import _ from 'lodash';
import clsx from 'clsx';
import {FormFooterMessage, MESSAGE_TYPE } from 'sources/components/FormComponents';
import { PrimaryButton, DefaultButton, PgIconButton } from 'sources/components/Buttons';
@ -41,35 +39,45 @@ import { useIsMounted } from '../custom_hooks';
import ErrorBoundary from '../helpers/ErrorBoundary';
import { usePgAdmin } from '../BrowserComponent';
import { PgButtonGroup } from '../components/Buttons';
import { styled } from '@mui/material/styles';
const useDialogStyles = makeStyles((theme)=>({
root: {
display: 'flex',
flexDirection: 'column',
height: '100%',
},
form: {
const StyledBox = styled(Box)(({theme})=>({
display: 'flex',
flexDirection: 'column',
height: '100%',
minHeight: 0,
'& .Dialog-form': {
flexGrow: 1,
position: 'relative',
minHeight: 0,
display: 'flex',
flexDirection: 'column',
},
formProperties: {
backgroundColor: theme.palette.grey[400],
},
footer: {
'& .Dialog-footer': {
padding: theme.spacing(1),
background: theme.otherVars.headerBg,
display: 'flex',
zIndex: 1010,
...theme.mixins.panelBorder.top,
'& .Dialog-buttonMargin': {
marginRight: '0.5rem',
},
},
mappedControl: {
paddingBottom: theme.spacing(1),
'& .Properties-toolbar': {
padding: theme.spacing(1),
background: theme.palette.background.default,
...theme.mixins.panelBorder.bottom,
},
buttonMargin: {
marginRight: '0.5rem',
'& .Properties-form': {
padding: theme.spacing(1),
overflow: 'auto',
flexGrow: 1,
'& .Properties-controlRow': {
marginBottom: theme.spacing(1),
},
},
'& .Properties-noPadding': {
padding: 0,
},
}));
@ -476,7 +484,6 @@ function prepareData(val, createMode=false) {
/* If its the dialog */
function SchemaDialogView({
getInitData, viewHelperProps, loadingText, schema={}, showFooter=true, isTabView=true, checkDirtyOnEnableSave=false, ...props}) {
const classes = useDialogStyles();
/* Some useful states */
const [dirty, setDirty] = useState(false);
/* formErr has 2 keys - name and message.
@ -784,10 +791,10 @@ function SchemaDialogView({
/* I am Groot */
return (
<StateUtilsContext.Provider value={stateUtils}>
<DepListenerContext.Provider value={depListenerObj.current}>
<Box className={classes.root}>
<Box className={classes.form}>
<StyledBox>
<StateUtilsContext.Provider value={stateUtils}>
<DepListenerContext.Provider value={depListenerObj.current}>
<Box className='Dialog-form'>
<Loader message={loaderText || loadingText}/>
<FormView value={sessData} viewHelperProps={viewHelperProps}
schema={schema} accessPath={[]} dataDispatch={sessDispatchWithListener}
@ -795,18 +802,18 @@ function SchemaDialogView({
<FormFooterMessage type={MESSAGE_TYPE.ERROR} message={formErr.message}
onClose={onErrClose} />
</Box>
{showFooter && <Box className={classes.footer}>
{showFooter && <Box className='Dialog-footer'>
{(!props.disableSqlHelp || !props.disableDialogHelp) && <Box>
<PgIconButton data-test="sql-help" onClick={()=>props.onHelp(true, isNew)} icon={<InfoIcon />}
disabled={props.disableSqlHelp} className={classes.buttonMargin} title="SQL help for this object type."/>
disabled={props.disableSqlHelp} className='Dialog-buttonMargin' title="SQL help for this object type."/>
<PgIconButton data-test="dialog-help" onClick={()=>props.onHelp(false, isNew)} icon={<HelpIcon />} title="Help for this dialog."
disabled={props.disableDialogHelp}/>
</Box>}
<Box marginLeft="auto">
<DefaultButton data-test="Close" onClick={props.onClose} startIcon={<CloseIcon />} className={classes.buttonMargin}>
<DefaultButton data-test="Close" onClick={props.onClose} startIcon={<CloseIcon />} className='Dialog-buttonMargin'>
{gettext('Close')}
</DefaultButton>
<DefaultButton data-test="Reset" onClick={onResetClick} startIcon={<SettingsBackupRestoreIcon />} disabled={!dirty || saving} className={classes.buttonMargin}>
<DefaultButton data-test="Reset" onClick={onResetClick} startIcon={<SettingsBackupRestoreIcon />} disabled={!dirty || saving} className='Dialog-buttonMargin'>
{gettext('Reset')}
</DefaultButton>
<PrimaryButton data-test="Save" onClick={onSaveClick} startIcon={ButtonIcon} disabled={ !(viewHelperProps.mode === 'edit' || checkDirtyOnEnableSave ? dirty : true) || saving || Boolean(formErr.name && formErr.name !== 'apierror') || !formReady}>
@ -814,9 +821,9 @@ function SchemaDialogView({
</PrimaryButton>
</Box>
</Box>}
</Box>
</DepListenerContext.Provider>
</StateUtilsContext.Provider>
</DepListenerContext.Provider>
</StateUtilsContext.Provider>
</StyledBox>
);
}
@ -851,38 +858,12 @@ SchemaDialogView.propTypes = {
checkDirtyOnEnableSave: PropTypes.bool,
};
const usePropsStyles = makeStyles((theme)=>({
root: {
height: '100%',
minHeight: 0,
display: 'flex',
flexDirection: 'column'
},
controlRow: {
marginBottom: theme.spacing(1),
},
form: {
padding: theme.spacing(1),
overflow: 'auto',
flexGrow: 1,
},
toolbar: {
padding: theme.spacing(1),
background: theme.palette.background.default,
...theme.mixins.panelBorder.bottom,
},
buttonMargin: {
marginRight: '0.5rem',
},
noPadding: {
padding: 0,
}
}));
/* If its the properties tab */
function SchemaPropertiesView({
getInitData, viewHelperProps, schema={}, updatedData, ...props}) {
const classes = usePropsStyles();
let defaultTab = 'General';
let tabs = {};
let tabsClassname = {};
@ -926,7 +907,7 @@ function SchemaPropertiesView({
group = group || defaultTab;
if(field.isFullTab) {
tabsClassname[group] = classes.noPadding;
tabsClassname[group] = 'Properties-noPadding';
}
if(modeSupported) {
@ -944,7 +925,7 @@ function SchemaPropertiesView({
viewHelperProps={viewHelperProps}
schema={field.schema}
accessPath={[]}
controlClassName={classes.controlRow}
controlClassName='Properties-controlRow'
{...field}
visible={visible}
/>
@ -959,7 +940,7 @@ function SchemaPropertiesView({
schema={field.schema}
accessPath={[field.id]}
formErr={{}}
containerClassName={classes.controlRow}
containerClassName='Properties-controlRow'
canAdd={false}
canEdit={false}
canDelete={false}
@ -983,11 +964,11 @@ function SchemaPropertiesView({
readonly={readonly}
disabled={disabled}
visible={visible}
className={field.isFullTab ? null : classes.controlRow}
className={field.isFullTab ? null :'Properties-controlRow'}
noLabel={field.isFullTab}
memoDeps={[
origData[field.id],
classes.controlRow,
'Properties-controlRow',
field.isFullTab
]}
/>
@ -998,9 +979,9 @@ function SchemaPropertiesView({
let finalTabs = _.pickBy(tabs, (v, tabName)=>schema.filterGroups.indexOf(tabName) <= -1);
return (
<Box className={classes.root}>
<StyledBox>
<Loader message={loaderText}/>
<Box className={classes.toolbar}>
<Box className='Properties-toolbar'>
<PgButtonGroup size="small">
<PgIconButton
data-test="help" onClick={()=>props.onHelp(true, false)} icon={<InfoIcon />} disabled={props.disableSqlHelp}
@ -1009,7 +990,7 @@ function SchemaPropertiesView({
onClick={props.onEdit} icon={<EditIcon />} title={gettext('Edit object...')} />
</PgButtonGroup>
</Box>
<Box className={clsx(classes.form, classes.formProperties)}>
<Box className={'Properties-form'}>
<Box>
{Object.keys(finalTabs).map((tabName)=>{
let id = tabName.replace(' ', '');
@ -1032,7 +1013,7 @@ function SchemaPropertiesView({
})}
</Box>
</Box>
</Box>
</StyledBox>
);
}

View File

@ -1,5 +1,5 @@
import { Box, Button, darken } from '@mui/material';
import { makeStyles } from '@mui/styles';
import { styled } from '@mui/material/styles';
import { useSnackbar } from 'notistack';
import React, { useEffect } from 'react';
import { MESSAGE_TYPE, NotifierMessage } from '../components/FormComponents';
@ -7,18 +7,13 @@ import { FinalNotifyContent } from '../helpers/Notifier';
import PropTypes from 'prop-types';
import CustomPropTypes from '../custom_prop_types';
const contentBg = '#2b709b';
const loginBtnBg = '#038bba';
const useStyles = makeStyles((theme)=>({
root: {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
display: 'flex',
justifyContent: 'center',
height: '100%',
},
pageContent: {
const StyledBox = styled(Box)(({theme}) => ({
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
display: 'flex',
justifyContent: 'center',
height: '100%',
'& .BasePage-pageContent': {
display: 'flex',
flexDirection: 'column',
padding: '16px',
@ -26,44 +21,45 @@ const useStyles = makeStyles((theme)=>({
borderRadius: theme.shape.borderRadius,
maxHeight: '100%',
minWidth: '450px',
maxWidth: '450px'
},
logo: {
width: '96px',
height: '40px',
background: 'url(data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMDUgNTAiPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDojZmZmO30uY2xzLTJ7ZmlsbDojMzI2ODkzO308L3N0eWxlPjwvZGVmcz48dGl0bGU+cGdBZG1pbjwvdGl0bGU+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNNTguOTQsNDEuNGEyLjQ4LDIuNDgsMCwwLDEtMi4yNy0zLjQ5TDY0LDIxLjI5VjZhNiw2LDAsMCwwLTYtNkg2QTYsNiwwLDAsMCwwLDZWNDRhNiw2LDAsMCwwLDYsNkg1OGE2LDYsMCwwLDAsNi02VjQxLjRaIi8+PHBhdGggY2xhc3M9ImNscy0yIiBkPSJNMjkuMjUsMzAuMTdhMTMuMTMsMTMuMTMsMCwwLDEtMS44Mi02LjkzLDEzLDEzLDAsMCwxLDEuODItNi44OCwxMi41LDEyLjUsMCwwLDEsMS40OC0xLjk1LDEwLjQ0LDEwLjQ0LDAsMCwwLTMuMjUtMi44OSwxMS4xNiwxMS4xNiwwLDAsMC01LjY1LTEuNDVxLTQuNDgsMC02LjcyLDIuNjRWMTAuNDRINy41MVY0MC4zNmExLDEsMCwwLDAsMSwxaDZhMSwxLDAsMCwwLDEtMVYzMS4xOWE4LjQ3LDguNDcsMCwwLDAsNi4zNCwyLjQsMTEuMjYsMTEuMjYsMCwwLDAsNS42NS0xLjQ1LDEwLjUzLDEwLjUzLDAsMCwwLDIuMDYtMS41NkMyOS40NCwzMC40NCwyOS4zNCwzMC4zMSwyOS4yNSwzMC4xN1pNMjMuNiwyNS44YTQuNTIsNC41MiwwLDAsMS0zLjQ1LDEuNDQsNC40OCw0LjQ4LDAsMCwxLTMuNDQtMS40NCw1LjYsNS42LDAsMCwxLTEuMzUtNCw1LjU5LDUuNTksMCwwLDEsMS4zNS00LDQuNDYsNC40NiwwLDAsMSwzLjQ0LTEuNDUsNC40OSw0LjQ5LDAsMCwxLDMuNDUsMS40NSw1LjYzLDUuNjMsMCwwLDEsMS4zNCw0QTUuNjQsNS42NCwwLDAsMSwyMy42LDI1LjhaIi8+PHBhdGggY2xhc3M9ImNscy0yIiBkPSJNNTYuNDksMTIuNjNWMzEuMjRxMCw2LjM1LTMuNDQsOS41MXQtOS45MiwzLjE3YTI1LjQyLDI1LjQyLDAsMCwxLTYuMy0uNzUsMTUsMTUsMCwwLDEtNS0yLjIzbDIuODktNS41OWExMC4xNywxMC4xNywwLDAsMCwzLjUxLDEuNzksMTQuMzcsMTQuMzcsMCwwLDAsNC4xOC42NUE2LjUzLDYuNTMsMCwwLDAsNDcsMzYuNGE1LjM3LDUuMzcsMCwwLDAsMS40Ny00LjExdi0uNzZjLTEuNTQsMS44LTMuNzksMi42OS02Ljc2LDIuNjlhMTEuNywxMS43LDAsMCwxLTUuNTktMS4zNkExMC4zNywxMC4zNywwLDAsMSwzMi4wOSwyOWExMC44OSwxMC44OSwwLDAsMS0xLjUxLTUuNzcsMTAuODYsMTAuODYsMCwwLDEsMS41MS01Ljc0LDEwLjQyLDEwLjQyLDAsMCwxLDQuMDctMy44NiwxMS43MSwxMS43MSwwLDAsMSw1LjU5LTEuMzdjMy4yNSwwLDUuNjMsMS4wNiw3LjE0LDMuMTVWMTIuNjNabS05LjMsMTMuOTVhNC40LDQuNCwwLDAsMCwxLjQtMy4zNiw0LjM0LDQuMzQsMCwwLDAtMS4zOC0zLjM0LDUuNjUsNS42NSwwLDAsMC03LjE2LDAsNC4zLDQuMywwLDAsMC0xLjQxLDMuMzQsNC4zNSw0LjM1LDAsMCwwLDEuNDMsMy4zNiw1LjA4LDUuMDgsMCwwLDAsMy41NywxLjNBNSw1LDAsMCwwLDQ3LjE5LDI2LjU4WiIvPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTgzLjQzLDMyLjg5SDcxbC0yLDUuMDlhMSwxLDAsMCwxLS45My42Mkg2MS43M2ExLDEsMCwwLDEtLjkxLTEuNEw3Mi45MSw5LjhhMSwxLDAsMCwxLC45Mi0uNmg2Ljg5YTEsMSwwLDAsMSwuOTEuNkw5My43NywzNy4yYTEsMSwwLDAsMS0uOTIsMS40SDg2LjQxYTEsMSwwLDAsMS0uOTMtLjYyWk04MSwyNi43NmwtMy43OC05LjQxLTMuNzgsOS40MVoiLz48cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik0xMjAuNDQsOC40NFYzNy42YTEsMSwwLDAsMS0xLDFoLTUuNmExLDEsMCwwLDEtMS0xVjM2LjMzUTExMC42MiwzOSwxMDYuMTYsMzlhMTEuMjksMTEuMjksMCwwLDEtNS42Ny0xLjQ1LDEwLjU0LDEwLjU0LDAsMCwxLTQtNC4xNEExMi42MiwxMi42MiwwLDAsMSw5NSwyNy4xOCwxMi41MywxMi41MywwLDAsMSw5Ni40NCwyMWExMC4zNSwxMC4zNSwwLDAsMSw0LTQuMDksMTEuNDgsMTEuNDgsMCwwLDEsNS42Ny0xLjQzLDguMjQsOC4yNCwwLDAsMSw2LjMsMi4zNVY4LjQ0YTEsMSwwLDAsMSwxLTFoNkExLDEsMCwwLDEsMTIwLjQ0LDguNDRabS05LjE5LDIyLjc1YTUuNzEsNS43MSwwLDAsMCwxLjM0LTQsNS42LDUuNiwwLDAsMC0xLjMyLTMuOTUsNC40Nyw0LjQ3LDAsMCwwLTMuNDMtMS40Myw0LjUzLDQuNTMsMCwwLDAtMy40NCwxLjQzLDUuNTEsNS41MSwwLDAsMC0xLjM0LDMuOTUsNS42Nyw1LjY3LDAsMCwwLDEuMzQsNCw0Ljc3LDQuNzcsMCwwLDAsNi44NSwwWiIvPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTE2MSwxOGMxLjY2LDEuNjgsMi41LDQuMjEsMi41LDcuNnYxMmExLDEsMCwwLDEtMSwxaC02YTEsMSwwLDAsMS0xLTFWMjYuODhhNS42Nyw1LjY3LDAsMCwwLS45LTMuNTMsMy4wOSwzLjA5LDAsMCwwLTIuNTUtMS4xMywzLjYyLDMuNjIsMCwwLDAtMi44OSwxLjI2LDUuNzEsNS43MSwwLDAsMC0xLjEsMy44MlYzNy42YTEsMSwwLDAsMS0xLDFoLTZhMSwxLDAsMCwxLTEtMVYyNi44OGMwLTMuMTEtMS4xNC00LjY2LTMuNDQtNC42NmEzLjcsMy43LDAsMCwwLTIuOTQsMS4yNiw1LjcxLDUuNzEsMCwwLDAtMS4wOSwzLjgyVjM3LjZhMSwxLDAsMCwxLTEsMWgtNmExLDEsMCwwLDEtMS0xVjE2Ljg0YTEsMSwwLDAsMSwxLTFoNS42YTEsMSwwLDAsMSwxLDF2MS4zOWE4LDgsMCwwLDEsMy0yLjA4LDEwLjIzLDEwLjIzLDAsMCwxLDMuOC0uNjksMTAsMTAsMCwwLDEsNC4yOS44OEE3LjI4LDcuMjgsMCwwLDEsMTQ2LjQyLDE5YTguODUsOC44NSwwLDAsMSwzLjQxLTIuNjUsMTAuOTMsMTAuOTMsMCwwLDEsNC40OS0uOTJBOSw5LDAsMCwxLDE2MSwxOFoiLz48cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik0xNjguMTIsMTIuMWEzLjkxLDMuOTEsMCwwLDEtMS4zNC0yLjc5QTQuMTYsNC4xNiwwLDAsMSwxNjgsNi4xOWE1LDUsMCwwLDEsMy42Ny0xLjM2QTUuMjUsNS4yNSwwLDAsMSwxNzUuMTgsNmEzLjc1LDMuNzUsMCwwLDEsMS4zNCwzLDQuMSw0LjEsMCwwLDEtMS4zNCwzLjEzLDUuNjgsNS42OCwwLDAsMS03LjA2LDBabS41NCwzLjc0aDZhMSwxLDAsMCwxLDEsMVYzNy42YTEsMSwwLDAsMS0xLDFoLTZhMSwxLDAsMCwxLTEtMVYxNi44NEExLDEsMCwwLDEsMTY4LjY2LDE1Ljg0WiIvPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTIwMS41NSwxOHEyLjU5LDIuNTIsMi41OSw3LjZ2MTJhMSwxLDAsMCwxLTEsMWgtNmExLDEsMCwwLDEtMS0xVjI2Ljg4cTAtNC42Ni0zLjc0LTQuNjZhNC4zLDQuMywwLDAsMC0zLjMsMS4zNCw1LjgzLDUuODMsMCwwLDAtMS4yNCw0djEwYTEsMSwwLDAsMS0xLDFoLTZhMSwxLDAsMCwxLTEtMVYxNi44NGExLDEsMCwwLDEsMS0xaDUuNjFhMSwxLDAsMCwxLDEsMXYxLjQ3YTkuMDUsOS4wNSwwLDAsMSwzLjE5LTIuMTIsMTAuNzgsMTAuNzgsMCwwLDEsNC0uNzNBOS4zNCw5LjM0LDAsMCwxLDIwMS41NSwxOFoiLz48L3N2Zz4=) 0 0 no-repeat',
backgroundPositionY: 'center',
},
item: {
display: 'flex',
justifyContent: 'center',
marginBottom: '15px',
fontSize: '1.2rem'
},
button: {
backgroundColor: loginBtnBg,
color: '#fff',
padding: '4px 8px',
width: '100%',
'&:hover': {
backgroundColor: darken(loginBtnBg, 0.1),
maxWidth: '450px',
'& .BasePage-item': {
display: 'flex',
justifyContent: 'center',
marginBottom: '15px',
fontSize: '1.2rem',
'& .BasePage-logo': {
width: '96px',
height: '40px',
background: 'url(data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMDUgNTAiPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDojZmZmO30uY2xzLTJ7ZmlsbDojMzI2ODkzO308L3N0eWxlPjwvZGVmcz48dGl0bGU+cGdBZG1pbjwvdGl0bGU+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNNTguOTQsNDEuNGEyLjQ4LDIuNDgsMCwwLDEtMi4yNy0zLjQ5TDY0LDIxLjI5VjZhNiw2LDAsMCwwLTYtNkg2QTYsNiwwLDAsMCwwLDZWNDRhNiw2LDAsMCwwLDYsNkg1OGE2LDYsMCwwLDAsNi02VjQxLjRaIi8+PHBhdGggY2xhc3M9ImNscy0yIiBkPSJNMjkuMjUsMzAuMTdhMTMuMTMsMTMuMTMsMCwwLDEtMS44Mi02LjkzLDEzLDEzLDAsMCwxLDEuODItNi44OCwxMi41LDEyLjUsMCwwLDEsMS40OC0xLjk1LDEwLjQ0LDEwLjQ0LDAsMCwwLTMuMjUtMi44OSwxMS4xNiwxMS4xNiwwLDAsMC01LjY1LTEuNDVxLTQuNDgsMC02LjcyLDIuNjRWMTAuNDRINy41MVY0MC4zNmExLDEsMCwwLDAsMSwxaDZhMSwxLDAsMCwwLDEtMVYzMS4xOWE4LjQ3LDguNDcsMCwwLDAsNi4zNCwyLjQsMTEuMjYsMTEuMjYsMCwwLDAsNS42NS0xLjQ1LDEwLjUzLDEwLjUzLDAsMCwwLDIuMDYtMS41NkMyOS40NCwzMC40NCwyOS4zNCwzMC4zMSwyOS4yNSwzMC4xN1pNMjMuNiwyNS44YTQuNTIsNC41MiwwLDAsMS0zLjQ1LDEuNDQsNC40OCw0LjQ4LDAsMCwxLTMuNDQtMS40NCw1LjYsNS42LDAsMCwxLTEuMzUtNCw1LjU5LDUuNTksMCwwLDEsMS4zNS00LDQuNDYsNC40NiwwLDAsMSwzLjQ0LTEuNDUsNC40OSw0LjQ5LDAsMCwxLDMuNDUsMS40NSw1LjYzLDUuNjMsMCwwLDEsMS4zNCw0QTUuNjQsNS42NCwwLDAsMSwyMy42LDI1LjhaIi8+PHBhdGggY2xhc3M9ImNscy0yIiBkPSJNNTYuNDksMTIuNjNWMzEuMjRxMCw2LjM1LTMuNDQsOS41MXQtOS45MiwzLjE3YTI1LjQyLDI1LjQyLDAsMCwxLTYuMy0uNzUsMTUsMTUsMCwwLDEtNS0yLjIzbDIuODktNS41OWExMC4xNywxMC4xNywwLDAsMCwzLjUxLDEuNzksMTQuMzcsMTQuMzcsMCwwLDAsNC4xOC42NUE2LjUzLDYuNTMsMCwwLDAsNDcsMzYuNGE1LjM3LDUuMzcsMCwwLDAsMS40Ny00LjExdi0uNzZjLTEuNTQsMS44LTMuNzksMi42OS02Ljc2LDIuNjlhMTEuNywxMS43LDAsMCwxLTUuNTktMS4zNkExMC4zNywxMC4zNywwLDAsMSwzMi4wOSwyOWExMC44OSwxMC44OSwwLDAsMS0xLjUxLTUuNzcsMTAuODYsMTAuODYsMCwwLDEsMS41MS01Ljc0LDEwLjQyLDEwLjQyLDAsMCwxLDQuMDctMy44NiwxMS43MSwxMS43MSwwLDAsMSw1LjU5LTEuMzdjMy4yNSwwLDUuNjMsMS4wNiw3LjE0LDMuMTVWMTIuNjNabS05LjMsMTMuOTVhNC40LDQuNCwwLDAsMCwxLjQtMy4zNiw0LjM0LDQuMzQsMCwwLDAtMS4zOC0zLjM0LDUuNjUsNS42NSwwLDAsMC03LjE2LDAsNC4zLDQuMywwLDAsMC0xLjQxLDMuMzQsNC4zNSw0LjM1LDAsMCwwLDEuNDMsMy4zNiw1LjA4LDUuMDgsMCwwLDAsMy41NywxLjNBNSw1LDAsMCwwLDQ3LjE5LDI2LjU4WiIvPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTgzLjQzLDMyLjg5SDcxbC0yLDUuMDlhMSwxLDAsMCwxLS45My42Mkg2MS43M2ExLDEsMCwwLDEtLjkxLTEuNEw3Mi45MSw5LjhhMSwxLDAsMCwxLC45Mi0uNmg2Ljg5YTEsMSwwLDAsMSwuOTEuNkw5My43NywzNy4yYTEsMSwwLDAsMS0uOTIsMS40SDg2LjQxYTEsMSwwLDAsMS0uOTMtLjYyWk04MSwyNi43NmwtMy43OC05LjQxLTMuNzgsOS40MVoiLz48cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik0xMjAuNDQsOC40NFYzNy42YTEsMSwwLDAsMS0xLDFoLTUuNmExLDEsMCwwLDEtMS0xVjM2LjMzUTExMC42MiwzOSwxMDYuMTYsMzlhMTEuMjksMTEuMjksMCwwLDEtNS42Ny0xLjQ1LDEwLjU0LDEwLjU0LDAsMCwxLTQtNC4xNEExMi42MiwxMi42MiwwLDAsMSw5NSwyNy4xOCwxMi41MywxMi41MywwLDAsMSw5Ni40NCwyMWExMC4zNSwxMC4zNSwwLDAsMSw0LTQuMDksMTEuNDgsMTEuNDgsMCwwLDEsNS42Ny0xLjQzLDguMjQsOC4yNCwwLDAsMSw2LjMsMi4zNVY4LjQ0YTEsMSwwLDAsMSwxLTFoNkExLDEsMCwwLDEsMTIwLjQ0LDguNDRabS05LjE5LDIyLjc1YTUuNzEsNS43MSwwLDAsMCwxLjM0LTQsNS42LDUuNiwwLDAsMC0xLjMyLTMuOTUsNC40Nyw0LjQ3LDAsMCwwLTMuNDMtMS40Myw0LjUzLDQuNTMsMCwwLDAtMy40NCwxLjQzLDUuNTEsNS41MSwwLDAsMC0xLjM0LDMuOTUsNS42Nyw1LjY3LDAsMCwwLDEuMzQsNCw0Ljc3LDQuNzcsMCwwLDAsNi44NSwwWiIvPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTE2MSwxOGMxLjY2LDEuNjgsMi41LDQuMjEsMi41LDcuNnYxMmExLDEsMCwwLDEtMSwxaC02YTEsMSwwLDAsMS0xLTFWMjYuODhhNS42Nyw1LjY3LDAsMCwwLS45LTMuNTMsMy4wOSwzLjA5LDAsMCwwLTIuNTUtMS4xMywzLjYyLDMuNjIsMCwwLDAtMi44OSwxLjI2LDUuNzEsNS43MSwwLDAsMC0xLjEsMy44MlYzNy42YTEsMSwwLDAsMS0xLDFoLTZhMSwxLDAsMCwxLTEtMVYyNi44OGMwLTMuMTEtMS4xNC00LjY2LTMuNDQtNC42NmEzLjcsMy43LDAsMCwwLTIuOTQsMS4yNiw1LjcxLDUuNzEsMCwwLDAtMS4wOSwzLjgyVjM3LjZhMSwxLDAsMCwxLTEsMWgtNmExLDEsMCwwLDEtMS0xVjE2Ljg0YTEsMSwwLDAsMSwxLTFoNS42YTEsMSwwLDAsMSwxLDF2MS4zOWE4LDgsMCwwLDEsMy0yLjA4LDEwLjIzLDEwLjIzLDAsMCwxLDMuOC0uNjksMTAsMTAsMCwwLDEsNC4yOS44OEE3LjI4LDcuMjgsMCwwLDEsMTQ2LjQyLDE5YTguODUsOC44NSwwLDAsMSwzLjQxLTIuNjUsMTAuOTMsMTAuOTMsMCwwLDEsNC40OS0uOTJBOSw5LDAsMCwxLDE2MSwxOFoiLz48cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik0xNjguMTIsMTIuMWEzLjkxLDMuOTEsMCwwLDEtMS4zNC0yLjc5QTQuMTYsNC4xNiwwLDAsMSwxNjgsNi4xOWE1LDUsMCwwLDEsMy42Ny0xLjM2QTUuMjUsNS4yNSwwLDAsMSwxNzUuMTgsNmEzLjc1LDMuNzUsMCwwLDEsMS4zNCwzLDQuMSw0LjEsMCwwLDEtMS4zNCwzLjEzLDUuNjgsNS42OCwwLDAsMS03LjA2LDBabS41NCwzLjc0aDZhMSwxLDAsMCwxLDEsMVYzNy42YTEsMSwwLDAsMS0xLDFoLTZhMSwxLDAsMCwxLTEtMVYxNi44NEExLDEsMCwwLDEsMTY4LjY2LDE1Ljg0WiIvPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTIwMS41NSwxOHEyLjU5LDIuNTIsMi41OSw3LjZ2MTJhMSwxLDAsMCwxLTEsMWgtNmExLDEsMCwwLDEtMS0xVjI2Ljg4cTAtNC42Ni0zLjc0LTQuNjZhNC4zLDQuMywwLDAsMC0zLjMsMS4zNCw1LjgzLDUuODMsMCwwLDAtMS4yNCw0djEwYTEsMSwwLDAsMS0xLDFoLTZhMSwxLDAsMCwxLTEtMVYxNi44NGExLDEsMCwwLDEsMS0xaDUuNjFhMSwxLDAsMCwxLDEsMXYxLjQ3YTkuMDUsOS4wNSwwLDAsMSwzLjE5LTIuMTIsMTAuNzgsMTAuNzgsMCwwLDEsNC0uNzNBOS4zNCw5LjM0LDAsMCwxLDIwMS41NSwxOFoiLz48L3N2Zz4=) 0 0 no-repeat',
backgroundPositionY: 'center',
},
},
'&.Mui-disabled': {
opacity: 0.60,
color: '#fff'
},
}
'& .BasePage-button': {
backgroundColor: loginBtnBg,
color: '#fff',
padding: '4px 8px',
width: '100%',
'&:hover': {
backgroundColor: darken(loginBtnBg, 0.1),
},
'&.Mui-disabled': {
opacity: 0.60,
color: '#fff'
},
}
},
}));
const contentBg = '#2b709b';
const loginBtnBg = '#038bba';
export function SecurityButton({...props}) {
const classes = useStyles();
return <Button type="submit" className={classes.button} {...props} />;
return <Button type="submit" className='BasePage-button' {...props} />;
}
export default function BasePage({pageImage, title, children, messages}) {
const classes = useStyles();
const snackbar = useSnackbar();
useEffect(()=>{
messages?.forEach((m)=>{
snackbar.enqueueSnackbar(null, {
@ -79,18 +75,18 @@ export default function BasePage({pageImage, title, children, messages}) {
});
}, [messages]);
return (
<Box className={classes.root}>
<StyledBox >
<Box display="flex" minWidth="80%" gap="40px" alignItems="center" padding="20px 80px">
<Box flexGrow={1} height="80%" textAlign="center">
{pageImage}
</Box>
<Box className={classes.pageContent}>
<Box className={classes.item}><div className={classes.logo} /></Box>
<Box className={classes.item}>{title}</Box>
<Box className='BasePage-pageContent'>
<Box className='BasePage-item'><div className='BasePage-logo' /></Box>
<Box className='BasePage-item'>{title}</Box>
<Box display="flex" flexDirection="column" minHeight={0}>{children}</Box>
</Box>
</Box>
</Box>
</StyledBox>
);
}

View File

@ -13,7 +13,6 @@
*/
import React, { useEffect, useMemo, useState } from 'react';
import { makeStyles } from '@mui/styles';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import { LocalizationProvider } from '@mui/x-date-pickers';
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFnsV3';
@ -30,6 +29,7 @@ import jsonEditorOverride from './overrides/jsoneditor.override';
import pgadminOverride from './overrides/pgadmin.classes.override';
import reactAspenOverride from './overrides/reactaspen.override';
import usePreferences from '../../../preferences/static/js/store';
import szhMenuOverride from './overrides/szhmenu.override';
/* Common settings across all themes */
let basicSettings = createTheme();
@ -423,7 +423,8 @@ function getFinalTheme(baseTheme) {
...cmOverride(baseTheme),
...jsonEditorOverride(baseTheme),
...pgadminOverride(baseTheme),
...reactAspenOverride(baseTheme)
...reactAspenOverride(baseTheme),
...szhMenuOverride(baseTheme)
},
},
MuiOutlinedInput: {
@ -805,83 +806,3 @@ export default function Theme({children}) {
Theme.propTypes = {
children: CustomPropTypes.children,
};
export const commonTableStyles = makeStyles((theme)=>({
table: {
borderSpacing: 0,
width: '100%',
overflow: 'auto',
backgroundColor: theme.otherVars.tableBg,
border: '1px solid '+theme.otherVars.borderColor,
'& tbody td, & thead th': {
margin: 0,
padding: theme.spacing(0.5),
border: '1px solid '+theme.otherVars.borderColor,
borderBottom: 'none',
position: 'relative',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
userSelect: 'text',
maxWidth: '250px',
'&:first-of-type':{
borderLeft: 'none',
},
},
'& thead tr:first-of-type th': {
borderTop: 'none',
},
'& tbody tr:last-of-type': {
'&:hover td': {
borderBottomColor: theme.palette.primary.main,
},
'& td': {
borderBottomColor: theme.otherVars.borderColor,
}
},
'& th': {
fontWeight: theme.typography.fontWeightBold,
padding: theme.spacing(1, 0.5),
textAlign: 'left',
},
'& tbody > tr': {
'&:hover': {
backgroundColor: theme.palette.primary.light,
'& td': {
borderBottom: '1px solid '+theme.palette.primary.main,
borderTop: '1px solid '+theme.palette.primary.main,
},
'&:last-of-type td': {
borderBottomColor: theme.palette.primary.main,
},
},
},
},
noBorder: {
border: 0,
},
borderBottom: {
'& tbody tr:last-of-type td': {
borderBottom: '1px solid '+theme.otherVars.borderColor,
},
},
wrapTd: {
'& tbody td': {
whiteSpace: 'pre-wrap',
}
},
noHover: {
'& tbody > tr': {
'&:hover': {
backgroundColor: theme.otherVars.tableBg,
'& td': {
borderBottomColor: theme.otherVars.borderColor,
borderTopColor: theme.otherVars.borderColor,
},
'&:last-of-type td': {
borderBottomColor: theme.otherVars.borderColor,
},
},
},
}
}));

View File

@ -0,0 +1,38 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2024, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
export default function szhMenuOverride(theme) {
return {
'& .szh-menu': {
padding: '4px 0px',
zIndex: 1005,
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
border: `1px solid ${theme.otherVars.borderColor}`
},
'& .szh-menu__divider': {
margin: 0,
background: theme.otherVars.borderColor,
},
'& .szh-menu__item': {
display: 'flex',
padding: '3px 12px',
'&:after': {
right: '0.75rem',
},
'&.szh-menu__item--active, &.szh-menu__item--hover': {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
},
'&.szh-menu__item--disabled':{
color: theme.palette.text.muted,
}
}
};
}

View File

@ -8,15 +8,15 @@
//////////////////////////////////////////////////////////////
import { Button, ButtonGroup, Tooltip } from '@mui/material';
import { makeStyles } from '@mui/styles';
import React, { forwardRef } from 'react';
import clsx from 'clsx';
import PropTypes from 'prop-types';
import CustomPropTypes from '../custom_prop_types';
import ShortcutTitle from './ShortcutTitle';
import { styled } from '@mui/material/styles';
const useStyles = makeStyles((theme)=>({
primaryButton: {
const StyledButton = styled(Button)(({theme}) => ({
'&.Buttons-primaryButton': {
border: '1px solid '+theme.palette.primary.main,
'&.Mui-disabled': {
color: [theme.palette.primary.contrastText,'!important'],
@ -26,8 +26,17 @@ const useStyles = makeStyles((theme)=>({
backgroundColor: theme.palette.primary.hoverMain,
borderColor: theme.palette.primary.hoverBorderColor,
},
'&.Buttons-noBorderPrimary': {
color: theme.palette.primary.contrastText,
backgroundColor: theme.palette.primary.main,
'&:hover': {
color: theme.palette.primary.contrastText,
backgroundColor: theme.palette.primary.hoverMain,
borderColor: theme.palette.primary.hoverBorderColor,
},
}
},
defaultButton: {
'&.Buttons-defaultButton': {
backgroundColor: theme.palette.default.main,
color: theme.palette.default.contrastText,
border: '1px solid '+theme.palette.default.borderColor,
@ -40,15 +49,23 @@ const useStyles = makeStyles((theme)=>({
backgroundColor: theme.palette.default.hoverMain,
color: theme.palette.default.hoverContrastText,
borderColor: theme.palette.default.hoverBorderColor,
}
},
iconButton: {
minWidth: 0,
padding: '2px 4px',
'&.MuiButton-sizeSmall, &.MuiButton-outlined.MuiButton-sizeSmall, &.MuiButton-contained.MuiButton-sizeSmall': {
},
'&.Buttons-noBorder': {
border: 0,
backgroundColor: 'transparent',
color: theme.custom.icon.contrastText,
'&:hover': {
border: 0,
color: theme.custom.icon.contrastText,
backgroundColor: 'inherit',
filter: 'brightness(85%)',
},
'&.Mui-disabled': {
border: 0,
},
},
},
iconButtonDefault: {
'&.Buttons-iconButtonDefault': {
borderColor: theme.custom.icon.borderColor,
color: theme.custom.icon.contrastText,
backgroundColor: theme.custom.icon.main,
@ -72,7 +89,13 @@ const useStyles = makeStyles((theme)=>({
borderColor: theme.custom.icon.borderColor,
}
},
splitButton: {
'&.Buttons-iconButton': {
minWidth: 0,
padding: '2px 4px',
'&.MuiButton-sizeSmall, &.MuiButton-outlined.MuiButton-sizeSmall, &.MuiButton-contained.MuiButton-sizeSmall': {
},
},
'&.Buttons-splitButton': {
'&.MuiButton-sizeSmall, &.MuiButton-outlined.MuiButton-sizeSmall, &.MuiButton-contained.MuiButton-sizeSmall': {
width: '20px',
minWidth: 0,
@ -81,7 +104,7 @@ const useStyles = makeStyles((theme)=>({
}
}
},
xsButton: {
'&.Buttons-xsButton': {
padding: '2px 1px',
height: '24px !important',
minWidth: '24px',
@ -92,44 +115,22 @@ const useStyles = makeStyles((theme)=>({
minWidth: '30px',
}
},
noBorder: {
border: 0,
backgroundColor: 'transparent',
color: theme.custom.icon.contrastText,
'&:hover': {
border: 0,
color: theme.custom.icon.contrastText,
backgroundColor: 'inherit',
filter: 'brightness(85%)',
},
'&.Mui-disabled': {
border: 0,
},
},
noBorderPrimary: {
color: theme.palette.primary.contrastText,
backgroundColor: theme.palette.primary.main,
'&:hover': {
color: theme.palette.primary.contrastText,
backgroundColor: theme.palette.primary.hoverMain,
borderColor: theme.palette.primary.hoverBorderColor,
},
}
}));
/* pgAdmin primary button */
export const PrimaryButton = forwardRef((props, ref)=>{
let {children, className, size, noBorder, ...otherProps} = props;
const classes = useStyles();
let allClassName = [classes.primaryButton, className];
let allClassName = ['Buttons-primaryButton', className];
if(size == 'xs') {
size = undefined;
allClassName.push(classes.xsButton);
allClassName.push('Buttons-xsButton');
}
noBorder && allClassName.push(...[classes.noBorder, classes.noBorderPrimary]);
noBorder && allClassName.push(...['Buttons-noBorder', 'Buttons-noBorderPrimary']);
const dataLabel = typeof(children) == 'string' ? children : undefined;
return (
<Button ref={ref} size={size} className={clsx(allClassName)} data-label={dataLabel} {...otherProps} color="primary" variant="contained">{children}</Button>
<StyledButton ref={ref} size={size} className={allClassName.join(' ')} data-label={dataLabel} {...otherProps} color="primary" variant="contained">{children}</StyledButton>
);
});
PrimaryButton.displayName = 'PrimaryButton';
@ -143,16 +144,15 @@ PrimaryButton.propTypes = {
/* pgAdmin default button */
export const DefaultButton = forwardRef((props, ref)=>{
let {children, className, size, noBorder, ...otherProps} = props;
const classes = useStyles();
let allClassName = [classes.defaultButton, className];
let allClassName = ['Buttons-defaultButton', className];
if(size == 'xs') {
size = undefined;
allClassName.push(classes.xsButton);
allClassName.push('Buttons-xsButton');
}
noBorder && allClassName.push(classes.noBorder);
noBorder && allClassName.push('Buttons-noBorder');
const dataLabel = typeof(children) == 'string' ? children : undefined;
return (
<Button variant="outlined" ref={ref} size={size} className={clsx(allClassName)} data-label={dataLabel} color="default" {...otherProps}>{children}</Button>
<StyledButton variant="outlined" ref={ref} size={size} className={allClassName.join(' ')} data-label={dataLabel} color="default" {...otherProps}>{children}</StyledButton>
);
});
DefaultButton.displayName = 'DefaultButton';
@ -166,8 +166,6 @@ DefaultButton.propTypes = {
/* pgAdmin Icon button, takes Icon component as input */
export const PgIconButton = forwardRef(({icon, title, shortcut, className, splitButton, style, color, accesskey, ...props}, ref)=>{
const classes = useStyles();
let shortcutTitle = null;
if(accesskey || shortcut) {
shortcutTitle = <ShortcutTitle title={title} accesskey={accesskey} shortcut={shortcut}/>;
@ -178,7 +176,7 @@ export const PgIconButton = forwardRef(({icon, title, shortcut, className, split
if(color == 'primary') {
return (
<PrimaryButton ref={ref} style={style}
className={clsx(classes.iconButton, (splitButton ? classes.splitButton : ''), className)}
className={['Buttons-iconButton', (splitButton ? 'Buttons-splitButton' : ''), className].join(' ')}
accessKey={accesskey} data-label={title || ''} {...props}>
{icon}
</PrimaryButton>
@ -186,7 +184,7 @@ export const PgIconButton = forwardRef(({icon, title, shortcut, className, split
} else {
return (
<DefaultButton ref={ref} style={style}
className={clsx(classes.iconButton, classes.iconButtonDefault, (splitButton ? classes.splitButton : ''), className)}
className={['Buttons-iconButton', 'Buttons-iconButtonDefault',(splitButton ? 'Buttons-splitButton' : ''), className].join(' ')}
accessKey={accesskey} data-label={title || ''} {...props}>
{icon}
</DefaultButton>
@ -196,17 +194,18 @@ export const PgIconButton = forwardRef(({icon, title, shortcut, className, split
return (
<Tooltip title={shortcutTitle || title || ''} aria-label={title || ''}>
<PrimaryButton ref={ref} style={style}
className={clsx(classes.iconButton, (splitButton ? classes.splitButton : ''), className)}
className={['Buttons-iconButton', (splitButton ? 'Buttons-splitButton' : ''), className].join(' ')}
accessKey={accesskey} data-label={title || ''} {...props}>
{icon}
</PrimaryButton>
</Tooltip>
);
} else {
return (
<Tooltip title={shortcutTitle || title || ''} aria-label={title || ''}>
<DefaultButton ref={ref} style={style}
className={clsx(classes.iconButton, classes.iconButtonDefault, (splitButton ? classes.splitButton : ''), className)}
className={['Buttons-iconButton', 'Buttons-iconButtonDefault',(splitButton ? 'Buttons-splitButton' : ''), className].join(' ')}
accessKey={accesskey} data-label={title || ''} {...props}>
{icon}
</DefaultButton>

View File

@ -1,5 +1,5 @@
import React from 'react';
import { makeStyles } from '@mui/styles';
import { styled } from '@mui/material/styles';
import CheckboxTree from 'react-checkbox-tree';
import CheckBoxIcon from '@mui/icons-material/CheckBox';
import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank';
@ -8,37 +8,33 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import PropTypes from 'prop-types';
const useStyles = makeStyles((theme) =>
({
treeRoot: {
'& .rct-collapse, .rct-checkbox': {
padding: 0
},
'& .rct-node-leaf':{
padding: '0 0 0 10px'
},
'& .react-checkbox-tree': {
height: '97%',
fontSize: '0.815rem',
overflow: 'auto',
...theme.mixins.panelBorder
},
height: '100%'
},
unchecked: {
fill: theme.otherVars.borderColor
},
checked: {
fill: theme.palette.primary.main
}
})
);
const StyledDiv = styled('div')(({theme}) => ({
height: '100%',
'& .rct-collapse, .rct-checkbox': {
padding: 0
},
'& .rct-node-leaf':{
padding: '0 0 0 10px'
},
'& .react-checkbox-tree': {
height: '97%',
fontSize: '0.815rem',
overflow: 'auto',
...theme.mixins.panelBorder
},
'& .CheckBoxTree-unchecked': {
fill: theme.otherVars.borderColor
},
'& .CheckBoxTree-checked': {
fill: theme.palette.primary.main
}
}));
export default function CheckBoxTree({treeData, ...props}) {
const [checked, setChecked] = React.useState([]);
const [expanded, setExpanded] = React.useState([]);
const classes = useStyles();
React.useEffect(() => {
if (props.getSelectedServers) {
props.getSelectedServers(checked);
@ -46,7 +42,7 @@ export default function CheckBoxTree({treeData, ...props}) {
}, [checked]);
return (
<div className={classes.treeRoot}>
<StyledDiv>
<CheckboxTree
nodes={treeData}
checked={checked}
@ -55,15 +51,15 @@ export default function CheckBoxTree({treeData, ...props}) {
onExpand={expandedVal => setExpanded(expandedVal)}
showNodeIcon={false}
icons={{
check: <CheckBoxIcon className={classes.checked}/>,
uncheck: <CheckBoxOutlineBlankIcon className={classes.unchecked}/>,
halfCheck: <IndeterminateCheckBoxIcon className={classes.checked}/>,
check: <CheckBoxIcon className='CheckBoxTree-checked'/>,
uncheck: <CheckBoxOutlineBlankIcon className='CheckBoxTree-unchecked'/>,
halfCheck: <IndeterminateCheckBoxIcon className='CheckBoxTree-checked'/>,
expandClose: <ChevronRightIcon />,
expandOpen: <ExpandMoreIcon />,
leaf: <ChevronRightIcon />
}}
/>
</div>
</StyledDiv>
);
}

View File

@ -1,28 +1,26 @@
import React from 'react';
import { styled } from '@mui/material/styles';
import { Box } from '@mui/material';
import InfoRoundedIcon from '@mui/icons-material/InfoRounded';
import { makeStyles } from '@mui/styles';
import PropTypes from 'prop-types';
const useStyles = makeStyles((theme)=>({
root: {
color: theme.palette.text.primary,
margin: '24px auto 12px',
fontSize: '0.8rem',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
},
const StyledBox = styled(Box)(({theme}) => ({
color: theme.palette.text.primary,
margin: '24px auto 12px',
fontSize: '0.8rem',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
}));
export default function EmptyPanelMessage({text, style}) {
const classes = useStyles();
return (
<Box className={classes.root} style={style}>
<StyledBox style={style}>
<InfoRoundedIcon style={{height: '1.2rem'}}/>
<span style={{marginLeft: '4px'}}>{text}</span>
</Box>
</StyledBox>
);
}
EmptyPanelMessage.propTypes = {

View File

@ -1,17 +1,14 @@
import { makeStyles } from '@mui/styles';
import { styled } from '@mui/material/styles';
import React from 'react';
import clsx from 'clsx';
import PropTypes from 'prop-types';
import CustomPropTypes from '../custom_prop_types';
const useStyles = makeStyles((theme)=>({
fieldset: {
padding: theme.spacing(0.5),
borderRadius: theme.shape.borderRadius,
backgroundColor: 'inherit',
border: '1px solid ' + theme.otherVars.borderColor,
},
legend: {
const StyledFieldset = styled('fieldset')(({theme}) => ({
padding: theme.spacing(0.5),
borderRadius: theme.shape.borderRadius,
backgroundColor: 'inherit',
border: '1px solid ' + theme.otherVars.borderColor,
'& .FieldSet-legend': {
width: 'unset',
fontSize: 'inherit',
fontWeight: 'bold',
@ -19,12 +16,12 @@ const useStyles = makeStyles((theme)=>({
}));
export default function FieldSet({title='', className, children}) {
const classes = useStyles();
return (
<fieldset className={clsx(classes.fieldset, className)}>
<legend className={classes.legend}>{title}</legend>
<StyledFieldset className={className}>
<legend className='FieldSet-legend'>{title}</legend>
{children}
</fieldset>
</StyledFieldset>
);
}

View File

@ -9,7 +9,7 @@
/* Common form components used in pgAdmin */
import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { makeStyles } from '@mui/styles';
import { styled } from '@mui/material/styles';
import {
Box, FormControl, OutlinedInput, FormHelperText, ToggleButton, ToggleButtonGroup,
Grid, IconButton, FormControlLabel, Switch, Checkbox, useTheme, InputLabel, Paper, Select as MuiSelect, Radio, Tooltip,
@ -24,7 +24,6 @@ import DescriptionIcon from '@mui/icons-material/Description';
import AssignmentTurnedIn from '@mui/icons-material/AssignmentTurnedIn';
import Select, { components as RSComponents } from 'react-select';
import CreatableSelect from 'react-select/creatable';
import clsx from 'clsx';
import PropTypes from 'prop-types';
import HTMLReactParse from 'html-react-parser';
@ -46,52 +45,40 @@ import PgTreeView from '../PgTreeView';
import Loader from 'sources/components/Loader';
const useStyles = makeStyles((theme) => ({
formRoot: {
padding: '1rem'
const Root = styled('div')(({theme}) => ({
'& .Form-optionIcon': {
...theme.mixins.nodeIcon,
},
img: {
maxWidth: '100%',
height: 'auto'
},
info: {
color: theme.palette.info.main,
marginLeft: '0.25rem',
fontSize: '1rem',
},
formLabel: {
margin: theme.spacing(0.75, 0.75, 0.75, 0.75),
display: 'flex',
wordBreak: 'break-word'
},
formLabelError: {
color: theme.palette.error.main,
},
sql: {
// '& .Form-label': {
// margin: theme.spacing(0.75, 0.75, 0.75, 0.75),
// display: 'flex',
// wordBreak: 'break-word'
// },
// '& .Form-labelError': {
// color: theme.palette.error.main,
// },
'& .Form-sql': {
border: '1px solid ' + theme.otherVars.inputBorderColor,
borderRadius: theme.shape.borderRadius,
height: '100%',
},
optionIcon: {
...theme.mixins.nodeIcon,
'& .Form-readOnlySwitch': {
opacity: 0.75,
'& .MuiSwitch-track': {
opacity: theme.palette.action.disabledOpacity,
}
},
colorBtn: {
'& .Form-colorBtn': {
height: theme.spacing(3.5),
minHeight: theme.spacing(3.5),
width: theme.spacing(3.5),
minWidth: theme.spacing(3.5),
},
noteRoot: {
'& .Form-noteRoot': {
display: 'flex',
backgroundColor: theme.otherVars.borderColor,
padding: theme.spacing(1),
},
readOnlySwitch: {
opacity: 0.75,
'& .MuiSwitch-track': {
opacity: theme.palette.action.disabledOpacity,
}
}
}));
@ -125,36 +112,47 @@ FormIcon.propTypes = {
close: PropTypes.bool,
};
const StyledGrid = styled(Grid)(({theme}) => ({
'& .Form-label': {
margin: theme.spacing(0.75, 0.75, 0.75, 0.75),
display: 'flex',
wordBreak: 'break-word'
},
'& .Form-labelError': {
color: theme.palette.error.main,
},
}));
/* Wrapper on any form component to add label, error indicator and help message */
export function FormInput({ children, error, className, label, helpMessage, required, testcid, lid, withContainer=true, labelGridBasis=3, controlGridBasis=9, labelTooltip='' }) {
const classes = useStyles();
const cid = testcid || _.uniqueId('c');
const helpid = `h${cid}`;
if(!withContainer) {
return (
<>
<Grid item lg={labelGridBasis} md={labelGridBasis} sm={12} xs={12}>
<InputLabel id={lid} htmlFor={lid ? undefined : cid} className={clsx(classes.formLabel, error ? classes.formLabelError : null)} required={required}>
(<>
<StyledGrid item lg={labelGridBasis} md={labelGridBasis} sm={12} xs={12}>
<InputLabel id={lid} htmlFor={lid ? undefined : cid} className={'Form-label ' + (error ? 'Form-labelError' : null)} required={required}>
{label}
<FormIcon type={MESSAGE_TYPE.ERROR} style={{ marginLeft: 'auto', visibility: error ? 'unset' : 'hidden' }} />
</InputLabel>
</Grid>
<Grid item lg={controlGridBasis} md={controlGridBasis} sm={12} xs={12}>
</StyledGrid>
<StyledGrid item lg={controlGridBasis} md={controlGridBasis} sm={12} xs={12}>
<FormControl error={Boolean(error)} fullWidth>
{React.cloneElement(children, { cid, helpid })}
</FormControl>
<FormHelperText id={helpid} variant="outlined">{HTMLReactParse(helpMessage || '')}</FormHelperText>
</Grid>
</>
</StyledGrid>
</>)
);
}
let labelComponent = <InputLabel id={lid} htmlFor={lid ? undefined : cid} className={clsx(classes.formLabel, error ? classes.formLabelError : null)} required={required}>
let labelComponent = <InputLabel id={lid} htmlFor={lid ? undefined : cid} className={'Form-label ' + (error ? 'Form-labelError' : null)} required={required}>
{label}
<FormIcon type={MESSAGE_TYPE.ERROR} style={{ marginLeft: 'auto', visibility: error ? 'unset' : 'hidden' }} />
</InputLabel>;
return (
<Grid container spacing={0} className={className} data-testid="form-input">
<StyledGrid container spacing={0} className={className} data-testid="form-input">
<Grid item lg={labelGridBasis} md={labelGridBasis} sm={12} xs={12}>
{
labelTooltip ?
@ -169,7 +167,7 @@ export function FormInput({ children, error, className, label, helpMessage, requ
</FormControl>
<FormHelperText id={helpid} variant="outlined">{HTMLReactParse(helpMessage || '')}</FormHelperText>
</Grid>
</Grid>
</StyledGrid>
);
}
FormInput.propTypes = {
@ -188,24 +186,26 @@ FormInput.propTypes = {
};
export function InputSQL({ value, options, onChange, className, controlProps, inputRef, ...props }) {
const classes = useStyles();
const editor = useRef();
return (
<CodeMirror
currEditor={(obj) => {
editor.current = obj;
inputRef?.(obj);
}}
value={value || ''}
options={{
...options,
}}
className={clsx(classes.sql, className)}
onChange={onChange}
{...controlProps}
{...props}
/>
<Root style={{height: '100%'}}>
<CodeMirror
currEditor={(obj) => {
editor.current = obj;
inputRef?.(obj);
}}
value={value || ''}
options={{
...options,
}}
className={'Form-sql ' + className}
onChange={onChange}
{...controlProps}
{...props}
/>
</Root>
);
}
InputSQL.propTypes = {
@ -510,7 +510,7 @@ FormInputFileSelect.propTypes = {
};
export function InputSwitch({ cid, helpid, value, onChange, readonly, controlProps, ...props }) {
const classes = useStyles();
return (
<Switch color="primary"
checked={Boolean(value)}
@ -523,7 +523,7 @@ export function InputSwitch({ cid, helpid, value, onChange, readonly, controlPro
}}
{...controlProps}
{...props}
className={(readonly || props.disabled) ? classes.readOnlySwitch : null}
className={(readonly || props.disabled) ? 'Form-readOnlySwitch' : null}
/>
);
}
@ -605,7 +605,6 @@ FormInputCheckbox.propTypes = {
};
export function InputRadio({ helpid, value, onChange, controlProps, readonly, labelPlacement, ...props }) {
const classes = useStyles();
controlProps = controlProps || {};
return (
<FormControlLabel
@ -624,11 +623,10 @@ export function InputRadio({ helpid, value, onChange, controlProps, readonly, la
disableRipple
{...props}
/>
}
label={controlProps.label}
labelPlacement={labelPlacement}
className={(readonly || props.disabled) ? classes.readOnlySwitch : null}
className={(readonly || props.disabled) ? 'Form-readOnlySwitch' : null}
/>
);
}
@ -811,13 +809,13 @@ const customReactSelectStyles = (theme, readonly) => ({
});
function OptionView({ image, imageUrl, label }) {
const classes = useStyles();
return (
<>
{image && <span className={clsx(classes.optionIcon, image)}></span>}
<Root>
{image && <span className={'Form-optionIcon ' + image}></span>}
{imageUrl && <img style={{height: '20px', marginRight: '4px'}} src={imageUrl} />}
<span>{label}</span>
</>
</Root>
);
}
OptionView.propTypes = {
@ -1063,11 +1061,9 @@ FormInputSelect.propTypes = {
const ColorButton = withColorPicker(PgIconButton);
export function InputColor({ value, controlProps, disabled, onChange, currObj }) {
const classes = useStyles();
let btnStyles = { backgroundColor: value };
return (
<ColorButton title={gettext('Select the color')} className={classes.colorBtn} style={btnStyles} disabled={disabled}
<ColorButton title={gettext('Select the color')} className='Form-colorBtn' style={btnStyles} disabled={disabled}
icon={(_.isUndefined(value) || _.isNull(value) || value === '') && <CloseIcon data-label="CloseIcon" />} options={{
...controlProps,
disabled: disabled
@ -1115,15 +1111,17 @@ PlainString.propTypes = {
};
export function FormNote({ text, className, controlProps }) {
const classes = useStyles();
/* If raw, then remove the styles and icon */
return (
<Box className={className}>
<Paper elevation={0} className={controlProps?.raw ? '' : classes.noteRoot}>
{!controlProps?.raw && <Box paddingRight="0.25rem"><DescriptionIcon fontSize="small" /></Box>}
<Box>{HTMLReactParse(text || '')}</Box>
</Paper>
</Box>
<Root>
<Box className={className}>
<Paper elevation={0} className={controlProps?.raw ? '' : 'Form-noteRoot'}>
{!controlProps?.raw && <Box paddingRight="0.25rem"><DescriptionIcon fontSize="small" /></Box>}
<Box>{HTMLReactParse(text || '')}</Box>
</Paper>
</Box>
</Root>
);
}
FormNote.propTypes = {
@ -1132,76 +1130,25 @@ FormNote.propTypes = {
controlProps: PropTypes.object,
};
const useStylesFormFooter = makeStyles((theme) => ({
root: {
padding: theme.spacing(0.5),
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
zIndex: 10,
},
container: {
borderWidth: '1px',
borderStyle: 'solid',
borderRadius: theme.shape.borderRadius,
padding: theme.spacing(0.5),
display: 'flex',
alignItems: 'center',
minHeight: '36px',
},
containerSuccess: {
borderColor: theme.palette.success.main,
backgroundColor: theme.palette.success.light,
},
iconSuccess: {
color: theme.palette.success.main,
},
containerError: {
borderColor: theme.palette.error.main,
backgroundColor: theme.palette.error.light,
},
iconError: {
color: theme.palette.error.main,
},
containerInfo: {
borderColor: theme.palette.primary.main,
backgroundColor: theme.palette.primary.light,
},
iconInfo: {
color: theme.palette.primary.main,
},
containerWarning: {
borderColor: theme.palette.warning.main,
backgroundColor: theme.palette.warning.light,
},
iconWarning: {
color: theme.palette.warning.main,
},
message: {
color: theme.palette.text.primary,
marginLeft: theme.spacing(0.5),
},
messageCenter: {
color: theme.palette.text.primary,
margin: 'auto',
},
closeButton: {
marginLeft: 'auto',
},
const StyledBox = styled(Box)(({theme}) => ({
padding: theme.spacing(0.5),
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
zIndex: 10,
}));
/* The form footer used mostly for showing error */
export function FormFooterMessage({style, ...props}) {
const classes = useStylesFormFooter();
if (!props.message) {
return <></>;
}
return (
<Box className={classes.root} style={style}>
<StyledBox style={style}>
<NotifierMessage {...props}></NotifierMessage>
</Box>
</StyledBox>
);
}
@ -1210,18 +1157,17 @@ FormFooterMessage.propTypes = {
message: PropTypes.string,
};
const useStylesKeyboardShortcut = makeStyles(() => ({
customRow: {
const StyledFormInput = styled(FormInput)(() => ({
'&.FormInput-customRow': {
paddingTop: 5
}
}));
export function FormInputKeyboardShortcut({ hasError, label, className, helpMessage, onChange, labelTooltip, ...props }) {
const classes = useStylesKeyboardShortcut();
return (
<FormInput label={label} error={hasError} className={clsx(classes.customRow, className)} helpMessage={helpMessage} labelTooltip={labelTooltip}>
<StyledFormInput label={label} error={hasError} className={'FormInput-customRow ' + className} helpMessage={helpMessage} labelTooltip={labelTooltip}>
<KeyboardShortcuts onChange={onChange} {...props} />
</FormInput>
</StyledFormInput>
);
}
@ -1276,20 +1222,66 @@ FormInputSelectThemes.propTypes = {
labelTooltip: PropTypes.string
};
const StyledNotifierMessageBox = styled(Box)(({theme}) => ({
borderWidth: '1px',
borderStyle: 'solid',
borderRadius: theme.shape.borderRadius,
padding: theme.spacing(0.5),
display: 'flex',
alignItems: 'center',
minHeight: '36px',
'&.FormFooter-containerError': {
borderColor: theme.palette.error.main,
backgroundColor: theme.palette.error.light,
'& .FormFooter-iconError': {
color: theme.palette.error.main,
},
},
'&.FormFooter-containerSuccess': {
borderColor: theme.palette.success.main,
backgroundColor: theme.palette.success.light,
'& .FormFooter-iconSuccess': {
color: theme.palette.success.main,
},
},
'&.FormFooter-containerInfo': {
borderColor: theme.palette.primary.main,
backgroundColor: theme.palette.primary.light,
'& .FormFooter-iconInfo': {
color: theme.palette.primary.main,
},
},
'&.FormFooter-containerWarning': {
borderColor: theme.palette.warning.main,
backgroundColor: theme.palette.warning.light,
'& .FormFooter-iconWarning': {
color: theme.palette.warning.main,
},
},
'& .FormFooter-message': {
color: theme.palette.text.primary,
marginLeft: theme.spacing(0.5),
},
'& .FormFooter-messageCenter': {
color: theme.palette.text.primary,
margin: 'auto',
},
'& .FormFooter-closeButton': {
marginLeft: 'auto',
},
}));
export function NotifierMessage({
type = MESSAGE_TYPE.SUCCESS, message, style, closable = true, showIcon=true, textCenter=false,
onClose = () => {/*This is intentional (SonarQube)*/ }}) {
const classes = useStylesFormFooter();
return (
<Box className={clsx(classes.container, classes[`container${type}`])} style={style} data-test="notifier-message">
{showIcon && <FormIcon type={type} className={classes[`icon${type}`]} />}
<Box className={textCenter ? classes.messageCenter : classes.message}>{HTMLReactParse(message || '')}</Box>
{closable && <IconButton title={gettext('Close Message')} className={clsx(classes.closeButton, classes[`icon${type}`])} onClick={onClose}>
<StyledNotifierMessageBox className={`FormFooter-container${type}`} style={style} data-test="notifier-message">
{showIcon && <FormIcon type={type} className={`FormFooter-icon${type}`} />}
<Box className={textCenter ? 'FormFooter-messageCenter' : 'FormFooter-message'}>{HTMLReactParse(message || '')}</Box>
{closable && <IconButton title={gettext('Close Message')} className={'FormFooter-closeButton ' + `FormFooter-icon${type}`} onClick={onClose}>
<FormIcon close={true} />
</IconButton>}
</Box>
</StyledNotifierMessageBox>
);
}

View File

@ -112,4 +112,4 @@ KeyboardShortcuts.propTypes = {
onChange: PropTypes.func,
fields: PropTypes.array,
name: PropTypes.string,
};
};

View File

@ -8,52 +8,50 @@
//////////////////////////////////////////////////////////////
import { CircularProgress, Box, Typography } from '@mui/material';
import { makeStyles } from '@mui/styles';
import { styled } from '@mui/material/styles';
import React from 'react';
import PropTypes from 'prop-types';
const useStyles = makeStyles((theme)=>({
root: {
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0,
backgroundColor: theme.otherVars.loader.backgroundColor,
color: theme.otherVars.loader.color,
zIndex: 1000,
display: 'flex',
},
loaderRoot: {
const StyledBox = styled(Box)(({theme}) => ({
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0,
backgroundColor: theme.otherVars.loader.backgroundColor,
color: theme.otherVars.loader.color,
zIndex: 1000,
display: 'flex',
'& .Loader-loaderBody': {
color: theme.otherVars.loader.color,
display: 'flex',
alignItems: 'center',
margin: 'auto',
'.MuiTypography-root': {
marginLeft: theme.spacing(1),
},
'& .Loader-icon': {
color: theme.otherVars.loader.color,
},
'& .Loader-message': {
marginLeft: '0.5rem',
fontSize: '16px',
}
},
loader: {
color: theme.otherVars.loader.color,
},
message: {
marginLeft: '0.5rem',
fontSize: '16px',
}
}));
export default function Loader({message, style, autoEllipsis=false, ...props}) {
const classes = useStyles();
if(!message) {
return <></>;
}
return (
<Box className={classes.root} style={style} data-label="loader" {...props}>
<Box className={classes.loaderRoot}>
<CircularProgress className={classes.loader} />
<Typography className={classes.message}>{message}{autoEllipsis ? '...':''}</Typography>
<StyledBox style={style} data-label="loader" {...props}>
<Box className='Loader-loaderBody'>
<CircularProgress className='Loader-icon' />
<Typography className='Loader-message'>{message}{autoEllipsis ? '...':''}</Typography>
</Box>
</Box>
</StyledBox>
);
}

View File

@ -1,4 +1,3 @@
import { makeStyles } from '@mui/styles';
import React, { useRef } from 'react';
import CheckIcon from '@mui/icons-material/Check';
import PropTypes from 'prop-types';
@ -12,52 +11,9 @@ import {
} from '@szhsin/react-menu';
export {MenuDivider as PgMenuDivider} from '@szhsin/react-menu';
import { shortcutToString } from './ShortcutTitle';
import clsx from 'clsx';
import CustomPropTypes from '../custom_prop_types';
const useStyles = makeStyles((theme)=>({
menu: {
'& .szh-menu': {
padding: '4px 0px',
zIndex: 1005,
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
border: `1px solid ${theme.otherVars.borderColor}`
},
'& .szh-menu__divider': {
margin: 0,
background: theme.otherVars.borderColor,
},
'& .szh-menu__item': {
display: 'flex',
padding: '3px 12px',
'&:after': {
right: '0.75rem',
},
'&.szh-menu__item--active, &.szh-menu__item--hover': {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
},
'&.szh-menu__item--disabled':{
color: theme.palette.text.muted,
}
}
},
checkIcon: {
width: '1.3rem',
},
hideCheck: {
visibility: 'hidden',
},
shortcut: {
marginLeft: 'auto',
fontSize: '0.8em',
paddingLeft: '12px',
}
}));
export function PgMenu({open, className='', label, menuButton=null, ...props}) {
const classes = useStyles();
const state = open ? 'open' : 'closed';
props.anchorRef?.current?.setAttribute('data-state', state);
@ -65,7 +21,7 @@ export function PgMenu({open, className='', label, menuButton=null, ...props}) {
return <Menu
{...props}
menuButton={menuButton}
className={clsx(classes.menu, className)}
className={className}
aria-label={label || 'Menu'}
onContextMenu={(e)=>e.preventDefault()}
viewScroll='close'
@ -75,7 +31,7 @@ export function PgMenu({open, className='', label, menuButton=null, ...props}) {
<ControlledMenu
state={state}
{...props}
className={clsx(classes.menu, className)}
className={className}
aria-label={label || 'Menu'}
data-state={state}
onContextMenu={(e)=>e.preventDefault()}
@ -99,7 +55,7 @@ export const PgSubMenu = applyStatics(SubMenu)(({label, ...props})=>{
});
export const PgMenuItem = applyStatics(MenuItem)(({hasCheck=false, checked=false, accesskey, shortcut, children, closeOnCheck=false, ...props})=>{
const classes = useStyles();
let onClick = props.onClick;
if(hasCheck) {
onClick = (e)=>{
@ -111,9 +67,9 @@ export const PgMenuItem = applyStatics(MenuItem)(({hasCheck=false, checked=false
const dataLabel = typeof(children) == 'string' ? children : props.datalabel;
return <MenuItem {...props} onClick={onClick} data-label={dataLabel} data-checked={checked}>
{hasCheck && <CheckIcon className={classes.checkIcon} style={checked ? {} : {visibility: 'hidden'}} data-label="CheckIcon"/>}
{hasCheck && <CheckIcon style={checked ? {} : {visibility: 'hidden', width: '1.3rem'}} data-label="CheckIcon"/>}
{children}
<div className={classes.shortcut}>
<div>
{keyVal ? `(${keyVal})` : ''}
</div>
</MenuItem>;

View File

@ -0,0 +1,56 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2024, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import React, { forwardRef } from 'react';
import { styled } from '@mui/material/styles';
import CustomPropTypes from '../custom_prop_types';
import { Box } from '@mui/material';
const StyledBox = styled(Box)(({theme})=>({
backgroundColor: theme.palette.background.default,
display: 'flex',
flexDirection: 'column',
'& .ModalContent-footer': {
display: 'flex',
justifyContent: 'flex-end',
padding: '0.5rem',
...theme.mixins.panelBorder?.top,
gap: '5px',
'&.ModalContent-iconButtonStyle': {
marginLeft: 'auto',
marginRight: '4px'
},
},
}));
export const ModalContent = forwardRef(({children, ...props }, ref) => {
return (
<StyledBox ref={ref} {...props}>{children}</StyledBox>
);
});
ModalContent.displayName = 'ModalContent';
ModalContent.propTypes = {
children: CustomPropTypes.children,
};
export function ModalFooter({children, classNameRoot, ...props}) {
return (
<StyledBox className={[classNameRoot]} {...props}>
<Box className='ModalContent-footer'>
{children}
</Box>
</StyledBox>
);
}
ModalFooter.propTypes = {
children: CustomPropTypes.children,
classNameRoot: CustomPropTypes.className
};

View File

@ -7,7 +7,7 @@
//
//////////////////////////////////////////////////////////////
import { Box } from '@mui/material';
import { makeStyles } from '@mui/styles';
import { styled } from '@mui/material/styles';
import React, { useState, useEffect } from 'react';
import AccountTreeIcon from '@mui/icons-material/AccountTree';
import CommentIcon from '@mui/icons-material/Comment';
@ -15,33 +15,30 @@ import ArrowForwardIosRoundedIcon from '@mui/icons-material/ArrowForwardIosRound
import { usePgAdmin } from '../../../static/js/BrowserComponent';
import usePreferences from '../../../preferences/static/js/store';
const useStyles = makeStyles((theme)=>({
root: {
position: 'absolute',
bottom: 0,
width: 'auto',
maxWidth: '99%',
zIndex: 1004,
padding: '0.25rem 0.5rem',
fontSize: '0.95em',
color: theme.palette.background.default,
backgroundColor: theme.palette.text.primary,
borderTopRightRadius: theme.shape.borderRadius,
},
row: {
const StyledBox = styled(Box)(({theme}) => ({
position: 'absolute',
bottom: 0,
width: 'auto',
maxWidth: '99%',
zIndex: 1004,
padding: '0.25rem 0.5rem',
fontSize: '0.95em',
color: theme.palette.background.default,
backgroundColor: theme.palette.text.primary,
borderTopRightRadius: theme.shape.borderRadius,
'& .ObjectBreadcrumbs-row': {
display: 'flex',
alignItems: 'center'
alignItems: 'center',
'& .ObjectBreadcrumbs-overflow': {
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
}
},
overflow: {
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
}
}));
export default function ObjectBreadcrumbs() {
const classes = useStyles();
const pgAdmin = usePgAdmin();
const preferences = usePreferences().getPreferencesForModule('browser');
const [objectData, setObjectData] = useState({
@ -75,11 +72,11 @@ export default function ObjectBreadcrumbs() {
return <></>;
}
return(
<Box className={classes.root} data-testid="object-breadcrumbs">
<div className={classes.row}>
return (
<StyledBox data-testid="object-breadcrumbs">
<div className='ObjectBreadcrumbs-row'>
<AccountTreeIcon style={{height: '1rem', marginRight: '0.125rem'}} data-label="AccountTreeIcon"/>
<div className={classes.overflow}>
<div className='ObjectBreadcrumbs-overflow'>
{
objectData.path?.reduce((res, item, i)=>(
res.concat(<span key={item+i}>{item}</span>, <ArrowForwardIosRoundedIcon key={item+i+'-arrow'} style={{height: '0.8rem', width: '1.25rem'}} />)
@ -88,10 +85,10 @@ export default function ObjectBreadcrumbs() {
</div>
</div>
{preferences.breadcrumbs_show_comment && objectData.description &&
<div className={classes.row}>
<div className='ObjectBreadcrumbs-row'>
<CommentIcon style={{height: '1rem', marginRight: '0.125rem'}} data-label="CommentIcon"/>
<div className={classes.overflow}>{objectData.description}</div>
<div className='ObjectBreadcrumbs-overflow'>{objectData.description}</div>
</div>}
</Box>
</StyledBox>
);
}

View File

@ -3,7 +3,7 @@ import UplotReact from 'uplot-react';
import { useResizeDetector } from 'react-resize-detector';
import gettext from 'sources/gettext';
import PropTypes from 'prop-types';
import { useTheme } from '@mui/styles';
import { useTheme } from '@mui/material';
function tooltipPlugin(refreshRate) {
let tooltipTopOffset = -20;

View File

@ -9,23 +9,22 @@
import React, { useContext, useEffect } from 'react';
import ReactDataGrid, { Row } from 'react-data-grid';
import { Box } from '@mui/material';
import { makeStyles } from '@mui/styles';
import clsx from 'clsx';
import PropTypes from 'prop-types';
import CustomPropTypes from '../custom_prop_types';
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
import gettext from 'sources/gettext';
import { styled } from '@mui/material/styles';
const useStyles = makeStyles((theme)=>({
root: {
const StyledReactDataGrid = styled(ReactDataGrid)(({theme})=>({
'&.ReactGrid-root': {
height: '100%',
color: theme.palette.text.primary,
backgroundColor: theme.otherVars.qtDatagridBg,
fontSize: '12px',
border: 'none',
'--rdg-selection-color': theme.palette.primary.main,
'& .rdg-cell': {
'&.rdg-cell': {
...theme.mixins.panelBorder.right,
...theme.mixins.panelBorder.bottom,
fontWeight: 'abc',
@ -44,7 +43,7 @@ const useStyles = makeStyles((theme)=>({
'& .rdg-header-row': {
backgroundColor: theme.palette.background.default,
},
'& .rdg-row': {
'&.rdg-row': {
backgroundColor: theme.palette.background.default,
'&[aria-selected=true]': {
backgroundColor: theme.palette.primary.light,
@ -52,7 +51,7 @@ const useStyles = makeStyles((theme)=>({
},
}
},
cellSelection: {
'&.ReactGrid-cellSelection': {
'& .rdg-cell': {
'&[aria-selected=true]:not([role="columnheader"])': {
outlineWidth: '1px',
@ -62,7 +61,7 @@ const useStyles = makeStyles((theme)=>({
}
},
},
hasSelectColumn: {
'&.ReactGrid-hasSelectColumn': {
'& .rdg-cell': {
'&[aria-selected=true][aria-colindex="1"]': {
outlineWidth: '2px',
@ -71,7 +70,7 @@ const useStyles = makeStyles((theme)=>({
color: theme.palette.text.primary,
}
},
'& .rdg-row[aria-selected=true] .rdg-cell:nth-child(1)': {
'& .rdg-row[aria-selected=true] .rdg-cell:nth-of-type(1)': {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
}
@ -131,16 +130,16 @@ CustomRow.propTypes = {
export default function PgReactDataGrid({gridRef, className, hasSelectColumn=true, onItemEnter, onItemSelect,
onItemClick, noRowsText, noRowsIcon,...props}) {
const classes = useStyles();
let finalClassName = [classes.root];
hasSelectColumn && finalClassName.push(classes.hasSelectColumn);
props.enableCellSelect && finalClassName.push(classes.cellSelection);
let finalClassName = ['ReactGrid-root'];
hasSelectColumn && finalClassName.push('ReactGrid-hasSelectColumn');
props.enableCellSelect && finalClassName.push('ReactGrid-cellSelection');
finalClassName.push(className);
return (
<GridContextUtils.Provider value={{onItemEnter, onItemSelect, onItemClick}}>
<ReactDataGrid
<StyledReactDataGrid
ref={gridRef}
className={clsx(finalClassName)}
className={finalClassName.join(' ')}
components={{
sortIcon: CutomSortIcon,
rowRenderer: CustomRow,

View File

@ -9,13 +9,12 @@
import React, { forwardRef, useEffect } from 'react';
import { flexRender } from '@tanstack/react-table';
import { styled } from '@mui/styles';
import { styled } from '@mui/material/styles';
import PropTypes from 'prop-types';
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import { PgIconButton } from './Buttons';
import clsx from 'clsx';
import CustomPropTypes from '../custom_prop_types';
import { InputSwitch } from './FormComponents';
@ -161,6 +160,9 @@ export const PgReactTableCell = forwardRef(({row, cell, children, className}, re
if(row.original.icon && row.original.icon[cell.column.id]) {
classNames.push(row.original.icon[cell.column.id], 'cell-with-icon');
}
if(cell.column.columnDef.dataClassName){
classNames.push(cell.column.columnDef.dataClassName);
}
classNames.push(className);
@ -170,7 +172,7 @@ export const PgReactTableCell = forwardRef(({row, cell, children, className}, re
width: `calc(var(--col-${cell.column.id.replace(/\W/g, '_')}-size)*1px)`,
...(cell.column.columnDef.maxSize ? { maxWidth: `${cell.column.columnDef.maxSize}px` } : {})
}} role='cell'
className={clsx(...classNames)}
className={classNames.join(' ')}
title={String(cell.getValue() ?? '')}>
<div className='pgrd-row-cell-content'>{children}</div>
</div>
@ -187,7 +189,7 @@ PgReactTableCell.propTypes = {
export const PgReactTableRow = forwardRef(({ children, className, ...props }, ref)=>{
return (
<div className={clsx('pgrt-row', className)} ref={ref} role="row" {...props}>
<div className={['pgrt-row', className].join(' ')} ref={ref} role="row" {...props}>
{children}
</div>
);
@ -200,7 +202,7 @@ PgReactTableRow.propTypes = {
export const PgReactTableRowContent = forwardRef(({children, className, ...props}, ref)=>{
return (
<div className={clsx('pgrt-row-content', className)} ref={ref} {...props}>
<div className={['pgrt-row-content', className].join(' ')} ref={ref} {...props}>
{children}
</div>
);
@ -311,8 +313,8 @@ export const PgReactTable = forwardRef(({children, table, rootClassName, tableCl
}, [columns, table.getState().columnSizingInfo]);
return (
<StyledDiv className={clsx('pgrt', rootClassName)} ref={ref} >
<div className={clsx('pgrt-table', tableClassName)} style={{ ...columnSizeVars }} {...props}>
<StyledDiv className={['pgrt', rootClassName].join(' ')} ref={ref} >
<div className={['pgrt-table', tableClassName].join(' ')} style={{ ...columnSizeVars }} {...props}>
{children}
</div>
</StyledDiv>

View File

@ -17,7 +17,7 @@ import {
flexRender,
} from '@tanstack/react-table';
import { useVirtualizer } from '@tanstack/react-virtual';
import { styled } from '@mui/styles';
import { styled } from '@mui/material/styles';
import PropTypes from 'prop-types';
import { Checkbox, Box } from '@mui/material';
import { InputText } from './FormComponents';
@ -28,7 +28,7 @@ import EmptyPanelMessage from './EmptyPanelMessage';
import { PgReactTable, PgReactTableBody, PgReactTableCell, PgReactTableHeader, PgReactTableRow, PgReactTableRowContent, PgReactTableRowExpandContent } from './PgReactTableStyled';
const ROW_HEIGHT = 30;
function TableRow({ index, style, schema, row, measureElement }) {
function TableRow({ index, style, schema, row, measureElement}) {
const [expandComplete, setExpandComplete] = React.useState(false);
const rowRef = React.useRef();
@ -199,8 +199,6 @@ const StyledPgTableRoot = styled('div')(({theme})=>({
overflow: 'hidden',
flexDirection: 'column',
height: '100%',
'& .pgtable-header': {
display: 'flex',
background: theme.palette.background.default,
@ -210,7 +208,6 @@ const StyledPgTableRoot = styled('div')(({theme})=>({
minWidth: '300px'
},
},
'& .pgtable-body': {
flexGrow: 1,
minHeight: 0,
@ -218,13 +215,11 @@ const StyledPgTableRoot = styled('div')(({theme})=>({
flexDirection: 'column',
backgroundColor: theme.otherVars.emptySpaceBg,
},
'&.pgtable-pgrt-border': {
'& .pgrt': {
border: '1px solid ' + theme.otherVars.borderColor,
}
},
'&.pgtable-pgrt-cave': {
'& .pgtable-body': {
padding: '8px',
@ -244,7 +239,7 @@ export default function PgTable({ caveTable = true, tableNoBorder = true, ...pro
return (
<StyledPgTableRoot className={[tableNoBorder ? '' : 'pgtable-pgrt-border', caveTable ? 'pgtable-pgrt-cave' : ''].join(' ')} data-test={props['data-test']}>
<Box className='pgtable-header'>
{props.CustomHeader && (<Box className='pgtable-custom-header-section'> <props.CustomHeader /></Box>)}
{props.CustomHeader && (<Box className={['pgtable-custom-header-section', props['className']].join(' ')}> <props.CustomHeader /></Box>)}
<Box marginLeft="auto">
<InputText
placeholder={gettext('Search')}
@ -268,5 +263,6 @@ PgTable.propTypes = {
CustomHeader: PropTypes.func,
caveTable: PropTypes.bool,
tableNoBorder: PropTypes.bool,
'data-test': PropTypes.string
'data-test': PropTypes.string,
'className': PropTypes.string
};

View File

@ -1,34 +1,23 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2024, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import { makeStyles } from '@mui/styles';
import { styled } from '@mui/material/styles';
import _ from 'lodash';
import React from 'react';
import { InputCheckbox, InputText } from './FormComponents';
import clsx from 'clsx';
import PropTypes from 'prop-types';
const useStyles = makeStyles(()=>({
const Root = styled('div')(()=>({
/* Display the privs table only when focussed */
root: {
'&:not(:focus-within) .priv-table': {
display: 'none',
}
'&:not(:focus-within) .priv-table': {
display: 'none',
},
table: {
'& .Privilege-table': {
borderSpacing: 0,
width: '100%',
fontSize: '0.8em',
'& .Privilege-tableCell': {
textAlign: 'left',
}
},
tableCell: {
textAlign: 'left',
}
}));
export default function Privilege({value, onChange, controlProps}) {
@ -50,7 +39,7 @@ export default function Privilege({value, onChange, controlProps}) {
};
let all = false;
let allWithGrant = false;
const classes = useStyles();
let textValue = '';
for(const v of value||[]) {
if(v.privilege) {
@ -129,16 +118,16 @@ export default function Privilege({value, onChange, controlProps}) {
allWithGrant = (realVal.length === (value || []).length) && (value || []).every((d)=>d.with_grant);
return (
<div className={classes.root}>
<Root>
<InputText value={textValue} readOnly/>
<table className={clsx(classes.table, 'priv-table')} tabIndex="0">
<table className={'Privilege-table priv-table'} tabIndex="0">
{(realVal.length > 1) && <thead>
<tr>
<td className={classes.tableCell}>
<td className='Privilege-tableCell'>
<InputCheckbox name="all" controlProps={{label: 'ALL'}} id={checkboxId} size="small"
onChange={(e)=>onCheckAll(e, false)} value={all}/>
</td>
<td className={classes.tableCell}>
<td className='Privilege-tableCell'>
<InputCheckbox name="all" controlProps={{label: 'WITH GRANT OPTION'}} id={checkboxId} size="small"
disabled={!all} onChange={(e)=>onCheckAll(e, true)} value={allWithGrant}/>
</td>
@ -149,12 +138,12 @@ export default function Privilege({value, onChange, controlProps}) {
realVal.map((d)=>{
return (
<tr key={d.privilege_type}>
<td className={classes.tableCell}>
<td className='Privilege-tableCell'>
<InputCheckbox name={d.privilege_type} controlProps={{label: LABELS[d.privilege_type]}}
id={checkboxId} value={Boolean(d.privilege)} size="small"
onChange={(e)=>onCheck(e, false)}/>
</td>
<td className={classes.tableCell}>
<td className='Privilege-tableCell'>
<InputCheckbox name={d.privilege_type} controlProps={{label: 'WITH GRANT OPTION'}}
id={checkboxId} value={Boolean(d.with_grant)} size="small" disabled={!d.privilege}
onChange={(e)=>onCheck(e, true)}/>
@ -165,7 +154,7 @@ export default function Privilege({value, onChange, controlProps}) {
}
</tbody>
</table>
</div>
</Root>
);
}

View File

@ -10,36 +10,12 @@
import gettext from 'sources/gettext';
import _ from 'lodash';
import { FormGroup, Grid, Typography } from '@mui/material';
import { makeStyles } from '@mui/styles';
import React from 'react';
import { InputText } from './FormComponents';
import PropTypes from 'prop-types';
const useStyles = makeStyles(() => ({
formControlLabel: {
padding: '3px',
},
formInput: {
marginLeft: '5px'
},
formCheckboxControl: {
padding: '3px',
border: '1px solid',
borderRadius: '0.25rem',
},
formGroup: {
padding: '5px'
},
contentTextAlign: {
textAlign: 'center'
},
contentStyle: {
paddingLeft: 10,
}
}));
export default function QueryThresholds({ value, onChange }) {
const classes = useStyles();
const warningCid = _.uniqueId('c');
const warninghelpid = `h${warningCid}`;
const alertCid = _.uniqueId('c');
@ -70,13 +46,13 @@ export default function QueryThresholds({ value, onChange }) {
<Grid item lg={2} md={2} sm={2} xs={12}>
<InputText cid={warningCid} helpid={warninghelpid} type='numeric' value={value?.warning} onChange={onWarningChange} />
</Grid>
<Grid item lg={2} md={2} sm={2} xs={12} className={classes.contentTextAlign}>
<Grid item lg={2} md={2} sm={2} xs={12} sx={{ textAlign: 'center' }}>
<Typography>{gettext('Alert')}</Typography>
</Grid>
<Grid item lg={2} md={2} sm={2} xs={12}>
<InputText cid={alertCid} helpid={alerthelpid} type='numeric' value={value?.alert} onChange={onAlertChange} />
</Grid>
<Grid item lg={4} md={4} sm={4} xs={12} className={classes.contentStyle}>
<Grid item lg={4} md={4} sm={4} xs={12} sx={{paddingLeft: 10 }}>
<Typography>{gettext('(in minutes)')}</Typography>
</Grid>
</Grid>

View File

@ -8,10 +8,10 @@
//////////////////////////////////////////////////////////////
import React, { useEffect, useRef, useState } from 'react';
import { styled } from '@mui/material/styles';
import PropTypes from 'prop-types';
import gettext from 'sources/gettext';
import { Box, InputAdornment } from '@mui/material';
import { makeStyles } from '@mui/styles';
import { InputText } from '../../FormComponents';
import { PgIconButton } from '../../Buttons';
import CloseIcon from '@mui/icons-material/CloseRounded';
@ -32,21 +32,19 @@ import {
replaceAll,
} from '@codemirror/search';
const useStyles = makeStyles((theme)=>({
root: {
position: 'absolute',
zIndex: 99,
right: '4px',
top: '0px',
...theme.mixins.panelBorder.all,
borderTop: 'none',
padding: '2px 4px',
width: '250px',
backgroundColor: theme.palette.background.default,
},
marginTop: {
const StyledBox = styled(Box)(({theme}) => ({
position: 'absolute',
zIndex: 99,
right: '4px',
top: '0px',
...theme.mixins.panelBorder.all,
borderTop: 'none',
padding: '2px 4px',
width: '250px',
backgroundColor: theme.palette.background.default,
'& .CodeMirror-marginTop': {
marginTop: '0.25rem',
},
}
}));
export default function FindDialog({editor, show, replace, onClose}) {
@ -56,7 +54,7 @@ export default function FindDialog({editor, show, replace, onClose}) {
const [matchCase, setMatchCase] = useState(false);
const findInputRef = useRef();
const searchQuery = useRef();
const classes = useStyles();
const search = ()=>{
if(editor) {
@ -148,7 +146,7 @@ export default function FindDialog({editor, show, replace, onClose}) {
}
return (
<Box className={classes.root} style={{visibility: show ? 'visible' : 'hidden'}} tabIndex="0" onKeyDown={onEscape}>
<StyledBox style={{visibility: show ? 'visible' : 'hidden'}} tabIndex="0" onKeyDown={onEscape}>
<InputText value={findVal}
inputRef={(ele)=>{findInputRef.current = ele;}}
onChange={(value)=>setFindVal(value)}
@ -164,12 +162,12 @@ export default function FindDialog({editor, show, replace, onClose}) {
/>
{replace &&
<InputText value={replaceVal}
className={classes.marginTop}
className='CodeMirror-marginTop'
onChange={(value)=>setReplaceVal(value)}
onKeyPress={onReplaceEnter}
/>}
<Box display="flex" className={classes.marginTop}>
<Box display="flex" className='CodeMirror-marginTop'>
<PgIconButton title={gettext('Previous')} icon={<ArrowUpwardRoundedIcon />} size="xs" noBorder onClick={onFindPrev}
style={{marginRight: '2px'}} />
<PgIconButton title={gettext('Next')} icon={<ArrowDownwardRoundedIcon />} size="xs" noBorder onClick={onFindNext}
@ -183,7 +181,7 @@ export default function FindDialog({editor, show, replace, onClose}) {
<PgIconButton title={gettext('Close')} icon={<CloseIcon />} size="xs" noBorder onClick={clearAndClose}/>
</Box>
</Box>
</Box>
</StyledBox>
);
}

View File

@ -8,35 +8,33 @@
//////////////////////////////////////////////////////////////
import React, { useEffect, useRef, useState } from 'react';
import { styled } from '@mui/material/styles';
import PropTypes from 'prop-types';
import gettext from 'sources/gettext';
import { Box, FormControl } from '@mui/material';
import { makeStyles } from '@mui/styles';
import { InputText } from '../../FormComponents';
import { PgIconButton } from '../../Buttons';
import CloseIcon from '@mui/icons-material/CloseRounded';
const useStyles = makeStyles((theme)=>({
root: {
position: 'absolute',
zIndex: 99,
right: '4px',
top: '0px',
...theme.mixins.panelBorder.all,
borderTop: 'none',
padding: '2px 4px',
width: '250px',
backgroundColor: theme.palette.background.default,
display: 'flex',
alignItems: 'center',
gap: '4px',
},
const StyledBox = styled(Box)(({theme}) => ({
position: 'absolute',
zIndex: 99,
right: '4px',
top: '0px',
...theme.mixins.panelBorder.all,
borderTop: 'none',
padding: '2px 4px',
width: '250px',
backgroundColor: theme.palette.background.default,
display: 'flex',
alignItems: 'center',
gap: '4px',
}));
export default function GotoDialog({editor, show, onClose}) {
const [gotoVal, setGotoVal] = useState('');
const inputRef = useRef();
const classes = useStyles();
useEffect(()=>{
if(show) {
@ -72,8 +70,8 @@ export default function GotoDialog({editor, show, onClose}) {
}
return (
<Box className={classes.root} style={{visibility: show ? 'visible' : 'hidden'}} tabIndex="0" onKeyDown={onEscape}>
<div style={{whiteSpace: 'nowrap'}}>Ln [,Col]</div>
<StyledBox style={{visibility: show ? 'visible' : 'hidden'}} tabIndex="0" onKeyDown={onEscape}>
<div style={{whiteSpace: 'nowrap'}}>Ln, [Col]</div>
<FormControl>
<InputText
value={gotoVal}
@ -83,7 +81,7 @@ export default function GotoDialog({editor, show, onClose}) {
/>
</FormControl>
<PgIconButton title={gettext('Close')} icon={<CloseIcon />} size="xs" noBorder onClick={onClose}/>
</Box>
</StyledBox>
);
}

View File

@ -8,10 +8,9 @@
//////////////////////////////////////////////////////////////
import React, { useCallback, useMemo, useRef, useState, useEffect } from 'react';
import { makeStyles } from '@mui/styles';
import { styled } from '@mui/material/styles';
import FileCopyRoundedIcon from '@mui/icons-material/FileCopyRounded';
import CheckRoundedIcon from '@mui/icons-material/CheckRounded';
import clsx from 'clsx';
import PropTypes from 'prop-types';
import gettext from 'sources/gettext';
@ -24,12 +23,10 @@ import CustomPropTypes from '../../custom_prop_types';
import FindDialog from './components/FindDialog';
import GotoDialog from './components/GotoDialog';
const useStyles = makeStyles(() => ({
root: {
position: 'relative',
height: '100%'
},
copyButton: {
const Root = styled('div')(() => ({
position: 'relative',
height: '100%',
'& .CodeMirror-copyButton': {
position: 'absolute',
zIndex: 99,
right: '4px',
@ -39,14 +36,14 @@ const useStyles = makeStyles(() => ({
function CopyButton({ editor }) {
const classes = useStyles();
const [isCopied, setIsCopied] = useState(false);
const revertCopiedText = useDelayedCaller(() => {
setIsCopied(false);
});
return (
<PgIconButton size="small" className={classes.copyButton} icon={isCopied ? <CheckRoundedIcon /> : <FileCopyRoundedIcon />}
<PgIconButton size="small" className='CodeMirror-copyButton' icon={isCopied ? <CheckRoundedIcon /> : <FileCopyRoundedIcon />}
title={isCopied ? gettext('Copied!') : gettext('Copy')}
onClick={() => {
copyToClipboard(editor?.getValue());
@ -63,7 +60,6 @@ CopyButton.propTypes = {
export default function CodeMirror({className, currEditor, showCopyBtn=false, customKeyMap=[], onTextSelect, ...props}) {
const classes = useStyles();
const editor = useRef();
const [[showFind, isReplace], setShowFind] = useState([false, false]);
const [showGoto, setShowGoto] = useState(false);
@ -140,12 +136,12 @@ export default function CodeMirror({className, currEditor, showCopyBtn=false, cu
return (
<div className={clsx(className, classes.root)} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
<Root className={[className].join(' ')} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} >
<Editor currEditor={currEditorWrap} customKeyMap={finalCustomKeyMap} {...props} />
{showCopy && <CopyButton editor={editor.current} />}
<FindDialog editor={editor.current} show={showFind} replace={isReplace} onClose={closeFind} />
<GotoDialog editor={editor.current} show={showGoto} onClose={closeGoto} />
</div>
</Root>
);
}

View File

@ -9,20 +9,14 @@
import gettext from 'sources/gettext';
import { Grid } from '@mui/material';
import { makeStyles } from '@mui/styles';
import React, { useMemo } from 'react';
import {InputSelect } from './FormComponents';
import PropTypes from 'prop-types';
import CustomPropTypes from '../custom_prop_types';
const useStyles = makeStyles(() => ({
preview: {
paddingTop: 10
}
}));
export default function SelectThemes({onChange, ...props}) {
const classes = useStyles();
const previewSrc = useMemo(()=>(props.options?.find((o)=>o.value==props.value)?.preview_src), [props.value]);
return (
@ -30,7 +24,7 @@ export default function SelectThemes({onChange, ...props}) {
<Grid item lg={12} md={12} sm={12} xs={12}>
<InputSelect ref={props.inputRef} onChange={onChange} {...props} />
</Grid>
<Grid item lg={12} md={12} sm={12} xs={12} className={classes.preview}>
<Grid item lg={12} md={12} sm={12} xs={12} sx={{paddingTop: 10}}>
<img className='img-fluid mx-auto d-block border' src={previewSrc} alt={gettext('Preview not available...')} />
</Grid>
</Grid>

View File

@ -1,26 +1,27 @@
import React from 'react';
import { makeStyles } from '@mui/styles';
import { styled } from '@mui/material/styles';
import PropTypes from 'prop-types';
import { isMac } from '../keyboard_shortcuts';
import _ from 'lodash';
import CustomPropTypes from '../custom_prop_types';
import gettext from 'sources/gettext';
const useStyles = makeStyles((theme)=>({
shortcutTitle: {
const Root = styled('div')(({theme}) => ({
'& .ShortcutTitle-title': {
width: '100%',
textAlign: 'center',
},
shortcut: {
'& .ShortcutTitle-shortcut': {
justifyContent: 'center',
marginTop: '0.125rem',
display: 'flex',
},
key: {
padding: '0 0.25rem',
border: `1px solid ${theme.otherVars.borderColor}`,
marginRight: '0.125rem',
borderRadius: theme.shape.borderRadius,
'& .ShortcutTitle-key': {
padding: '0 0.25rem',
border: `1px solid ${theme.otherVars.borderColor}`,
marginRight: '0.125rem',
borderRadius: theme.shape.borderRadius,
}
},
}));
@ -67,17 +68,17 @@ export function shortcutToString(shortcut, accesskey=null, asArray=false) {
/* The tooltip content to show shortcut details */
export default function ShortcutTitle({title, shortcut, accesskey}) {
const classes = useStyles();
let keys = shortcutToString(shortcut, accesskey, true);
return (
<>
<div className={classes.shortcutTitle}>{title}</div>
<div className={classes.shortcut}>
(<Root>
<div className='ShortcutTitle-title'>{title}</div>
<div className='ShortcutTitle-shortcut'>
{keys.map((key)=>{
return <div key={key} className={classes.key}>{key}</div>;
return <div key={key} className='ShortcutTitle-key'>{key}</div>;
})}
</div>
</>
</Root>)
);
}

View File

@ -9,28 +9,25 @@
import React from 'react';
import { Box } from '@mui/material';
import { makeStyles } from '@mui/styles';
import clsx from 'clsx';
import { styled } from '@mui/material/styles';
import PropTypes from 'prop-types';
import CustomPropTypes from '../custom_prop_types';
export const tabPanelStyles = makeStyles((theme)=>({
root: {
...theme.mixins.tabPanel,
},
content: {
const StyledBox = styled(Box)(({theme})=>({
...theme.mixins.tabPanel,
'& .TabPanel-content':{
height: '100%',
}
}));
/* Material UI does not have any tabpanel component, we create one for us */
export default function TabPanel({children, classNameRoot, className, value, index, ...props}) {
const classes = tabPanelStyles();
const active = value === index;
return (
<Box className={clsx(classes.root, classNameRoot)} component="div" hidden={!active} data-test="tabpanel" {...props}>
<Box className={clsx(classes.content, className)}>{children}</Box>
</Box>
<StyledBox className={[classNameRoot].join(' ')} component="div" hidden={!active} data-test="tabpanel" {...props}>
<Box className={['TabPanel-content', className].join(' ')}>{children}</Box>
</StyledBox>
);
}

View File

@ -0,0 +1,76 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2024, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import React from 'react';
import { styled } from '@mui/material/styles';
import CustomPropTypes from '../custom_prop_types';
const StyledTable = styled('table')(({theme})=>({
borderSpacing: 0,
width: '100%',
overflow: 'auto',
backgroundColor: theme.otherVars.tableBg,
border: '1px solid '+theme.otherVars.borderColor,
'& tbody td, & thead th': {
margin: 0,
padding: theme.spacing(0.5),
border: '1px solid '+theme.otherVars.borderColor,
borderBottom: 'none',
position: 'relative',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
userSelect: 'text',
maxWidth: '250px',
'&:first-of-type':{
borderLeft: 'none',
},
},
'& thead tr:first-of-type th': {
borderTop: 'none',
},
'& tbody tr:last-of-type': {
'&:hover td': {
borderBottomColor: theme.palette.primary.main,
},
'& td': {
borderBottomColor: theme.otherVars.borderColor,
}
},
'& th': {
fontWeight: theme.typography.fontWeightBold,
padding: theme.spacing(1, 0.5),
textAlign: 'left',
},
'& tbody > tr': {
'&:hover': {
backgroundColor: theme.palette.primary.light,
'& td': {
borderBottom: '1px solid '+theme.palette.primary.main,
borderTop: '1px solid '+theme.palette.primary.main,
},
'&:last-of-type td': {
borderBottomColor: theme.palette.primary.main,
},
},
},
})
);
export default function Table({children, classNameRoot, ...props}) {
return (
<StyledTable className={[classNameRoot].join(' ')} {...props}>{children}</StyledTable>
);
}
Table.propTypes = {
children: CustomPropTypes.children,
classNameRoot: CustomPropTypes.className
};

View File

@ -8,8 +8,8 @@
//////////////////////////////////////////////////////////////
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { styled } from '@mui/material/styles';
import { Box } from '@mui/material';
import { makeStyles } from '@mui/styles';
import DataGridView, { DataGridHeader } from '../SchemaView/DataGridView';
import SchemaView, { SCHEMA_STATE_ACTIONS } from '../SchemaView';
import { DefaultButton } from '../components/Buttons';
@ -18,22 +18,22 @@ import PropTypes from 'prop-types';
import CustomPropTypes from '../custom_prop_types';
import _ from 'lodash';
const useStyles = makeStyles((theme)=>({
formBorder: {
const StyledBox = styled(Box)(({theme}) => ({
'& .DataGridViewWithHeaderForm-border': {
...theme.mixins.panelBorder,
borderBottom: 0,
'& .DataGridViewWithHeaderForm-body': {
padding: '0.25rem',
'& .DataGridViewWithHeaderForm-addBtn': {
marginLeft: 'auto',
}
},
},
form: {
padding: '0.25rem',
},
addBtn: {
marginLeft: 'auto',
}
}));
export default function DataGridViewWithHeaderForm(props) {
let {containerClassName, headerSchema, headerVisible, ...otherProps} = props;
const classes = useStyles();
const headerFormData = useRef({});
const schemaRef = useRef(otherProps.schema);
const [addDisabled, setAddDisabled] = useState(true);
@ -61,10 +61,10 @@ export default function DataGridViewWithHeaderForm(props) {
headerVisible = headerVisible && evalFunc(null, headerVisible, state);
return (
<Box className={containerClassName}>
<Box className={classes.formBorder}>
<StyledBox className={containerClassName}>
<Box className='DataGridViewWithHeaderForm-border'>
{props.label && <DataGridHeader label={props.label} />}
{headerVisible && <Box className={classes.form}>
{headerVisible && <Box className='DataGridViewWithHeaderForm-body'>
<SchemaView
formType={'dialog'}
getInitData={()=>Promise.resolve({})}
@ -80,12 +80,12 @@ export default function DataGridViewWithHeaderForm(props) {
resetKey={headerFormResetKey}
/>
<Box display="flex">
<DefaultButton className={classes.addBtn} onClick={onAddClick} disabled={addDisabled}>Add</DefaultButton>
<DefaultButton className='DataGridViewWithHeaderForm-addBtn' onClick={onAddClick} disabled={addDisabled}>Add</DefaultButton>
</Box>
</Box>}
</Box>
<DataGridView {...otherProps} label="" canAdd={false}/>
</Box>
</StyledBox>
);
}

View File

@ -8,9 +8,7 @@
//////////////////////////////////////////////////////////////
import { Box, Dialog, DialogContent, DialogTitle, Paper } from '@mui/material';
import { makeStyles } from '@mui/styles';
import React, { useState, useMemo } from 'react';
import clsx from 'clsx';
import { getEpoch } from 'sources/utils';
import { DefaultButton, PgIconButton, PrimaryButton } from '../components/Buttons';
import Draggable from 'react-draggable';
@ -22,38 +20,38 @@ import HTMLReactParser from 'html-react-parser';
import CheckRoundedIcon from '@mui/icons-material/CheckRounded';
import { Rnd } from 'react-rnd';
import { ExpandDialogIcon, MinimizeDialogIcon } from '../components/ExternalIcon';
import { styled } from '@mui/material/styles';
export const ModalContext = React.createContext({});
const MIN_HEIGHT = 190;
const MIN_WIDTH = 500;
export function useModal() {
return React.useContext(ModalContext);
}
const useAlertStyles = makeStyles((theme) => ({
footer: {
const StyledBox = styled(Box)(({theme}) => ({
'& .Alert-footer': {
display: 'flex',
justifyContent: 'flex-end',
padding: '0.5rem',
...theme.mixins.panelBorder.top,
},
margin: {
'& .Alert-margin': {
marginLeft: '0.25rem',
}
},
}));
export function useModal() {
return React.useContext(ModalContext);
}
function AlertContent({ text, confirm, okLabel = gettext('OK'), cancelLabel = gettext('Cancel'), onOkClick, onCancelClick }) {
const classes = useAlertStyles();
return (
<Box display="flex" flexDirection="column" height="100%">
<StyledBox display="flex" flexDirection="column" height="100%">
<Box flexGrow="1" p={2}>{typeof (text) == 'string' ? HTMLReactParser(text) : text}</Box>
<Box className={classes.footer}>
<Box className='Alert-footer'>
{confirm &&
<DefaultButton startIcon={<CloseIcon />} onClick={onCancelClick} >{cancelLabel}</DefaultButton>
}
<PrimaryButton className={classes.margin} startIcon={<CheckRoundedIcon />} onClick={onOkClick} autoFocus={true} >{okLabel}</PrimaryButton>
<PrimaryButton className='Alert-margin' startIcon={<CheckRoundedIcon />} onClick={onOkClick} autoFocus={true} >{okLabel}</PrimaryButton>
</Box>
</Box>
</StyledBox>
);
}
AlertContent.propTypes = {
@ -143,17 +141,17 @@ ModalProvider.propTypes = {
children: CustomPropTypes.children,
};
const dialogStyle = makeStyles((theme) => ({
dialog: {
const StyledRnd = styled(Rnd)(({theme}) => ({
'&.Dialog-content': {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
border: '1px solid ' + theme.otherVars.inputBorderColor,
borderRadius: theme.shape.borderRadius,
},
fullScreen: {
'&.Dialog-fullScreen': {
transform: 'none !important'
}
},
}));
@ -166,7 +164,6 @@ function setEnableResizing(props, resizeable) {
}
function PaperComponent({minHeight, minWidth, ...props}) {
let classes = dialogStyle();
let [dialogPosition, setDialogPosition] = useState(null);
let resizeable = checkIsResizable(props);
@ -179,9 +176,9 @@ function PaperComponent({minHeight, minWidth, ...props}) {
return (
props.isresizeable == 'true' ?
<Rnd
<StyledRnd
size={props.isfullscreen == 'true' && { width: '100%', height: '100%' }}
className={clsx(classes.dialog, props.isfullscreen == 'true' ? classes.fullScreen : '')}
className={'Dialog-content ' + ( props.isfullscreen == 'true' ? 'Dialog-fullScreen' : '')}
default={{
x: x_position,
y: y_position,
@ -210,7 +207,7 @@ function PaperComponent({minHeight, minWidth, ...props}) {
dragHandleClassName="modal-drag-area"
>
<Paper {...props} style={{ width: '100%', height: '100%', maxHeight: '100%', maxWidth: '100%' }} />
</Rnd>
</StyledRnd>
:
<Draggable cancel={'[class*="MuiDialogContent-root"]'}>
<Paper {...props} style={{ minWidth: '600px' }} />
@ -227,19 +224,15 @@ PaperComponent.propTypes = {
minHeight: PropTypes.number,
};
export const useModalStyles = makeStyles((theme) => ({
container: {
const StyleDialog = styled(Dialog)(({theme}) => ({
'& .Modal-container': {
backgroundColor: theme.palette.background.default
},
titleBar: {
'& .Modal-titleBar': {
display: 'flex',
flexGrow: 1
},
title: {
flexGrow: 1
},
icon: {
'& .Modal-icon': {
fill: 'currentColor',
width: '1em',
height: '1em',
@ -249,16 +242,13 @@ export const useModalStyles = makeStyles((theme) => ({
flexShrink: 0,
userSelect: 'none',
},
footer: {
'& .Modal-footer': {
display: 'flex',
justifyContent: 'flex-end',
padding: '0.5rem',
...theme.mixins.panelBorder?.top,
},
margin: {
marginLeft: '0.25rem',
},
iconButtonStyle: {
'& .Modal-iconButtonStyle': {
marginLeft: 'auto',
marginRight: '4px'
},
@ -266,7 +256,6 @@ export const useModalStyles = makeStyles((theme) => ({
function ModalContainer({ id, title, content, dialogHeight, dialogWidth, onClose, fullScreen = false, isFullWidth = false, showFullScreen = false, isResizeable = false, minHeight = MIN_HEIGHT, minWidth = MIN_WIDTH, showTitle=true }) {
let useModalRef = useModal();
const classes = useModalStyles();
let closeModal = (_e, reason) => {
if(reason == 'backdropClick' && showTitle) {
return;
@ -279,7 +268,7 @@ function ModalContainer({ id, title, content, dialogHeight, dialogWidth, onClose
const [isFullScreen, setIsFullScreen] = useState(fullScreen);
return (
<Dialog
<StyleDialog
open={true}
onClose={closeModal}
PaperComponent={PaperComponent}
@ -290,15 +279,15 @@ function ModalContainer({ id, title, content, dialogHeight, dialogWidth, onClose
>
{ showTitle &&
<DialogTitle className='modal-drag-area'>
<Box className={classes.titleBar}>
<Box className={classes.title} marginRight="0.25rem" >{title}</Box>
<Box className='Modal-titleBar'>
<Box sx={{ marginRight:'0.25rem', flexGrow: 1}}>{title}</Box>
{
showFullScreen && !isFullScreen &&
<Box className={classes.iconButtonStyle}><PgIconButton title={gettext('Maximize')} icon={<ExpandDialogIcon className={classes.icon} />} size="xs" noBorder onClick={() => { setIsFullScreen(!isFullScreen); }} /></Box>
<Box className='Modal-iconButtonStyle'><PgIconButton title={gettext('Maximize')} icon={<ExpandDialogIcon className='Modal-icon' />} size="xs" noBorder onClick={() => { setIsFullScreen(!isFullScreen); }} /></Box>
}
{
showFullScreen && isFullScreen &&
<Box className={classes.iconButtonStyle}><PgIconButton title={gettext('Minimize')} icon={<MinimizeDialogIcon className={classes.icon} />} size="xs" noBorder onClick={() => { setIsFullScreen(!isFullScreen); }} /></Box>
<Box className='Modal-iconButtonStyle'><PgIconButton title={gettext('Minimize')} icon={<MinimizeDialogIcon className='Modal-icon' />} size="xs" noBorder onClick={() => { setIsFullScreen(!isFullScreen); }} /></Box>
}
<Box marginLeft="auto"><PgIconButton title={gettext('Close')} icon={<CloseIcon />} size="xs" noBorder onClick={closeModal} /></Box>
@ -308,7 +297,7 @@ function ModalContainer({ id, title, content, dialogHeight, dialogWidth, onClose
<DialogContent height="100%">
{useMemo(()=>{ return content(closeModal); }, [])}
</DialogContent>
</Dialog>
</StyleDialog>
);
}
ModalContainer.propTypes = {

View File

@ -8,7 +8,7 @@
//////////////////////////////////////////////////////////////
import { SnackbarProvider, SnackbarContent } from 'notistack';
import { makeStyles } from '@mui/styles';
import { styled } from '@mui/material/styles';
import {Box} from '@mui/material';
import CloseIcon from '@mui/icons-material/CloseRounded';
import { DefaultButton, PrimaryButton } from '../components/Buttons';
@ -23,6 +23,19 @@ import _ from 'lodash';
import { useModal } from './ModalProvider';
import { parseApiError } from '../api_instance';
const Root = styled('div')(({theme}) => ({
'& .Notifier-footer': {
display: 'flex',
justifyContent: 'flex-end',
padding: '0.5rem',
...theme.mixins.panelBorder.top,
'& .Notifier-margin': {
marginLeft: '0.25rem',
}
},
}));
const AUTO_HIDE_DURATION = 3000; // In milliseconds
export const FinalNotifyContent = React.forwardRef(({children}, ref) => {
@ -33,27 +46,15 @@ FinalNotifyContent.propTypes = {
children: CustomPropTypes.children,
};
const useModalStyles = makeStyles((theme)=>({
footer: {
display: 'flex',
justifyContent: 'flex-end',
padding: '0.5rem',
...theme.mixins.panelBorder.top,
},
margin: {
marginLeft: '0.25rem',
},
}));
function AlertContent({text, confirm, okLabel=gettext('OK'), cancelLabel=gettext('Cancel'), onOkClick, onCancelClick}) {
const classes = useModalStyles();
return (
<Box display="flex" flexDirection="column" height="100%">
<Box flexGrow="1" p={2}>{HTMLReactParser(text)}</Box>
<Box className={classes.footer}>
<Box className='Notifier-footer'>
{confirm &&
<DefaultButton startIcon={<CloseIcon />} onClick={onCancelClick} >{cancelLabel}</DefaultButton>
}
<PrimaryButton className={classes.margin} startIcon={<CheckRoundedIcon />} onClick={onOkClick} autoFocus={true} >{okLabel}</PrimaryButton>
<PrimaryButton className='Notifier-margin' startIcon={<CheckRoundedIcon />} onClick={onOkClick} autoFocus={true} >{okLabel}</PrimaryButton>
</Box>
</Box>
);
@ -197,23 +198,25 @@ export function NotifierProvider({ pgAdmin, pgWindow, getInstance, children, onR
// if open in a window, then create your own Snackbar
if(window.self == window.top) {
return (
<SnackbarProvider
maxSnack={30}
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
ref={(obj)=>{
pgAdmin.Browser.notifier = new Notifier(modal, new SnackbarNotifier(obj));
getInstance?.(pgAdmin.Browser.notifier);
onReady?.();
}}
>
{children}
</SnackbarProvider>
<Root>
<SnackbarProvider
maxSnack={30}
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
ref={(obj)=>{
pgAdmin.Browser.notifier = new Notifier(modal, new SnackbarNotifier(obj));
getInstance?.(pgAdmin.Browser.notifier);
onReady?.();
}}
>
{children}
</SnackbarProvider>
</Root>
);
}
return (
<>
(<Root>
{children}
</>
</Root>)
);
}

View File

@ -7,8 +7,7 @@
//
//////////////////////////////////////////////////////////////
import React from 'react';
import { makeStyles } from '@mui/styles';
import clsx from 'clsx';
import { styled } from '@mui/material/styles';
import FastForwardIcon from '@mui/icons-material/FastForward';
import FastRewindIcon from '@mui/icons-material/FastRewind';
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
@ -21,119 +20,110 @@ import { Box } from '@mui/material';
import gettext from 'sources/gettext';
import Loader from 'sources/components/Loader';
const StyledBox = styled(Box)(({theme}) => ({
height: '100%',
display: 'flex',
flexDirection: 'column',
backgroundColor: theme.palette.background.default + ' !important',
'& .Wizard-root': {
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
minHeight: 0,
const useStyles = makeStyles((theme) =>
({
wizardBase: {
height: '100%',
display: 'flex',
flexDirection: 'column',
backgroundColor: theme.palette.background.default
},
root: {
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
minHeight: 0
},
rightPanel: {
position: 'relative',
display: 'flex',
flexBasis: '75%',
overflow: 'auto',
height: '100%',
minHeight: '100px'
},
leftPanel: {
display: 'flex',
flexBasis: '25%',
flexDirection: 'column',
alignItems: 'flex-start',
borderRight: '1px solid',
...theme.mixins.panelBorder.right,
},
label: {
display: 'inline-block',
position: 'relative',
paddingLeft: '0.5rem',
flexBasis: '70%'
},
labelArrow: {
display: 'inline-block',
position: 'relative',
flexBasis: '30%'
},
labelDone: {
display: 'inline-block',
position: 'relative',
flexBasis: '30%',
color: theme.otherVars.activeStepBg + ' !important',
padding: '4px'
},
stepLabel: {
padding: '1em',
paddingRight: 0
},
active: {
fontWeight: 600
},
activeIndex: {
backgroundColor: theme.otherVars.activeStepBg + ' !important',
color: theme.otherVars.activeStepFg + ' !important'
},
stepIndex: {
padding: '0.5em 1em ',
height: '2.5em',
borderRadius: '2em',
backgroundColor: theme.otherVars.stepBg,
color: theme.otherVars.stepFg,
display: 'inline-block',
flex: 0.5,
},
wizard: {
'& .Wizard-body': {
width: '100%',
height: '100%',
minHeight: 100,
display: 'flex',
flexWrap: 'wrap',
'& .Wizard-rightPanel': {
position: 'relative',
display: 'flex',
flexBasis: '75%',
overflow: 'auto',
height: '100%',
minHeight: '100px',
'& .Wizard-stepDefaultStyle': {
width: '100%',
height: '100%',
padding: '8px',
display: 'flex',
flexDirection: 'column',
},
'& .Wizard-hidden': {
display: 'none',
}
},
'& .Wizard-active': {
fontWeight: 600
},
'& .Wizard-leftPanel': {
display: 'flex',
flexBasis: '25%',
flexDirection: 'column',
alignItems: 'flex-start',
borderRight: '1px solid',
...theme.mixins.panelBorder.right,
'& .Wizard-active': {
fontWeight: 600
},
'& .Wizard-stepLabel': {
padding: '1em',
paddingRight: 0,
'& .Wizard-stepIndex': {
padding: '0.5em 1em ',
height: '2.5em',
borderRadius: '2em',
backgroundColor: theme.otherVars.stepBg,
color: theme.otherVars.stepFg,
display: 'inline-block',
flex: 0.5,
'& .Wizard-activeIndex': {
backgroundColor: theme.otherVars.activeStepBg + ' !important',
color: theme.otherVars.activeStepFg + ' !important'
},
},
'& .Wizard-label': {
display: 'inline-block',
position: 'relative',
paddingLeft: '0.5rem',
flexBasis: '70%'
},
'& .Wizard-labelArrow': {
display: 'inline-block',
position: 'relative',
flexBasis: '30%'
},
'& .Wizard-labelDone': {
display: 'inline-block',
position: 'relative',
flexBasis: '30%',
color: theme.otherVars.activeStepBg + ' !important',
padding: '4px'
},
},
},
},
wizardFooter: {
borderTop: `1px solid ${theme.otherVars.inputBorderColor} !important`,
padding: '0.5rem',
display: 'flex',
width: '100%',
background: theme.otherVars.headerBg,
zIndex: 999,
},
backButton: {
marginRight: theme.spacing(1),
},
instructions: {
marginTop: theme.spacing(1),
marginBottom: theme.spacing(1),
},
actionBtn: {
},
'& .Wizard-footer': {
borderTop: `1px solid ${theme.otherVars.inputBorderColor} !important`,
padding: '0.5rem',
display: 'flex',
width: '100%',
background: theme.otherVars.headerBg,
zIndex: 999,
'& .Wizard-actionBtn': {
alignItems: 'flex-start',
'& .Wizard-buttonMargin': {
marginLeft: '0.5em'
},
},
buttonMargin: {
marginLeft: '0.5em'
},
stepDefaultStyle: {
width: '100%',
height: '100%',
padding: '8px',
display: 'flex',
flexDirection: 'column',
},
hidden: {
display: 'none',
}
}),
);
},
}));
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);
@ -183,25 +173,25 @@ function Wizard({ stepList, onStepChange, onSave, className, ...props }) {
return (
<Box className={classes.wizardBase}>
<div className={clsx(classes.root, props?.rootClass)}>
<div className={clsx(classes.wizard, className)}>
<Box className={classes.leftPanel}>
<StyledBox>
<div className={'Wizard-root ' + (props?.rootClass ? props.rootClass : '') }>
<div className={'Wizard-body ' + className}>
<Box className='Wizard-leftPanel'>
{steps.map((label, index) => (
<Box key={label} className={clsx(classes.stepLabel, index === activeStep ? classes.active : '')}>
<Box className={clsx(classes.stepIndex, index === activeStep ? classes.activeIndex : '')}>{index + 1}</Box>
<Box className={classes.label}>{label} </Box>
<Box className={classes.labelArrow}>{index === activeStep ? <ChevronRightIcon /> : null}</Box>
<Box className={classes.labelDone}>{index < activeStep ? <DoneIcon />: null}</Box>
<Box key={label} className={'Wizard-stepLabel ' + (index === activeStep ? 'Wizard-active' : '')}>
<Box className={'Wizard-stepIndex ' + (index === activeStep ? 'Wizard-activeIndex' : '')}>{index + 1}</Box>
<Box className='Wizard-label'>{label} </Box>
<Box className='Wizard-labelArrow'>{index === activeStep ? <ChevronRightIcon /> : null}</Box>
<Box className='Wizard-labelDone'>{index < activeStep ? <DoneIcon />: null}</Box>
</Box>
))}
</Box>
<div className={clsx(classes.rightPanel, props.stepPanelCss)}>
<div className={'Wizard-rightPanel ' + props.stepPanelCss}>
<Loader message={props?.loaderText} />
{
React.Children.map(props.children, (child) => {
return (
<div className={clsx(classes.stepDefaultStyle, child.props.className, (child.props.stepId !== activeStep ? classes.hidden : ''))}>
<div className={'Wizard-stepDefaultStyle ' + child.props.className + ' ' +(child.props.stepId !== activeStep ? 'Wizard-hidden' : '')}>
{child}
</div>
);
@ -210,24 +200,24 @@ function Wizard({ stepList, onStepChange, onSave, className, ...props }) {
</div>
</div>
</div>
<div className={classes.wizardFooter}>
<div className='Wizard-footer'>
<Box>
<PgIconButton data-test="dialog-help" onClick={() => props.onHelp()} icon={<HelpIcon />} title="Help for this dialog."
disabled={props.disableDialogHelp} />
</Box>
<Box className={classes.actionBtn} marginLeft="auto">
<DefaultButton onClick={handleBack} disabled={activeStep === 0} className={classes.buttonMargin} startIcon={<FastRewindIcon />}>
<Box className='Wizard-actionBtn' marginLeft="auto">
<DefaultButton onClick={handleBack} disabled={activeStep === 0} className='Wizard-buttonMargin' startIcon={<FastRewindIcon />}>
{gettext('Back')}
</DefaultButton>
<DefaultButton onClick={() => handleNext()} className={classes.buttonMargin} startIcon={<FastForwardIcon />} disabled={activeStep == steps.length - 1 || disableNext}>
<DefaultButton onClick={() => handleNext()} className='Wizard-buttonMargin' startIcon={<FastForwardIcon />} disabled={activeStep == steps.length - 1 || disableNext}>
{gettext('Next')}
</DefaultButton>
<PrimaryButton className={classes.buttonMargin} startIcon={<CheckIcon />} disabled={activeStep !== (steps.length - 1) } onClick={onSave}>
<PrimaryButton className='Wizard-buttonMargin' startIcon={<CheckIcon />} disabled={activeStep !== (steps.length - 1) } onClick={onSave}>
{gettext('Finish')}
</PrimaryButton>
</Box>
</div>
</Box>
</StyledBox>
);
}

View File

@ -9,11 +9,12 @@
import PropTypes from 'prop-types';
import { styled } from '@mui/material/styles';
import React, { useEffect, useRef } from 'react';
import DeleteSweepIcon from '@mui/icons-material/DeleteSweep';
import { Box } from '@mui/material';
import { makeStyles } from '@mui/styles';
import BugReportRoundedIcon from '@mui/icons-material/BugReportRounded';
import CloseSharpIcon from '@mui/icons-material/CloseSharp';
@ -32,44 +33,40 @@ import { BROWSER_PANELS } from '../../../../../browser/static/js/constants';
import usePreferences from '../../../../../preferences/static/js/store';
const useStyles = makeStyles((theme) =>
({
root: {
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
height: '100%',
backgroundColor: theme.palette.background.default,
overflow: 'hidden',
},
body: {
display: 'flex',
flexDirection: 'column',
height: '100%',
minHeight: 0,
},
actionBtn: {
const StyledBox = styled(Box)(({theme}) => ({
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
height: '100%',
backgroundColor: theme.palette.background.default,
overflow: 'hidden',
'& .DebuggerArgument-body': {
display: 'flex',
flexDirection: 'column',
height: '100%',
minHeight: 0,
},
'& .DebuggerArgument-footer': {
borderTop: `1px solid ${theme.otherVars.inputBorderColor} !important`,
padding: '0.5rem',
display: 'flex',
width: '100%',
background: theme.otherVars.headerBg,
'& .DebuggerArgument-actionBtn': {
alignItems: 'flex-start',
'& .DebuggerArgument-buttonMargin': {
marginLeft: '0.5em'
},
'& .DebuggerArgument-debugBtn': {
fontSize: '1.12rem !important',
},
},
buttonMargin: {
marginLeft: '0.5em'
},
debugBtn: {
fontSize: '1.12rem !important',
},
footer: {
borderTop: `1px solid ${theme.otherVars.inputBorderColor} !important`,
padding: '0.5rem',
display: 'flex',
width: '100%',
background: theme.otherVars.headerBg,
}
}),
);
}
}));
export default function DebuggerArgumentComponent({ debuggerInfo, restartDebug, isEdbProc, transId, pgTreeInfo, pgData, ...props }) {
const classes = useStyles();
const debuggerArgsSchema = useRef(new DebuggerArgumentSchema());
const api = getApiInstance();
const debuggerArgsData = useRef([]);
@ -808,8 +805,8 @@ export default function DebuggerArgumentComponent({ debuggerInfo, restartDebug,
}
return (
<Box className={classes.root}>
<Box className={classes.body}>
<StyledBox>
<Box className='DebuggerArgument-body'>
{
loadArgs > 0 &&
<>
@ -852,25 +849,24 @@ export default function DebuggerArgumentComponent({ debuggerInfo, restartDebug,
</>
}
</Box>
<Box className={classes.footer}>
<Box className='DebuggerArgument-footer'>
<Box>
<DefaultButton className={classes.buttonMargin} onClick={() => { clearArgs(); }} startIcon={<DeleteSweepIcon onClick={() => { clearArgs(); }} />}>
<DefaultButton className='DebuggerArgument-buttonMargin' onClick={() => { clearArgs(); }} startIcon={<DeleteSweepIcon onClick={() => { clearArgs(); }} />}>
{gettext('Clear All')}
</DefaultButton>
</Box>
<Box className={classes.actionBtn} marginLeft="auto">
<DefaultButton className={classes.buttonMargin} onClick={() => { props.closeModal(); }} startIcon={<CloseSharpIcon onClick={() => { props.closeModal(); }} />}>
<Box className='DebuggerArgument-actionBtn' marginLeft="auto">
<DefaultButton className='DebuggerArgument-buttonMargin' onClick={() => { props.closeModal(); }} startIcon={<CloseSharpIcon onClick={() => { props.closeModal(); }} />}>
{gettext('Cancel')}
</DefaultButton>
<PrimaryButton className={classes.buttonMargin} startIcon={<BugReportRoundedIcon className={classes.debugBtn} />}
<PrimaryButton className='DebuggerArgument-buttonMargin' startIcon={<BugReportRoundedIcon className='DebuggerArgument-debugBtn' />}
disabled={isDisableDebug}
onClick={() => { startDebugging(); }}>
{gettext('Debug')}
</PrimaryButton>
</Box>
</Box>
</Box>
</StyledBox>
);
}

View File

@ -7,7 +7,7 @@
//
//////////////////////////////////////////////////////////////
import { makeStyles } from '@mui/styles';
import { styled } from '@mui/material/styles';
import PropTypes from 'prop-types';
import React, { useContext, useEffect, useMemo } from 'react';
@ -23,14 +23,13 @@ import { usePgAdmin } from '../../../../../static/js/BrowserComponent';
import { isShortcutValue, toCodeMirrorKey } from '../../../../../static/js/utils';
const useStyles = makeStyles(() => ({
sql: {
const StyledCodeMirror = styled(CodeMirror)(()=>({
'&.Query-sql': {
height: '100%',
}
}));
export default function DebuggerEditor({ getEditor, params }) {
const classes = useStyles();
const editor = React.useRef();
const eventBus = useContext(DebuggerEventsContext);
const pgAdmin = usePgAdmin();
@ -97,7 +96,7 @@ export default function DebuggerEditor({ getEditor, params }) {
);
return (
<CodeMirror
<StyledCodeMirror
currEditor={(obj) => {
editor.current = obj;
}}
@ -105,7 +104,7 @@ export default function DebuggerEditor({ getEditor, params }) {
onBreakPointChange={(line, on)=>{
setBreakpoint(line, on ? 1 : 0);
}}
className={classes.sql}
className='Query-sql'
readonly={true}
customKeyMap={shortcutOverrideKeys}
breakpoint

View File

@ -1,34 +1,24 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2024, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import { makeStyles } from '@mui/styles';
import { styled } from '@mui/material/styles';
import React from 'react';
import { DebuggerEventsContext } from './DebuggerComponent';
import { DEBUGGER_EVENTS } from '../DebuggerConstants';
const useStyles = makeStyles((theme)=>({
root: {
whiteSpace: 'pre-wrap',
fontFamily: '"Source Code Pro", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
padding: '5px 10px',
overflow: 'auto',
height: '100%',
fontSize: '12px',
userSelect: 'text',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
...theme.mixins.fontSourceCode,
}
const StyledDiv = styled('div')(({theme}) => ({
whiteSpace: 'pre-wrap',
fontFamily: '"Source Code Pro", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
padding: '5px 10px',
overflow: 'auto',
height: '100%',
fontSize: '12px',
userSelect: 'text',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
...theme.mixins.fontSourceCode,
}));
export default function DebuggerMessages() {
const classes = useStyles();
const [messageText, setMessageText] = React.useState('');
const eventBus = React.useContext(DebuggerEventsContext);
React.useEffect(()=>{
@ -41,7 +31,5 @@ export default function DebuggerMessages() {
});
});
}, []);
return (
<div className={classes.root} tabIndex="0" id='debugger-msg'>{messageText}</div>
);
return <StyledDiv tabIndex="0" id='debugger-msg'>{messageText}</StyledDiv>;
}

View File

@ -8,12 +8,11 @@
//////////////////////////////////////////////////////////////
import _ from 'lodash';
import clsx from 'clsx';
import { styled } from '@mui/material/styles';
import PropTypes from 'prop-types';
import React, { useCallback, useState } from 'react';
import { makeStyles } from '@mui/styles';
import Paper from '@mui/material/Paper';
import TableContainer from '@mui/material/TableContainer';
@ -21,32 +20,24 @@ import gettext from 'sources/gettext';
import { DebuggerEventsContext } from './DebuggerComponent';
import { DEBUGGER_EVENTS } from '../DebuggerConstants';
import { commonTableStyles } from '../../../../../static/js/Theme';
import { InputText, InputDateTimePicker } from '../../../../../static/js/components/FormComponents';
import Table from '../../../../../static/js/components/Table';
const useStyles = makeStyles(() => ({
table: {
minWidth: 650,
const StyledPaper = styled(Paper)(() => ({
flexGrow: 1,
minHeight: 0,
overflow: 'auto',
maxHeight: '100%',
'& .LocalVariablesAndParams-container': {
maxHeight: '100%',
'& .LocalVariablesAndParams-cell': {
textAlign: 'center'
}
},
summaryContainer: {
flexGrow: 1,
minHeight: 0,
overflow: 'auto',
maxHeight: '100%'
},
container: {
maxHeight: '100%'
},
cell: {
textAlign: 'center'
}
}));
export function LocalVariablesAndParams({ type }) {
const classes = useStyles();
const tableClasses = commonTableStyles();
const eventBus = React.useContext(DebuggerEventsContext);
const [variablesData, setVariablesData] = useState([]);
const preValue = React.useRef({});
@ -104,9 +95,9 @@ export function LocalVariablesAndParams({ type }) {
};
return (
<Paper variant="outlined" elevation={0} className={classes.summaryContainer}>
<TableContainer className={classes.container}>
<table className={clsx(tableClasses.table)} aria-label="sticky table">
<StyledPaper variant="outlined" elevation={0}>
<TableContainer className='LocalVariablesAndParams-container'>
<Table aria-label="sticky table">
<thead>
<tr>
<th>{gettext('Name')}</th>
@ -151,15 +142,15 @@ export function LocalVariablesAndParams({ type }) {
))}
{
variablesData.length == 0 &&
<tr key={_.uniqueId('c')} className={classes.cell}>
<tr key={_.uniqueId('c')} className='LocalVariablesAndParams-cell'>
<td colSpan={3} >{gettext('No data found')}</td>
</tr>
}
</tbody>
</table>
</Table>
</TableContainer>
</Paper>
</StyledPaper>
);
}

View File

@ -7,32 +7,23 @@
//
//////////////////////////////////////////////////////////////
import _ from 'lodash';
import clsx from 'clsx';
import { styled } from '@mui/material/styles';
import React, { useState } from 'react';
import { makeStyles } from '@mui/styles';
import Paper from '@mui/material/Paper';
import { DebuggerEventsContext } from './DebuggerComponent';
import { DEBUGGER_EVENTS } from '../DebuggerConstants';
import { commonTableStyles } from '../../../../../static/js/Theme';
import Table from '../../../../../static/js/components/Table';
const useStyles = makeStyles(() => ({
table: {
minWidth: 650,
},
summaryContainer: {
flexGrow: 1,
minHeight: 0,
overflow: 'auto',
}
const StyledPaper = styled(Paper)(() => ({
flexGrow: 1,
minHeight: 0,
overflow: 'auto',
}));
export function Results() {
const classes = useStyles();
const tableClasses = commonTableStyles();
const eventBus = React.useContext(DebuggerEventsContext);
const [resultData, setResultData] = useState([]);
const [columns, setColumns] = useState([]);
@ -43,8 +34,8 @@ export function Results() {
});
}, []);
return (
<Paper variant="outlined" elevation={0} className={classes.summaryContainer}>
<table className={clsx(tableClasses.table)}>
<StyledPaper variant="outlined" elevation={0}>
<Table>
<thead>
<tr key={_.uniqueId('c')}>
{
@ -65,7 +56,7 @@ export function Results() {
</tr>
))}
</tbody>
</table>
</Paper>
</Table>
</StyledPaper>
);
}

View File

@ -8,39 +8,31 @@
//////////////////////////////////////////////////////////////
import _ from 'lodash';
import clsx from 'clsx';
import { styled } from '@mui/material/styles';
import gettext from 'sources/gettext';
import React, { useState } from 'react';
import { makeStyles } from '@mui/styles';
import TableContainer from '@mui/material/TableContainer';
import Paper from '@mui/material/Paper';
import { DebuggerEventsContext } from './DebuggerComponent';
import { DEBUGGER_EVENTS } from '../DebuggerConstants';
import { commonTableStyles } from '../../../../../static/js/Theme';
import { InputText } from '../../../../../static/js/components/FormComponents';
import Table from '../../../../../static/js/components/Table';
const useStyles = makeStyles(() => ({
table: {
minWidth: 650,
},
summaryContainer: {
flexGrow: 1,
minHeight: 0,
overflow: 'auto',
maxHeight: '100%'
},
container: {
const StyledPaper = styled(Paper)(() => ({
flexGrow: 1,
minHeight: 0,
overflow: 'auto',
maxHeight: '100%',
'& .Stack-container': {
maxHeight: '100%'
}
}));
export function Stack() {
const classes = useStyles();
const tableClasses = commonTableStyles();
const eventBus = React.useContext(DebuggerEventsContext);
const [stackData, setStackData] = useState([]);
const [disableFrameSelection, setDisableFrameSelection] = useState(false);
@ -54,9 +46,9 @@ export function Stack() {
});
}, []);
return (
<Paper variant="outlined" elevation={0} className={classes.summaryContainer}>
<TableContainer className={classes.container}>
<table className={clsx(tableClasses.table)} aria-label="sticky table">
<StyledPaper variant="outlined" elevation={0}>
<TableContainer className='Stack-container'>
<Table aria-label="sticky table">
<thead>
<tr>
<th>{gettext('Name')}</th>
@ -77,8 +69,8 @@ export function Stack() {
</tr>
))}
</tbody>
</table>
</Table>
</TableContainer>
</Paper>
</StyledPaper>
);
}

View File

@ -9,8 +9,9 @@
import React, { useCallback, useContext, useEffect, useState } from 'react';
import { styled } from '@mui/material/styles';
import { Box } from '@mui/material';
import { makeStyles } from '@mui/styles';
import FormatIndentIncreaseIcon from '@mui/icons-material/FormatIndentIncrease';
import FormatIndentDecreaseIcon from '@mui/icons-material/FormatIndentDecrease';
import PlayCircleFilledWhiteIcon from '@mui/icons-material/PlayCircleFilledWhite';
@ -28,20 +29,20 @@ import { DebuggerContext, DebuggerEventsContext } from './DebuggerComponent';
import { DEBUGGER_EVENTS, MENUS } from '../DebuggerConstants';
import { useKeyboardShortcuts } from '../../../../../static/js/custom_hooks';
const useStyles = makeStyles((theme) => ({
root: {
padding: '2px 4px',
display: 'flex',
alignItems: 'center',
gap: '4px',
backgroundColor: theme.otherVars.editorToolbarBg,
flexWrap: 'wrap',
...theme.mixins.panelBorder.bottom,
},
const StyledBox = styled(Box)(({theme}) => ({
padding: '2px 4px',
display: 'flex',
alignItems: 'center',
gap: '4px',
backgroundColor: theme.otherVars.editorToolbarBg,
flexWrap: 'wrap',
...theme.mixins.panelBorder.bottom,
}));
export function ToolBar() {
const classes = useStyles();
const debuggerCtx = useContext(DebuggerContext);
const eventBus = useContext(DebuggerEventsContext);
let preferences = debuggerCtx.preferences.debugger;
@ -161,7 +162,7 @@ export function ToolBar() {
], debuggerCtx.containerRef);
return (
<Box className={classes.root}>
<StyledBox>
<PgButtonGroup size="small">
<PgIconButton data-test='step-in' title={gettext('Step into')} disabled={buttonsDisabled[MENUS.STEPINTO]} icon={<FormatIndentIncreaseIcon />} onClick={() => { stepInTODebugger(); }}
shortcut={preferences?.btn_step_into} />
@ -189,6 +190,6 @@ export function ToolBar() {
<PgIconButton title={gettext('Reset layout')} icon={<RotateLeftRoundedIcon />}
onClick={onResetLayout} />
</PgButtonGroup>
</Box>
</StyledBox>
);
}

View File

@ -8,13 +8,30 @@
//////////////////////////////////////////////////////////////
import React, { useMemo } from 'react';
import { styled } from '@mui/material/styles';
import gettext from 'sources/gettext';
import PropTypes from 'prop-types';
import { DefaultButton, PgButtonGroup, PgIconButton } from '../../../../../../static/js/components/Buttons';
import { Box, Tooltip, CircularProgress } from '@mui/material';
import { makeStyles } from '@mui/styles';
import { ConnectedIcon, DisonnectedIcon } from '../../../../../../static/js/components/ExternalIcon';
const StyledBox = styled(Box)(({theme}) => ({
padding: '2px 4px',
display: 'flex',
alignItems: 'center',
gap: '4px',
backgroundColor: theme.otherVars.editorToolbarBg,
flexWrap: 'wrap',
'& .Status-connectionButton': {
display: 'flex',
width: '450px',
backgroundColor: theme.palette.default.main,
color: theme.palette.default.contrastText,
border: '1px solid ' + theme.palette.default.borderColor,
justifyContent: 'flex-start',
}
}));
export const STATUS = {
CONNECTED: 1,
DISCONNECTED: 2,
@ -36,28 +53,9 @@ ConnectionStatusIcon.propTypes = {
status: PropTypes.oneOf(Object.values(STATUS)).isRequired,
};
const useStyles = makeStyles((theme)=>({
root: {
padding: '2px 4px',
display: 'flex',
alignItems: 'center',
gap: '4px',
backgroundColor: theme.otherVars.editorToolbarBg,
flexWrap: 'wrap',
},
connectionButton: {
display: 'flex',
width: '450px',
backgroundColor: theme.palette.default.main,
color: theme.palette.default.contrastText,
border: '1px solid ' + theme.palette.default.borderColor,
justifyContent: 'flex-start',
},
}));
/* The connection bar component */
export default function ConnectionBar({status, bgcolor, fgcolor, title}) {
const classes = useStyles();
const connTitle = useMemo(()=>{
if(status == STATUS.CONNECTED) {
return gettext('Connected');
@ -70,14 +68,14 @@ export default function ConnectionBar({status, bgcolor, fgcolor, title}) {
}
}, [status]);
return (
<Box className={classes.root}>
<StyledBox>
<PgButtonGroup size="small">
<PgIconButton
title={connTitle}
icon={<ConnectionStatusIcon status={status} />}
data-test="btn-conn-status"
/>
<DefaultButton className={classes.connectionButton} style={{backgroundColor: bgcolor, color: fgcolor}} data-test="btn-conn-title">
<DefaultButton className='Status-connectionButton' style={{backgroundColor: bgcolor, color: fgcolor}} data-test="btn-conn-title">
<Tooltip title={title}>
<Box display="flex" width="100%">
<Box textOverflow="ellipsis" overflow="hidden" marginRight="auto">
@ -89,7 +87,7 @@ export default function ConnectionBar({status, bgcolor, fgcolor, title}) {
</Tooltip>
</DefaultButton>
</PgButtonGroup>
</Box>
</StyledBox>
);
}

View File

@ -27,7 +27,6 @@ import ConfirmSaveContent from '../../../../../../static/js/Dialogs/ConfirmSaveC
import Loader from '../../../../../../static/js/components/Loader';
import { MainToolBar } from './MainToolBar';
import { Box } from '@mui/material';
import { withStyles } from '@mui/styles';
import EventBus from '../../../../../../static/js/helpers/EventBus';
import { ERD_EVENTS } from '../ERDConstants';
import getApiInstance, { callFetch, parseApiError } from '../../../../../../static/js/api_instance';
@ -35,6 +34,7 @@ import { openSocket, socketApiGet } from '../../../../../../static/js/socket_ins
import { LAYOUT_EVENTS } from '../../../../../../static/js/helpers/Layout';
import usePreferences from '../../../../../../preferences/static/js/store';
import pgAdmin from 'sources/pgadmin';
import { styled } from '@mui/material/styles';
/* Custom react-diagram action for keyboard events */
export class KeyboardShortcutAction extends Action {
@ -77,30 +77,30 @@ const getCanvasGrid = (theme)=>{
return `url("data:image/svg+xml, %3Csvg width='100%25' viewBox='0 0 45 45' style='background-color:${erdCanvasBg}' height='100%25' xmlns='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cpattern id='smallGrid' width='15' height='15' patternUnits='userSpaceOnUse'%3E%3Cpath d='M 15 0 L 0 0 0 15' fill='none' stroke='${erdGridColor}' stroke-width='0.5'/%3E%3C/pattern%3E%3Cpattern id='grid' width='45' height='45' patternUnits='userSpaceOnUse'%3E%3Crect width='100' height='100' fill='url(%23smallGrid)'/%3E%3Cpath d='M 100 0 L 0 0 0 100' fill='none' stroke='${erdGridColor}' stroke-width='1'/%3E%3C/pattern%3E%3C/defs%3E%3Crect width='100%25' height='100%25' fill='url(%23grid)' /%3E%3C/svg%3E%0A")`;
};
const styles = ((theme)=>({
diagramContainer: {
const StyledBox = styled(Box)(({theme})=>({
'& .ERDTool-diagramContainer': {
position: 'relative',
width: '100%',
flexGrow: 1,
minHeight: 0,
'& .ERDTool-diagramCanvas': {
width: '100%',
height: '100%',
color: theme.palette.text.primary,
backgroundColor: theme.otherVars.erdCanvasBg,
backgroundImage: getCanvasGrid(theme),
cursor: 'unset',
flexGrow: 1,
},
},
diagramCanvas: {
width: '100%',
height: '100%',
color: theme.palette.text.primary,
backgroundColor: theme.otherVars.erdCanvasBg,
backgroundImage: getCanvasGrid(theme),
cursor: 'unset',
flexGrow: 1,
},
html2canvasReset: {
'& .ERDTool-html2canvasReset': {
backgroundImage: 'none !important',
overflow: 'auto !important',
}
}));
/* The main body container for the ERD */
class ERDTool extends React.Component {
export default class ERDTool extends React.Component {
static contextType = ModalContext;
constructor(props) {
super(props);
@ -715,7 +715,7 @@ class ERDTool extends React.Component {
* the canvas back to original state.
* Code referred from - zoomToFitNodes function.
*/
this.diagramContainerRef.current?.classList.add(this.props.classes.html2canvasReset);
this.diagramContainerRef.current?.classList.add('ERDTool-html2canvasReset');
const margin = 10;
let nodesRect = this.diagram.getEngine().getBoundingNodesRect(this.diagram.getModel().getNodes());
let linksRect = this.diagram.getBoundingLinksRect();
@ -788,7 +788,7 @@ class ERDTool extends React.Component {
pgAdmin.Browser.notifier.alert(gettext('Error'), msg);
}).then(()=>{
/* Revert back to the original CSS styles */
this.diagramContainerRef.current.classList.remove(this.props.classes.html2canvasReset);
this.diagramContainerRef.current.classList.remove('ERDTool-html2canvasReset');
this.canvasEle.style.width = '';
this.canvasEle.style.height = '';
this.canvasEle.childNodes.forEach((ele)=>{
@ -923,7 +923,7 @@ class ERDTool extends React.Component {
this.erdDialogs.modal = this.context;
return (
<Box ref={this.containerRef} height="100%" display="flex" flexDirection="column">
<StyledBox ref={this.containerRef} height="100%" display="flex" flexDirection="column">
<ConnectionBar status={this.state.conn_status} bgcolor={this.props.params.bgcolor}
fgcolor={this.props.params.fgcolor} title={_.unescape(this.props.params.title)}/>
<MainToolBar preferences={this.state.preferences} eventBus={this.eventBus}
@ -932,20 +932,20 @@ class ERDTool extends React.Component {
/>
<FloatingNote open={this.state.note_open} onClose={this.onNoteClose}
anchorEl={this.noteRefEle} noteNode={this.state.note_node} appendTo={this.diagramContainerRef.current} rows={8}/>
<div className={this.props.classes.diagramContainer} data-test="diagram-container" ref={this.diagramContainerRef} onDrop={this.onDropNode} onDragOver={e => {e.preventDefault();}}>
<div className='ERDTool-diagramContainer' data-test="diagram-container" ref={this.diagramContainerRef} onDrop={this.onDropNode} onDragOver={e => {e.preventDefault();}}>
<Loader message={this.state.loading_msg} autoEllipsis={true}/>
<ERDCanvasSettings.Provider value={{
cardinality_notation: this.state.cardinality_notation
}}>
{!this.props.isTest && <CanvasWidget className={this.props.classes.diagramCanvas} ref={(ele)=>{this.canvasEle = ele?.ref?.current;}} engine={this.diagram.getEngine()} />}
{!this.props.isTest && <CanvasWidget className='ERDTool-diagramCanvas' ref={(ele)=>{this.canvasEle = ele?.ref?.current;}} engine={this.diagram.getEngine()} />}
</ERDCanvasSettings.Provider>
</div>
</Box>
</StyledBox>
);
}
}
export default withStyles(styles)(ERDTool);
//export default withStyles(styles)(ERDTool);
ERDTool.propTypes = {
params:PropTypes.shape({

View File

@ -8,53 +8,59 @@
//////////////////////////////////////////////////////////////
import React, { useEffect, useState, useMemo } from 'react';
import { styled } from '@mui/material/styles';
import gettext from 'sources/gettext';
import PropTypes from 'prop-types';
import CustomPropTypes from 'sources/custom_prop_types';
import { Box, Popper } from '@mui/material';
import { makeStyles } from '@mui/styles';
import { DefaultButton } from '../../../../../../static/js/components/Buttons';
import CheckIcon from '@mui/icons-material/Check';
const useStyles = makeStyles((theme)=>({
root: {
const StyledPopper = styled(Popper)(({theme}) => ({
'& .FloatingNote-root': {
width: '250px',
marginLeft: '8px',
...theme.mixins.panelBorder.all,
borderRadius: theme.shape.borderRadius,
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
'& .FloatingNote-note': {
padding: '4px',
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
borderTopLeftRadius: 'inherit',
borderTopRightRadius: 'inherit',
},
'& .FloatingNote-header': {
padding: '4px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
...theme.mixins.panelBorder.bottom,
},
'& .FloatingNote-textarea': {
width: '100%',
border: 0,
display: 'block',
},
'& .FloatingNote-buttons': {
padding: '4px',
...theme.mixins.panelBorder.top,
textAlign: 'right',
}
},
note: {
padding: '4px',
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
borderTopLeftRadius: 'inherit',
borderTopRightRadius: 'inherit',
},
header: {
padding: '4px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
...theme.mixins.panelBorder.bottom,
},
textarea: {
width: '100%',
border: 0,
display: 'block',
},
buttons: {
padding: '4px',
...theme.mixins.panelBorder.top,
textAlign: 'right',
},
}));
export default function FloatingNote({open, onClose, anchorEl, rows, noteNode}) {
const [text, setText] = useState('');
const classes = useStyles();
useEffect(()=>{
if(noteNode) {
setText(noteNode.getNote());
@ -70,16 +76,16 @@ export default function FloatingNote({open, onClose, anchorEl, rows, noteNode})
}, [open]);
return (
<Popper
<StyledPopper
open={open}
anchorEl={anchorEl}
placement="right-start"
>
<Box className={classes.root}>
<Box className={classes.note}>{gettext('Note')}:</Box>
<Box className={classes.header}>{header}</Box>
<textarea className={classes.textarea} autoFocus value={text} rows={rows} onChange={(e)=>setText(e.target.value)}/>
<Box className={classes.buttons}>
<Box className='FloatingNote-root'>
<Box className='FloatingNote-note'>{gettext('Note')}:</Box>
<Box className='FloatingNote-header'>{header}</Box>
<textarea className='FloatingNote-textarea' autoFocus value={text} rows={rows} onChange={(e)=>setText(e.target.value)}/>
<Box className='FloatingNote-buttons'>
<DefaultButton startIcon={<CheckIcon />} onClick={()=>{
let updated = (noteNode.getNote() != text);
noteNode.setNote(text);
@ -87,7 +93,7 @@ export default function FloatingNote({open, onClose, anchorEl, rows, noteNode})
}}>{gettext('OK')}</DefaultButton>
</Box>
</Box>
</Popper>
</StyledPopper>
);
}

View File

@ -7,7 +7,7 @@
//
//////////////////////////////////////////////////////////////
import React, {useCallback, useEffect, useState} from 'react';
import { makeStyles } from '@mui/styles';
import { styled } from '@mui/material/styles';
import { Box, useTheme } from '@mui/material';
import { PgButtonGroup, PgIconButton } from '../../../../../../static/js/components/Buttons';
import FolderRoundedIcon from '@mui/icons-material/FolderRounded';
@ -38,40 +38,17 @@ import { MagicIcon, SQLFileIcon } from '../../../../../../static/js/components/E
import { useModal } from '../../../../../../static/js/helpers/ModalProvider';
import { withColorPicker } from '../../../../../../static/js/helpers/withColorPicker';
const useStyles = makeStyles((theme)=>({
root: {
padding: '2px 4px',
display: 'flex',
alignItems: 'center',
gap: '4px',
backgroundColor: theme.otherVars.editorToolbarBg,
flexWrap: 'wrap',
...theme.mixins.panelBorder.bottom,
},
connectionButton: {
display: 'flex',
width: '450px',
backgroundColor: theme.palette.default.main,
color: theme.palette.default.contrastText,
border: '1px solid ' + theme.palette.default.borderColor,
justifyContent: 'flex-start',
},
fillColorIcon: (props)=>({
'& path[fill-opacity]': {
fillOpacity: 1,
color: props.fillColor ?? theme.palette.background.default,
}
}),
textColorIcon: (props)=>({
'& path[fill-opacity]': {
fillOpacity: 1,
color: props.textColor ?? theme.palette.text.primary,
}
}),
const StyledBox = styled(Box)(({theme}) => ({
padding: '2px 4px',
display: 'flex',
alignItems: 'center',
gap: '4px',
backgroundColor: theme.otherVars.editorToolbarBg,
flexWrap: 'wrap',
...theme.mixins.panelBorder.bottom,
}));
export function MainToolBar({preferences, eventBus, fillColor, textColor, notation, onNotationChange}) {
const classes = useStyles({fillColor,textColor});
const theme = useTheme();
const [buttonsDisabled, setButtonsDisabled] = useState({
'save': true,
@ -177,8 +154,8 @@ export function MainToolBar({preferences, eventBus, fillColor, textColor, notati
}, [checkedMenuItems['sql_with_drop']]);
return (
<>
<Box className={classes.root}>
(<>
<StyledBox>
<PgButtonGroup size="small">
<PgIconButton title={gettext('Load Project')} icon={<FolderRoundedIcon />}
shortcut={preferences.open_project} onClick={()=>{
@ -245,7 +222,7 @@ export function MainToolBar({preferences, eventBus, fillColor, textColor, notati
}} />
</PgButtonGroup>
<PgButtonGroup size="small">
<ColorButton title={gettext('Fill Color')} icon={<FormatColorFillRoundedIcon className={classes.fillColorIcon} />}
<ColorButton title={gettext('Fill Color')} icon={<FormatColorFillRoundedIcon />}
value={fillColor ?? theme.palette.background.default} options={{
allowSave: true,
}}
@ -256,7 +233,7 @@ export function MainToolBar({preferences, eventBus, fillColor, textColor, notati
eventBus.fireEvent(ERD_EVENTS.CHANGE_COLORS, null, textColor);
}
}}/>
<ColorButton title={gettext('Text Color')} icon={<FormatColorTextRoundedIcon className={classes.textColorIcon} />}
<ColorButton title={gettext('Text Color')} icon={<FormatColorTextRoundedIcon />}
value={textColor ?? theme.palette.text.primary} options={{
allowSave: true,
}}
@ -309,7 +286,7 @@ export function MainToolBar({preferences, eventBus, fillColor, textColor, notati
<PgButtonGroup size="small">
<PgIconButton title={gettext('Help')} icon={<HelpIcon />} onClick={onHelpClick} />
</PgButtonGroup>
</Box>
</StyledBox>
<PgMenu
anchorRef={saveAsMenuRef}
open={openMenuName=='menu-saveas'}
@ -338,7 +315,7 @@ export function MainToolBar({preferences, eventBus, fillColor, textColor, notati
<PgMenuItem hasCheck closeOnCheck value="crows" checked={notation == 'crows'} onClick={onNotationChange}>{gettext('Crow\'s Foot Notation')}</PgMenuItem>
<PgMenuItem hasCheck closeOnCheck value="chen" checked={notation == 'chen'} onClick={onNotationChange}>{gettext('Chen Notation')}</PgMenuItem>
</PgMenu>
</>
</>)
);
}

View File

@ -19,9 +19,9 @@ import {
import {Point} from '@projectstorm/geometry';
import _ from 'lodash';
import PropTypes from 'prop-types';
import { makeStyles } from '@mui/styles';
import clsx from 'clsx';
import { styled } from '@mui/material/styles';
import { ERDCanvasSettings } from '../components/ERDTool';
import { keyframes } from '@emotion/react';
export const POINTER_SIZE = 30;
@ -79,38 +79,46 @@ export class OneToManyLinkModel extends RightAngleLinkModel {
}
}
const useStyles = makeStyles((theme)=>({
svgLink: {
const svgLinkSelected = keyframes`
from { stroke-dashoffset: 24;}
to { stroke-dashoffset: 0; }
`;
const StyledG = styled('g')((
{
theme
}
) => ({
'& .OneToMany-svgLink': {
stroke: theme.palette.text.primary,
fontSize: '0.8em',
},
'@keyframes svgLinkSelected': {
'from': { strokeDashoffset: 24},
'to': { strokeDashoffset: 0 }
},
svgLinkSelected: {
strokeDasharray: '10, 2',
animation: '$svgLinkSelected 1s linear infinite'
},
svgLinkCircle: {
'& .OneToMany-svgLinkCircle': {
fill: theme.palette.text.primary,
},
svgLinkPath: {
'& .OneToMany-svgLinkSelected': {
strokeDasharray: '10, 2',
animation: `${svgLinkSelected} 1s linear infinite`
},
'& .OneToMany-svgLinkPath': {
pointerEvents: 'all',
cursor: 'move',
}
}));
function ChenNotation({rotation, type}) {
const classes = useStyles();
const textX = Math.sign(rotation) > 0 ? -14 : 8;
const textY = -5;
return (
<>
<text className={classes.svgLink} x={textX} y={textY} transform={'rotate(' + -rotation + ')' }>
<text className='OneToMany-svgLink' x={textX} y={textY} transform={'rotate(' + -rotation + ')' }>
{type == 'one' ? '1' : 'N'}
</text>
<line className={classes.svgLink} x1="0" y1="0" x2="0" y2="30"></line>
<line className='OneToMany-svgLink' x1="0" y1="0" x2="0" y2="30"></line>
</>
);
}
@ -121,7 +129,7 @@ ChenNotation.propTypes = {
function CustomLinkEndWidget(props) {
const { point, rotation, tx, ty, type } = props;
const classes = useStyles();
const settings = useContext(ERDCanvasSettings);
const svgForType = (itype) => {
@ -131,13 +139,13 @@ function CustomLinkEndWidget(props) {
if(itype == 'many') {
return (
<>
<circle className={clsx(classes.svgLink, classes.svgLinkCircle)} cx="0" cy="16" r={props.width*2.5} strokeWidth={props.width} />
<polyline className={classes.svgLink} points="-8,0 0,15 0,0 0,30 0,15 8,0" fill="none" strokeWidth={props.width} />
<circle className={['OneToMany-svgLink','OneToMany-svgLinkCircle'].join(' ')} cx="0" cy="16" r={props.width*2.5} strokeWidth={props.width} />
<polyline className='OneToMany-svgLink' points="-8,0 0,15 0,0 0,30 0,15 8,0" fill="none" strokeWidth={props.width} />
</>
);
} else if (itype == 'one') {
return (
<polyline className={classes.svgLink} points="-8,15 0,15 0,0 0,30 0,15 8,15" fill="none" strokeWidth={props.width} />
<polyline className='OneToMany-svgLink' points="-8,15 0,15 0,0 0,30 0,15 8,15" fill="none" strokeWidth={props.width} />
);
}
};
@ -313,16 +321,16 @@ export class OneToManyLinkWidget extends RightAngleLinkWidget {
this.refPaths = [];
return <g data-default-link-test={this.props.link.getOptions().testName}>{paths}</g>;
return <StyledG data-default-link-test={this.props.link.getOptions().testName}>{paths}</StyledG>;
}
}
const LinkSegment = forwardRef(({model, selected, path, ...props}, ref)=>{
const classes = useStyles();
return (
<path
ref={ref}
className={clsx(classes.svgLink, classes.svgLinkPath, (selected ? classes.svgLinkSelected : ''))}
className={['OneToMany-svgLink','OneToMany-svgLinkPath', (selected ? 'OneToMany-svgLinkSelected' : '')].join(' ')}
stroke={model.getOptions().color}
strokeWidth={model.getOptions().width}
selected={selected}

View File

@ -23,10 +23,8 @@ import { PgIconButton } from '../../../../../../static/js/components/Buttons';
import NoteRoundedIcon from '@mui/icons-material/NoteRounded';
import VisibilityRoundedIcon from '@mui/icons-material/VisibilityRounded';
import VisibilityOffRoundedIcon from '@mui/icons-material/VisibilityOffRounded';
import { withStyles } from '@mui/styles';
import clsx from 'clsx';
import { Box } from '@mui/material';
import { styled } from '@mui/material/styles';
const TYPE = 'table';
const TABLE_WIDTH = 175;
@ -180,8 +178,8 @@ RowIcon.propTypes = {
icon: PropTypes.any.isRequired,
};
const styles = (theme)=>({
tableNode: {
const StyledDiv = styled('div')(({theme})=>({
'&.TableNode-tableNode': {
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
...theme.mixins.panelBorder.all,
@ -192,48 +190,48 @@ const styles = (theme)=>({
'& div:last-child': {
borderBottomLeftRadius: 'inherit',
borderBottomRightRadius: 'inherit',
}
},
'& .TableNode-tableSection': {
...theme.mixins.panelBorder.bottom,
padding: '0.125rem 0.25rem',
display: 'flex',
'&.TableNode-tableNameText': {
fontWeight: 'bold',
wordBreak: 'break-all',
margin: 'auto 0',
'& .TableNode-error': {
color: theme.palette.error.main,
},
},
'&.TableNode-tableToolbar': {
background: theme.otherVars.editorToolbarBg,
borderTopLeftRadius: 'inherit',
borderTopRightRadius: 'inherit',
},
'& .TableNode-noteBtn': {
marginLeft: 'auto',
backgroundColor: theme.palette.warning.main,
color: theme.palette.warning.contrastText,
},
},
'& .TableNode-columnSection': {
display:'flex',
width: '100%' ,
...theme.mixins.panelBorder.bottom,
'& .TableNode-columnName': {
display:'flex',
width: '100%' ,
padding: '0.125rem 0.25rem',
wordBreak: 'break-all',
},
},
},
tableNodeSelected: {
'&.TableNode-tableNodeSelected': {
borderColor: theme.palette.primary.main,
},
tableSection: {
...theme.mixins.panelBorder.bottom,
padding: '0.125rem 0.25rem',
display: 'flex',
},
columnSection: {
display:'flex',
width: '100%' ,
...theme.mixins.panelBorder.bottom,
},
columnName: {
display:'flex',
width: '100%' ,
padding: '0.125rem 0.25rem',
wordBreak: 'break-all',
},
tableToolbar: {
background: theme.otherVars.editorToolbarBg,
borderTopLeftRadius: 'inherit',
borderTopRightRadius: 'inherit',
},
tableNameText: {
fontWeight: 'bold',
wordBreak: 'break-all',
margin: 'auto 0',
},
error: {
color: theme.palette.error.main,
},
noteBtn: {
marginLeft: 'auto',
backgroundColor: theme.palette.warning.main,
color: theme.palette.warning.contrastText,
}
});
}));
class TableNodeWidgetRaw extends React.Component {
export class TableNodeWidget extends React.Component {
constructor(props) {
super(props);
@ -277,14 +275,13 @@ class TableNodeWidgetRaw extends React.Component {
if(col.attlen) {
cltype += '('+ col.attlen + (col.attprecision ? ',' + col.attprecision : '') +')';
}
const {classes} = this.props;
return (
<Box className={classes.columnSection} key={col.attnum} data-test="column-row">
<Box className='TableNode-columnSection' key={col.attnum} data-test="column-row">
<Box marginRight="auto" padding="0" minHeight="0" display="flex" alignItems="center">
{this.generatePort(leftPort)}
</Box>
<Box className={classes.columnName}>
<Box className='TableNode-columnName'>
<RowIcon icon={icon} />
<Box margin="auto 0">
<span data-test="column-name">{col.name}</span>&nbsp;
@ -324,19 +321,18 @@ class TableNodeWidgetRaw extends React.Component {
(tableData.unique_constraint||[]).forEach((uk)=>{
localUkCols.push(...uk.columns.map((c)=>c.column));
});
const {classes} = this.props;
const styles = {
backgroundColor: tableMetaData.fillColor,
color: tableMetaData.textColor,
};
return (
<div className={clsx(classes.tableNode, (this.props.node.isSelected() ? classes.tableNodeSelected: ''))}
<StyledDiv className={['TableNode-tableNode', (this.props.node.isSelected() ? 'TableNode-tableNodeSelected': '')].join(' ')}
onDoubleClick={()=>{this.props.node.fireEvent({}, 'editTable');}} style={styles}>
<div className={clsx(classes.tableSection, classes.tableToolbar)}>
<div className={'TableNode-tableSection TableNode-tableToolbar'}>
<PgIconButton size="xs" title={gettext('Show Details')} icon={this.state.show_details ? <VisibilityRoundedIcon /> : <VisibilityOffRoundedIcon />}
onClick={this.toggleShowDetails} onDoubleClick={(e)=>{e.stopPropagation();}} />
{this.props.node.getNote() &&
<PgIconButton size="xs" className={classes.noteBtn}
<PgIconButton size="xs" className='TableNode-noteBtn'
title={gettext('Check Note')} icon={<NoteRoundedIcon />}
onClick={()=>{
this.props.node.fireEvent({}, 'showNote');
@ -344,31 +340,29 @@ class TableNodeWidgetRaw extends React.Component {
/>}
</div>
{tableMetaData.is_promise &&
<div className={classes.tableSection}>
{!tableMetaData.data_failed && <div className={classes.tableNameText}>{gettext('Fetching...')}</div>}
{tableMetaData.data_failed && <div className={clsx(classes.tableNameText, classes.error)}>{gettext('Failed to get data. Please delete this table.')}</div>}
<div className='TableNode-tableSection'>
{!tableMetaData.data_failed && <div className='TableNode-tableNameText'>{gettext('Fetching...')}</div>}
{tableMetaData.data_failed && <div className={'TableNode-tableNameText TableNode-error'}>{gettext('Failed to get data. Please delete this table.')}</div>}
</div>}
{!tableMetaData.is_promise && <>
<div className={classes.tableSection}>
<div className='TableNode-tableSection'>
<RowIcon icon={SchemaIcon}/>
<div className={classes.tableNameText} data-test="schema-name">{tableData.schema}</div>
<div className='TableNode-tableNameText' data-test="schema-name">{tableData.schema}</div>
</div>
<div className={classes.tableSection}>
<div className='TableNode-tableSection'>
<RowIcon icon={TableIcon} />
<div className={classes.tableNameText} data-test="table-name">{tableData.name}</div>
<div className='TableNode-tableNameText' data-test="table-name">{tableData.name}</div>
</div>
{tableData.columns.length > 0 && <div>
{_.map(tableData.columns, (col)=>this.generateColumn(col, localFkCols, localUkCols))}
</div>}
</>}
</div>
</StyledDiv>
);
}
}
export const TableNodeWidget = withStyles(styles)(TableNodeWidgetRaw);
TableNodeWidgetRaw.propTypes = {
TableNodeWidget.propTypes = {
node: PropTypes.instanceOf(TableNodeModel),
engine: PropTypes.instanceOf(DiagramEngine),
classes: PropTypes.object,

View File

@ -12,7 +12,6 @@ import _ from 'lodash';
import url_for from 'sources/url_for';
import React, { useEffect } from 'react';
import { Box } from '@mui/material';
import { makeStyles } from '@mui/styles';
import Wizard from '../../../../static/js/helpers/wizard/Wizard';
import WizardStep from '../../../../static/js/helpers/wizard/WizardStep';
import PgTable from 'sources/components/PgTable';
@ -20,46 +19,12 @@ import { getNodePrivilegeRoleSchema } from '../../../../../pgadmin/browser/serve
import { InputSQL, FormFooterMessage, MESSAGE_TYPE } from '../../../../static/js/components/FormComponents';
import getApiInstance from '../../../../static/js/api_instance';
import SchemaView from '../../../../static/js/SchemaView';
import clsx from 'clsx';
import PropTypes from 'prop-types';
import PrivilegeSchema from './privilege_schema.ui';
import { usePgAdmin } from '../../../../static/js/BrowserComponent';
const useStyles = makeStyles(() =>
({
root: {
height: '100%'
},
searchBox: {
marginBottom: '1em',
display: 'flex',
},
searchPadding: {
flex: 2.5
},
searchInput: {
flex: 1,
marginTop: 2,
borderLeft: 'none',
paddingLeft: 5
},
grantWizardSql: {
height: '90% !important',
width: '100%'
},
privilegeStep: {
height: '100%',
overflow: 'auto'
},
panelContent: {
flexGrow: 1,
minHeight: 0
}
}),
);
export default function GrantWizard({ sid, did, nodeInfo, nodeData, onClose }) {
const classes = useStyles();
let columns = [
{
@ -310,11 +275,10 @@ export default function GrantWizard({ sid, did, nodeInfo, nodeData, onClose }) {
loaderText={loaderText}
>
<WizardStep stepId={0}>
<Box className={classes.panelContent}>
<Box sx={{flexGrow: 1, minHeight: 0}}>
<PgTable
caveTable={false}
tableNoBorder={false}
className={classes.table}
height={window.innerHeight - 450}
columns={columns}
data={tableData}
@ -327,7 +291,7 @@ export default function GrantWizard({ sid, did, nodeInfo, nodeData, onClose }) {
</WizardStep>
<WizardStep
stepId={1}
className={clsx(classes.privilegeStep)}>
sx={{ height:'100%', overflow:'auto'}}>
{privSchemaInstance &&
<SchemaView
formType={'dialog'}
@ -347,7 +311,6 @@ export default function GrantWizard({ sid, did, nodeInfo, nodeData, onClose }) {
<Box>{gettext('The SQL below will be executed on the database server to grant the selected privileges. Please click on Finish to complete the process.')}</Box>
<InputSQL
onLable={true}
className={classes.grantWizardSql}
readonly={true}
value={msqlData.toString()} />
</WizardStep>

View File

@ -8,11 +8,11 @@
//////////////////////////////////////////////////////////////
import gettext from 'sources/gettext';
import { styled } from '@mui/material/styles';
import _ from 'lodash';
import url_for from 'sources/url_for';
import React from 'react';
import { Box, Paper} from '@mui/material';
import { makeStyles } from '@mui/styles';
import Wizard from '../../../../static/js/helpers/wizard/Wizard';
import WizardStep from '../../../../static/js/helpers/wizard/WizardStep';
import { FormFooterMessage, MESSAGE_TYPE, FormNote } from '../../../../static/js/components/FormComponents';
@ -22,39 +22,33 @@ import ImportExportSelectionSchema from './import_export_selection.ui';
import CheckBoxTree from '../../../../static/js/components/CheckBoxTree';
import getApiInstance from '../../../../static/js/api_instance';
import PropTypes from 'prop-types';
import { commonTableStyles } from '../../../../static/js/Theme';
import clsx from 'clsx';
import pgAdmin from 'sources/pgadmin';
import Table from '../../../../static/js/components/Table';
const useStyles = makeStyles(() =>
const StyledBox = styled(Box)(() =>
({
root: {
height: '100%'
},
treeContainer: {
flexGrow: 1,
minHeight: 0,
},
boxText: {
paddingBottom: '5px'
},
noOverflow: {
overflow: 'hidden'
},
summaryContainer: {
flexGrow: 1,
minHeight: 0,
overflow: 'auto',
},
noteContainer: {
marginTop: '5px',
height: '100%',
'& .ImportExportServers-noOverflow': {
overflow: 'hidden',
'& .ImportExportServers-treeContainer': {
flexGrow: 1,
minHeight: 0,
},
'& .ImportExportServers-boxText': {
paddingBottom: '5px'
},
'& .ImportExportServers-summaryContainer': {
flexGrow: 1,
minHeight: 0,
overflow: 'auto',
},
'& .ImportExportServers-noteContainer': {
marginTop: '5px',
}
}
}),
);
}));
export default function ImportExportServers({onClose}) {
const classes = useStyles();
const tableClasses = commonTableStyles();
let steps = [gettext('Import/Export'), gettext('Database Servers'), gettext('Summary')];
const [loaderText, setLoaderText] = React.useState('');
@ -202,7 +196,7 @@ export default function ImportExportServers({onClose}) {
};
return (
<Box className={classes.root}>
<StyledBox>
<Loader message={loaderText} />
<Wizard
title={gettext('Import/Export Servers')}
@ -227,18 +221,18 @@ export default function ImportExportServers({onClose}) {
/>
<FormFooterMessage type={MESSAGE_TYPE.ERROR} message={errMsg} onClose={onErrClose} />
</WizardStep>
<WizardStep stepId={1} className={classes.noOverflow}>
<Box className={classes.boxText}>{gettext('Select the Server Groups/Servers to import/export:')}</Box>
<Box className={classes.treeContainer}>
<WizardStep stepId={1} className='ImportExportServers-noOverflow'>
<Box className='ImportExportServers-boxText'>{gettext('Select the Server Groups/Servers to import/export:')}</Box>
<Box className='ImportExportServers-treeContainer'>
<CheckBoxTree treeData={serverData} getSelectedServers={(selServers) => {
setSelectedServers(selServers);
}}/>
</Box>
</WizardStep>
<WizardStep stepId={2} className={classes.noOverflow}>
<Box className={classes.boxText}>{gettext(summaryText)}</Box>
<Paper variant="outlined" elevation={0} className={classes.summaryContainer}>
<table className={clsx(tableClasses.table)}>
<WizardStep stepId={2} className='ImportExportServers-noOverflow'>
<Box className='ImportExportServers-boxText'>{gettext(summaryText)}</Box>
<Paper variant="outlined" elevation={0} className='ImportExportServers-summaryContainer'>
<Table>
<thead>
<tr>
<th>Server Group</th>
@ -255,13 +249,13 @@ export default function ImportExportServers({onClose}) {
</tr>
))}
</tbody>
</table>
</Table>
</Paper>
{selectionFormData.imp_exp == 'i' &&
<FormNote className={classes.noteContainer} text={noteText}/> }
<FormNote className='ImportExportServers-noteContainer' text={noteText}/> }
</WizardStep>
</Wizard>
</Box>
</StyledBox>
);
}
ImportExportServers.propTypes = {

View File

@ -11,29 +11,14 @@ import PropTypes from 'prop-types';
import React, { useContext, useState } from 'react';
import { Box, Grid, Typography } from '@mui/material';
import { makeStyles } from '@mui/styles';
import { InputSelect } from '../../../../../static/js/components/FormComponents';
import { SchemaDiffEventsContext } from './SchemaDiffComponent';
import { SCHEMA_DIFF_EVENT } from '../SchemaDiffConstants';
const useStyles = makeStyles(() => ({
root: {
padding: '0rem'
},
spanLabel: {
alignSelf: 'center',
marginRight: '4px',
},
inputLabel: {
padding: '0.3rem',
},
}));
export function InputComponent({ label, serverList, databaseList, schemaList, diff_type, selectedSid = null, selectedDid=null, selectedScid=null }) {
const classes = useStyles();
const [selectedServer, setSelectedServer] = useState(selectedSid);
const [selectedDatabase, setSelectedDatabase] = useState(selectedDid);
const [selectedSchema, setSelectedSchema] = useState(selectedScid);
@ -71,16 +56,16 @@ export function InputComponent({ label, serverList, databaseList, schemaList, di
};
return (
<Box className={classes.root}>
<Box sx={{padding: '0rem'}}>
<Grid
container
direction="row"
alignItems="center"
>
<Grid item lg={2} md={2} sm={2} xs={2} className={classes.inputLabel}>
<Grid item lg={2} md={2} sm={2} xs={2} sx={{padding: '0.3rem'}}>
<Typography id={label}>{label}</Typography>
</Grid>
<Grid item lg={4} md={4} sm={4} xs={4} className={classes.inputLabel}>
<Grid item lg={4} md={4} sm={4} xs={4} sx={{padding: '0.3rem'}}>
<InputSelect
options={serverList}
optionsReloadBasis={serverList?.length}
@ -95,7 +80,7 @@ export function InputComponent({ label, serverList, databaseList, schemaList, di
></InputSelect>
</Grid>
<Grid item lg={3} md={3} sm={3} xs={3} className={classes.inputLabel}>
<Grid item lg={3} md={3} sm={3} xs={3} sx={{padding: '0.3rem'}}>
<InputSelect
options={databaseList}
optionsReloadBasis={databaseList?.map ? _.join(databaseList.map((c)=>c.value), ',') : null}
@ -111,7 +96,7 @@ export function InputComponent({ label, serverList, databaseList, schemaList, di
></InputSelect>
</Grid>
<Grid item lg={3} md={3} sm={3} xs={3} className={classes.inputLabel}>
<Grid item lg={3} md={3} sm={3} xs={3} sx={{padding: '0.3rem'}}>
<InputSelect
options={schemaList}
optionsReloadBasis={schemaList?.map ? _.join(schemaList.map((c)=>c.value), ',') : null}
@ -127,7 +112,6 @@ export function InputComponent({ label, serverList, databaseList, schemaList, di
></InputSelect>
</Grid>
</Grid>
</Box >
);
}

View File

@ -7,22 +7,16 @@
//
//////////////////////////////////////////////////////////////
import _ from 'lodash';
import { styled } from '@mui/material/styles';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import { SelectColumn } from 'react-data-grid';
import React, { useContext, useEffect, useLayoutEffect, useReducer, useRef, useState } from 'react';
import { Box } from '@mui/material';
import { makeStyles } from '@mui/styles';
import KeyboardArrowRightRoundedIcon from '@mui/icons-material/KeyboardArrowRightRounded';
import KeyboardArrowDownRoundedIcon from '@mui/icons-material/KeyboardArrowDownRounded';
import InfoIcon from '@mui/icons-material/InfoRounded';
import gettext from 'sources/gettext';
import url_for from 'sources/url_for';
import { FILTER_NAME, SCHEMA_DIFF_EVENT } from '../SchemaDiffConstants';
import { SchemaDiffContext, SchemaDiffEventsContext } from './SchemaDiffComponent';
import { InputCheckbox } from '../../../../../static/js/components/FormComponents';
@ -30,152 +24,148 @@ import PgReactDataGrid from '../../../../../static/js/components/PgReactDataGrid
import { usePgAdmin } from '../../../../../static/js/BrowserComponent';
const useStyles = makeStyles((theme) => ({
root: {
paddingTop: '0.5rem',
display: 'flex',
height: '100%',
flexDirection: 'column',
color: theme.palette.text.primary,
backgroundColor: theme.otherVars.qtDatagridBg,
border: 'none',
fontSize: '13px',
'& .rdg': {
flex: 1,
borderTop: '1px solid' + theme.otherVars.borderColor,
},
'--rdg-background-color': theme.palette.default.main,
'--rdg-selection-color': theme.palette.primary.main,
'& .rdg-cell': {
padding: 0,
boxShadow: 'none',
color: theme.otherVars.schemaDiff.diffColorFg + ' !important',
...theme.mixins.panelBorder.right,
...theme.mixins.panelBorder.bottom,
'&[aria-colindex="1"]': {
padding: 0,
},
'&[aria-selected=true]:not([role="columnheader"]):not([aria-colindex="1"])': {
outlineWidth: '0',
outlineOffset: '-1px',
color: theme.otherVars.qtDatagridSelectFg,
},
'&[aria-selected=true][aria-colindex="1"]': {
outlineWidth: 0,
}
},
'& .rdg-header-row .rdg-cell': {
padding: 0,
paddingLeft: '0.5rem',
boxShadow: 'none',
const StyledBox = styled(Box)(({theme}) => ({
paddingTop: '0.5rem',
display: 'flex',
height: '100%',
flexDirection: 'column',
color: theme.palette.text.primary,
backgroundColor: theme.otherVars.qtDatagridBg,
border: 'none',
fontSize: '13px',
'--rdg-background-color': theme.palette.default.main,
'--rdg-selection-color': theme.palette.primary.main,
'& .ResultGridComponent-gridPanel': {
'--rdg-background-color': theme.palette.default.main + ' !important',
'&.ResultGridComponent-grid': {
fontSize: '13px',
'--rdg-selection-color': 'none'
},
'& .rdg-header-row': {
backgroundColor: theme.palette.background.default,
'& .rdg-cell': {
padding: 0,
paddingLeft: '0.5rem',
boxShadow: 'none',
'&[aria-colindex="1"]': {
padding: 0,
},
'& .ResultGridComponent-headerSelectCell': {
padding: '0rem 0.3rem 0 0.3rem'
},
},
},
'& .rdg-row': {
backgroundColor: theme.palette.background.default,
'&[aria-selected=true]': {
backgroundColor: theme.palette.primary.light,
color: theme.otherVars.qtDatagridSelectFg,
'& .rdg-cell:nth-child(1)': {
'& .rdg-cell:nth-of-type(1)': {
backgroundColor: 'transparent',
outlineColor: 'transparent',
color: theme.palette.primary.contrastText,
}
},
},
}
},
grid: {
fontSize: '13px',
'--rdg-selection-color': 'none'
},
subRow: {
paddingLeft: '1rem'
},
recordRow: {
marginLeft: '2.7rem',
height: '1.3rem',
width: '1.3rem',
display: 'inline-block',
marginRight: '0.3rem',
paddingLeft: '0.5rem',
},
rowIcon: {
display: 'inline-block !important',
height: '1.3rem',
width: '1.3rem'
},
cellExpand: {
display: 'table',
blockSize: '100%',
'& > span': {
verticalAlign: 'middle',
cursor: 'pointer',
'& > span': {
display: 'flex',
alignItems: 'center',
'& .rdg-cell': {
padding: 0,
boxShadow: 'none',
color: theme.otherVars.schemaDiff.diffColorFg + ' !important',
...theme.mixins.panelBorder.right,
...theme.mixins.panelBorder.bottom,
'&[aria-colindex="1"]': {
padding: 0,
},
'&[aria-selected=true]:not([role="columnheader"]):not([aria-colindex="1"])': {
outlineWidth: '0',
outlineOffset: '-1px',
color: theme.otherVars.qtDatagridSelectFg,
},
'&[aria-selected=true][aria-colindex="1"]': {
outlineWidth: 0,
},
'& .ResultGridComponent-cellExpand': {
display: 'table',
blockSize: '100%',
'& > span': {
verticalAlign: 'middle',
cursor: 'pointer',
'& > span': {
display: 'flex',
alignItems: 'center',
}
},
'& .ResultGridComponent-subRow': {
paddingLeft: '1rem',
'& .ResultGridComponent-rowIcon': {
display: 'inline-block !important',
height: '1.3rem',
width: '1.3rem'
},
'& .ResultGridComponent-count': {
display: 'inline-block !important',
'& .ResultGridComponent-countLabel': {
paddingLeft: '1rem',
},
'& .ResultGridComponent-countStyle': {
fontWeight: 900,
fontSize: '0.8rem',
paddingLeft: '0.3rem',
},
}
}
},
'& .ResultGridComponent-selectedRow': {
paddingLeft: '0.5rem',
backgroundColor: theme.palette.primary.light,
},
'& .ResultGridComponent-source': {
backgroundColor: theme.otherVars.schemaDiff.sourceRowColor,
color: theme.otherVars.schemaDiff.diffSelectFG,
paddingLeft: '0.5rem',
},
'& .ResultGridComponent-target': {
backgroundColor: theme.otherVars.schemaDiff.targetRowColor,
color: theme.otherVars.schemaDiff.diffSelectFG,
paddingLeft: '0.5rem',
},
'& .ResultGridComponent-different': {
backgroundColor: theme.otherVars.schemaDiff.diffRowColor,
color: theme.otherVars.schemaDiff.diffSelectFG,
paddingLeft: '0.5rem',
},
'& .ResultGridComponent-identical': {
paddingLeft: '0.5rem',
color: theme.otherVars.schemaDiff.diffColorFg,
},
'& .ResultGridComponent-recordRow': {
marginLeft: '2.7rem',
height: '1.3rem',
width: '1.3rem',
display: 'inline-block',
marginRight: '0.3rem',
paddingLeft: '0.5rem',
},
'& .ResultGridComponent-selectCell': {
padding: '0 0.3rem'
},
'& .ResultGridComponent-selectedRowCheckBox': {
backgroundColor: theme.otherVars.schemaDiff.diffSelCheckbox,
},
'& .ResultGridComponent-selChBox': {
paddingLeft: 0,
}
}
}
},
'& .ResultGridComponent-noRowsIcon': {
width: '1.1rem',
height: '1.1rem',
marginRight: '0.5rem',
},
'&.rdg': {
flex: 1,
borderTop: '1px solid' + theme.otherVars.borderColor,
},
},
gridPanel: {
'--rdg-background-color': theme.palette.default.main + ' !important',
},
source: {
backgroundColor: theme.otherVars.schemaDiff.sourceRowColor,
color: theme.otherVars.schemaDiff.diffSelectFG,
paddingLeft: '0.5rem',
},
target: {
backgroundColor: theme.otherVars.schemaDiff.targetRowColor,
color: theme.otherVars.schemaDiff.diffSelectFG,
paddingLeft: '0.5rem',
},
different: {
backgroundColor: theme.otherVars.schemaDiff.diffRowColor,
color: theme.otherVars.schemaDiff.diffSelectFG,
paddingLeft: '0.5rem',
},
identical: {
paddingLeft: '0.5rem',
color: theme.otherVars.schemaDiff.diffColorFg,
},
selectCell: {
padding: '0 0.3rem'
},
headerSelectCell: {
padding: '0rem 0.3rem 0 0.3rem'
},
count: {
display: 'inline-block !important',
},
countStyle: {
fontWeight: 900,
fontSize: '0.8rem',
paddingLeft: '0.3rem',
},
countLabel: {
paddingLeft: '1rem',
},
selectedRow: {
paddingLeft: '0.5rem',
backgroundColor: theme.palette.primary.light
},
selectedRowCheckBox: {
paddingLeft: '0.5rem',
backgroundColor: theme.otherVars.schemaDiff.diffSelCheckbox,
},
selChBox: {
paddingLeft: 0,
},
noRowsIcon:{
width: '1.1rem',
height: '1.1rem',
marginRight: '0.5rem',
}
}));
function useFocusRef(isSelected) {
@ -219,8 +209,7 @@ function CellExpanderFormatter({
isCellSelected,
expanded,
filterParams,
onCellExpand,
classes
onCellExpand
}) {
const { ref, tabIndex } = useFocusRef(isCellSelected);
'identicalCount' in row && setRecordCount(row, filterParams);
@ -233,24 +222,24 @@ function CellExpanderFormatter({
}
return (
<div className={classes.cellExpand}>
<div className='ResultGridComponent-cellExpand'>
<span onClick={onCellExpand} onKeyDown={handleKeyDown}>
<span ref={ref} tabIndex={tabIndex} className={'identicalCount' in row ? classes.subRow : null}>
{expanded ? <KeyboardArrowDownRoundedIcon /> : <KeyboardArrowRightRoundedIcon />} <span className={clsx(row.icon, classes.rowIcon)}></span>{row.label}
<span ref={ref} tabIndex={tabIndex} className={'identicalCount' in row ? 'ResultGridComponent-subRow' : null}>
{expanded ? <KeyboardArrowDownRoundedIcon /> : <KeyboardArrowRightRoundedIcon />} <span className={row.icon + ' ResultGridComponent-rowIcon'}></span>{row.label}
{
'identicalCount' in row ?
<span className={clsx(classes.count)}>
<span className={'ResultGridComponent-count'}>
{
filterParams.includes(FILTER_NAME.IDENTICAL) && <><span className={classes.countLabel}>{FILTER_NAME.IDENTICAL}:</span> <span className={classes.countStyle}>{row.identicalCount} </span></>
filterParams.includes(FILTER_NAME.IDENTICAL) && <><span className='ResultGridComponent-countLabel'>{FILTER_NAME.IDENTICAL}:</span> <span className='ResultGridComponent-countStyle'>{row.identicalCount} </span></>
}
{
filterParams.includes(FILTER_NAME.DIFFERENT) && <><span className={classes.countLabel}>{FILTER_NAME.DIFFERENT}:</span> <span className={classes.countStyle}>{row.differentCount} </span></>
filterParams.includes(FILTER_NAME.DIFFERENT) && <><span className='ResultGridComponent-countLabel'>{FILTER_NAME.DIFFERENT}:</span> <span className='ResultGridComponent-countStyle'>{row.differentCount} </span></>
}
{
filterParams.includes(FILTER_NAME.SOURCE_ONLY) && <><span className={classes.countLabel}>{FILTER_NAME.SOURCE_ONLY}:</span> <span className={classes.countStyle}>{row.sourceOnlyCount} </span></>
filterParams.includes(FILTER_NAME.SOURCE_ONLY) && <><span className='ResultGridComponent-countLabel'>{FILTER_NAME.SOURCE_ONLY}:</span> <span className='ResultGridComponent-countStyle'>{row.sourceOnlyCount} </span></>
}
{
filterParams.includes(FILTER_NAME.TARGET_ONLY) && <><span className={classes.countLabel}>{FILTER_NAME.TARGET_ONLY}: </span><span className={classes.countStyle}>{row.targetOnlyCount}</span></>
filterParams.includes(FILTER_NAME.TARGET_ONLY) && <><span className='ResultGridComponent-countLabel'>{FILTER_NAME.TARGET_ONLY}: </span><span className='ResultGridComponent-countStyle'>{row.targetOnlyCount}</span></>
}
</span>
: null
@ -444,7 +433,7 @@ function reducer(rows, { type, id, filterParams, gridData }) {
}
export function ResultGridComponent({ gridData, allRowIds, filterParams, selectedRowIds, transId, sourceData, targetData }) {
const classes = useStyles();
const [rows, dispatch] = useReducer(reducer, [...gridData]);
const [selectedRows, setSelectedRows] = useState([]);
const [rootSelection, setRootSelection] = useState(false);
@ -544,15 +533,15 @@ export function ResultGridComponent({ gridData, allRowIds, filterParams, selecte
function getStyleClassName(row, selectedRowIds, isCellSelected, activeRowId, isCheckbox = false) {
let clsName = null;
if (selectedRowIds.includes(`${row.id}`) || isCellSelected || row.id == activeRowId) {
clsName = isCheckbox ? classes.selectedRowCheckBox : classes.selectedRow;
clsName = isCheckbox ? 'ResultGridComponent-selectedRowCheckBox' : 'ResultGridComponent-selectedRow';
} else if (row.status == FILTER_NAME.DIFFERENT) {
clsName = classes.different;
clsName = 'ResultGridComponent-different';
} else if (row.status == FILTER_NAME.SOURCE_ONLY) {
clsName = classes.source;
clsName = 'ResultGridComponent-source';
} else if (row.status == FILTER_NAME.TARGET_ONLY) {
clsName = classes.target;
clsName = 'ResultGridComponent-target';
} else if (row.status == FILTER_NAME.IDENTICAL) {
clsName = classes.identical;
clsName = 'ResultGridComponent-identical';
}
return clsName;
@ -568,7 +557,7 @@ export function ResultGridComponent({ gridData, allRowIds, filterParams, selecte
return (
<InputCheckbox
cid={_.uniqueId('rgc')}
className={classes.headerSelectCell}
className='ResultGridComponent-headerSelectCell'
value={selectedRows.length == allRowIds.length ? rootSelection : false}
size='small'
onChange={(e) => {
@ -589,9 +578,9 @@ export function ResultGridComponent({ gridData, allRowIds, filterParams, selecte
formatter({ row, isCellSelected }) {
isCellSelected && setActiveRow(row.id);
return (
<Box className={!row?.children && clsx(getStyleClassName(row, selectedRows, isCellSelected, activeRow, true), classes.selChBox)}>
<Box className={!row?.children && getStyleClassName(row, selectedRows, isCellSelected, activeRow, true) + ' ResultGridComponent-selChBox'}>
<InputCheckbox
className={classes.selectCell}
className='ResultGridComponent-selectCell'
cid={`${row.id}`}
value={selectedRows.includes(`${row.id}`)}
size='small'
@ -639,14 +628,13 @@ export function ResultGridComponent({ gridData, allRowIds, filterParams, selecte
expanded={row.isExpanded === true}
filterParams={filterParams}
onCellExpand={() => dispatch({ id: row.id, type: 'toggleSubRow', filterParams: filterParams, gridData: gridData, selectedRows: selectedRows })}
classes={classes}
/>
)}
<div className="rdg-cell-value">
{!hasChildren && (
<Box className={clsx(getStyleClassName(row, selectedRows, isCellSelected, activeRow), classes.status)}>
<span className={clsx(classes.recordRow, row.icon)}></span>
<Box className={getStyleClassName(row, selectedRows, isCellSelected, activeRow)}>
<span className={'ResultGridComponent-recordRow ' + row.icon}></span>
{row.label}
</Box>
)}
@ -728,13 +716,13 @@ export function ResultGridComponent({ gridData, allRowIds, filterParams, selecte
}
return (
<Box className={classes.root} flexGrow="1" minHeight="0" id="schema-diff-grid">
<StyledBox flexGrow="1" minHeight="0" id="schema-diff-grid">
{
gridData ?
<PgReactDataGrid
id="schema-diff-result-grid"
columns={columns} rows={rows}
className={clsx('big-grid', classes.gridPanel, classes.grid)}
className={'ResultGridComponent-gridPanel ResultGridComponent-grid'}
treeDepth={2}
enableRowSelect={true}
defaultColumnOptions={{
@ -747,14 +735,14 @@ export function ResultGridComponent({ gridData, allRowIds, filterParams, selecte
rowKeyGetter={rowKeyGetter}
direction={'vertical-lr'}
noRowsText={gettext('No difference found')}
noRowsIcon={<InfoIcon className={classes.noRowsIcon} />}
noRowsIcon={<InfoIcon className='ResultGridComponent-noRowsIcon' />}
/>
:
<>
{gettext('Loading result grid...')}
</>
}
</Box>
</StyledBox>
);
}

View File

@ -8,52 +8,57 @@
//////////////////////////////////////////////////////////////
import gettext from 'sources/gettext';
import { styled } from '@mui/material/styles';
import React, { useContext, useState, useEffect } from 'react';
import { Box } from '@mui/material';
import { makeStyles } from '@mui/styles';
import { InputSQL } from '../../../../../static/js/components/FormComponents';
import { SchemaDiffEventsContext } from './SchemaDiffComponent';
import { SCHEMA_DIFF_EVENT } from '../SchemaDiffConstants';
const useStyles = makeStyles((theme) => ({
header: {
const Root = styled('div')(({theme}) => ({
height: '100%',
display:'flex',
flexDirection:'column',
'& .Results-header': {
padding: '0.5rem',
borderBottom: '1px solid ' + theme.otherVars.borderColor,
},
sqlContainer: {
'& .Results-labelContainer': {
display: 'flex',
flexDirection: 'row',
'& .Results-label': {
padding: '0.2rem 0.5rem',
width: '33.33%'
},
},
'& .Results-sqlContainer': {
display: 'flex',
flexDirection: 'row',
padding: '0rem 0rem 0.5rem',
flexGrow: 1,
overflow: 'hidden'
},
sqldata: {
display: 'flex',
flexGrow: 1,
flexDirection: 'column',
padding: '0.2rem 0.5rem',
width: '33.33%',
},
labelContainer: {
display: 'flex',
flexDirection: 'row',
},
label: {
padding: '0.2rem 0.5rem',
width: '33.33%'
},
sqlInput: {
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
overflow: 'hidden',
height: '100%',
}
'& .Results-sqldata': {
display: 'flex',
flexGrow: 1,
flexDirection: 'column',
padding: '0.2rem 0.5rem',
width: '33.33%',
height: '100%',
'& .Results-sqlInput': {
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
height: '100%',
}
},
},
}));
export function Results() {
const classes = useStyles();
const [sourceSQL, setSourceSQL] = useState(null);
const [targetSQL, setTargetSQL] = useState(null);
const [sqlDiff, setSqlDiff] = useState(null);
@ -72,20 +77,19 @@ export function Results() {
setSqlDiff(resultData.SQLdiff);
};
return (
<>
<Box className={classes.header}>
<span>{gettext('DDL Comparison')}</span>
(<Root>
<Box className='Results-header'>
<span>{gettext('DDL Comparision')}</span>
</Box>
<Box className={classes.labelContainer}>
<Box className={classes.label}>{gettext('Source')}</Box>
<Box className={classes.label}>{gettext('Target')}</Box>
<Box className={classes.label}>{gettext('Difference')}</Box>
<Box className='Results-labelContainer'>
<Box className='Results-label'>{gettext('Source')}</Box>
<Box className='Results-label'>{gettext('Target')}</Box>
<Box className='Results-label'>{gettext('Difference')}</Box>
</Box>
<Box className={classes.sqlContainer}>
<Box className={classes.sqldata}>
<Box className={classes.sqlInput}>
<Box className='Results-sqlContainer'>
<Box className='Results-sqldata'>
<Box className='Results-sqlInput'>
<InputSQL
onLable={true}
value={sourceSQL}
@ -97,8 +101,8 @@ export function Results() {
/>
</Box>
</Box>
<Box className={classes.sqldata}>
<Box className={classes.sqlInput}>
<Box className='Results-sqldata'>
<Box className='Results-sqlInput'>
<InputSQL
onLable={true}
value={targetSQL}
@ -110,8 +114,8 @@ export function Results() {
/>
</Box>
</Box>
<Box className={classes.sqldata}>
<Box className={classes.sqlInput}>
<Box className='Results-sqldata'>
<Box className='Results-sqlInput'>
<InputSQL
onLable={true}
value={sqlDiff}
@ -129,7 +133,7 @@ export function Results() {
</Box>
</Box>
</Box>
</>
</Root>)
);
}

View File

@ -7,6 +7,7 @@
//
//////////////////////////////////////////////////////////////
import _ from 'lodash';
import { styled } from '@mui/material/styles';
import PropTypes from 'prop-types';
import React, { useState, useRef, useContext, useEffect } from 'react';
@ -17,7 +18,6 @@ import { Box } from '@mui/material';
import CompareArrowsRoundedIcon from '@mui/icons-material/CompareArrowsRounded';
import FeaturedPlayListRoundedIcon from '@mui/icons-material/FeaturedPlayListRounded';
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
import { makeStyles } from '@mui/styles';
import { DefaultButton, PgButtonGroup, PgIconButton, PrimaryButton } from '../../../../../static/js/components/Buttons';
import { FilterIcon } from '../../../../../static/js/components/ExternalIcon';
@ -26,21 +26,19 @@ import { FILTER_NAME, MENUS, MENUS_COMPARE_CONSTANT, SCHEMA_DIFF_EVENT, IGNORE_O
import { SchemaDiffContext, SchemaDiffEventsContext } from './SchemaDiffComponent';
const useStyles = makeStyles((theme) => ({
emptyIcon: {
width: '1.5rem'
const Root = styled('div')(({theme}) => ({
display: 'flex',
justifyContent: 'flex-end',
'& .SchemaDiffButtons-compareBtn': {
display: 'flex',
flexGrow: 1,
justifyContent: 'flex-start',
paddingLeft: '1.5rem',
[theme.breakpoints.down('sm')]: {
paddingTop: '0.3rem',
},
},
diff_btn: {
marginRight: '1rem'
},
noactionBtn: {
cursor: 'default',
'&:hover': {
backgroundColor: 'inherit',
cursor: 'default'
}
},
scriptBtn: {
'& .SchemaDiffButtons-scriptBtn': {
display: 'flex',
justifyContent: 'flex-end',
paddingRight: '0.3rem',
@ -49,26 +47,22 @@ const useStyles = makeStyles((theme) => ({
flexGrow: 1,
},
},
filterBtn: {
'&.SchemaDiffButtons-filterBtn': {
[theme.breakpoints.down('sm')]: {
paddingTop: '0.3rem',
flexGrow: 1,
}
},
compareBtn: {
display: 'flex',
flexGrow: 1,
justifyContent: 'flex-start',
paddingLeft: '1.5rem',
[theme.breakpoints.down('sm')]: {
paddingTop: '0.3rem',
},
}
'& .SchemaDiffButtons-noactionBtn': {
cursor: 'default',
'&:hover': {
backgroundColor: 'inherit',
cursor: 'default'
}
},
},
}));
export function SchemaDiffButtonComponent({ sourceData, targetData, selectedRowIds, rows, compareParams, filterParams = [FILTER_NAME.DIFFERENT, FILTER_NAME.SOURCE_ONLY, FILTER_NAME.TARGET_ONLY] }) {
const classes = useStyles();
const filterRef = useRef(null);
const compareRef = useRef(null);
@ -164,8 +158,8 @@ export function SchemaDiffButtonComponent({ sourceData, targetData, selectedRowI
};
return (
<>
<Box className={classes.compareBtn}>
(<Root>
<Box className='SchemaDiffButtons-compareBtn'>
<PgButtonGroup size="small" disabled={isDisableCompare}>
<PrimaryButton startIcon={<CompareArrowsRoundedIcon />}
onClick={compareDiff}>{gettext('Compare')}</PrimaryButton>
@ -173,14 +167,14 @@ export function SchemaDiffButtonComponent({ sourceData, targetData, selectedRowI
name={MENUS.COMPARE} ref={compareRef} onClick={toggleMenu} ></PgIconButton>
</PgButtonGroup>
</Box>
<Box className={classes.scriptBtn}>
<Box className='SchemaDiffButtons-scriptBtn'>
<PgButtonGroup size="small" disabled={selectedRowIds?.length <= 0}>
<DefaultButton startIcon={<FeaturedPlayListRoundedIcon />} onClick={generateScript}>{gettext('Generate Script')}</DefaultButton>
</PgButtonGroup>
</Box>
<Box className={classes.filterBtn}>
<Box className='SchemaDiffButtons-filterBtn'>
<PgButtonGroup size="small" disabled={isDisableCompare} style={{ paddingRight: '0.3rem' }}>
<DefaultButton startIcon={<FilterIcon />} className={classes.noactionBtn}
<DefaultButton startIcon={<FilterIcon />} className='SchemaDiffButtons-noactionBtn'
>{gettext('Filter')}</DefaultButton>
<PgIconButton title={gettext('Filter')} disabled={isDisableCompare} icon={<KeyboardArrowDownIcon />} splitButton
name={MENUS.FILTER} ref={filterRef} onClick={toggleMenu} ></PgIconButton>
@ -218,7 +212,7 @@ export function SchemaDiffButtonComponent({ sourceData, targetData, selectedRowI
<PgMenuItem hasCheck checked={selectedFilters.includes(FILTER_NAME.TARGET_ONLY)}
onClick={() => { selectFilterOption(FILTER_NAME.TARGET_ONLY); }}>{FILTER_NAME.TARGET_ONLY}</PgMenuItem>
</PgMenu>
</>
</Root>)
);
}

View File

@ -14,7 +14,7 @@ import React, { useContext, useEffect, useState } from 'react';
import { Box, Grid } from '@mui/material';
import InfoRoundedIcon from '@mui/icons-material/InfoRounded';
import HelpIcon from '@mui/icons-material/HelpRounded';
import { makeStyles } from '@mui/styles';
import gettext from 'sources/gettext';
import url_for from 'sources/url_for';
@ -33,32 +33,6 @@ import { openSocket, socketApiGet } from '../../../../../static/js/socket_instan
import { parseApiError } from '../../../../../static/js/api_instance';
import { usePgAdmin } from '../../../../../static/js/BrowserComponent';
const useStyles = makeStyles(() => ({
table: {
minWidth: 650,
},
summaryContainer: {
flexGrow: 1,
minHeight: 0,
overflow: 'auto',
},
note: {
marginTop: '1.2rem',
textAlign: 'center',
},
helpBtn: {
display: 'flex',
flexDirection: 'row-reverse',
paddingRight: '0.3rem'
},
compareComp: {
flexGrow: 1,
},
diffBtn: {
display: 'flex',
justifyContent: 'flex-end'
}
}));
function generateFinalScript(script_array, scriptHeader, script_body) {
_.each(Object.keys(script_array).reverse(), function (s) {
@ -114,7 +88,6 @@ const onHelpClick=()=>{
};
export function SchemaDiffCompare({ params }) {
const classes = useStyles();
const schemaDiffToolContext = useContext(SchemaDiffContext);
const eventBus = useContext(SchemaDiffEventsContext);
@ -728,7 +701,7 @@ export function SchemaDiffCompare({ params }) {
diff_type={TYPE.SOURCE}
></InputComponent>
</Grid>
<Grid item lg={5} md={5} sm={2} xs={2} className={classes.helpBtn}>
<Grid item lg={5} md={5} sm={2} xs={2} sx={{ display: 'flex',flexDirection: 'row-reverse',paddingRight: '0.3rem'}}>
<PgButtonGroup size="small">
<PgIconButton data-test='schema-diff-help' title={gettext('Help')} icon={<HelpIcon />} onClick={onHelpClick} />
</PgButtonGroup>
@ -752,7 +725,7 @@ export function SchemaDiffCompare({ params }) {
></InputComponent>
</Grid>
<Grid item lg={5} md={5} sm={12} xs={12} className={classes.diffBtn}>
<Grid item lg={5} md={5} sm={12} xs={12}>
<SchemaDiffButtonComponent
sourceData={{
'sid': selectedSourceSid,
@ -792,7 +765,7 @@ export function SchemaDiffCompare({ params }) {
}}
></ResultGridComponent>
:
<Box className={classes.note}>
<Box sx={{ marginTop: '1.2rem',textAlign: 'center'}}>
<InfoRoundedIcon style={{ fontSize: '1.2rem' }} />
{gettext(' Source and Target database server must be of the same major version.')}<br />
<strong>{gettext(' Database Compare:')}</strong>

View File

@ -9,13 +9,13 @@
import React, { createContext, useMemo, useRef } from 'react';
import { styled } from '@mui/material/styles';
import PropTypes from 'prop-types';
import {DividerBox} from 'rc-dock';
import url_for from 'sources/url_for';
import { Box } from '@mui/material';
import { makeStyles } from '@mui/styles';
import { Results } from './Results';
import { SchemaDiffCompare } from './SchemaDiffCompare';
@ -24,21 +24,22 @@ import getApiInstance, { callFetch } from '../../../../../static/js/api_instance
import { useModal } from '../../../../../static/js/helpers/ModalProvider';
import usePreferences from '../../../../../preferences/static/js/store';
export const SchemaDiffEventsContext = createContext();
export const SchemaDiffContext = createContext();
const useStyles = makeStyles((theme) => ({
resultPanle: {
const StyledBox = styled(Box)(({theme}) => ({
'& .SchemaDiff-resultPanel': {
backgroundColor: theme.palette.default.main,
zIndex: 5,
border: '1px solid ' + theme.otherVars.borderColor,
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
height: 0,
overflow: 'hidden',
height: 0,
},
comparePanel:{
'& .SchemaDiff-comparePanel': {
overflow: 'hidden',
border: '1px solid ' + theme.otherVars.borderColor,
display: 'flex',
@ -49,7 +50,6 @@ const useStyles = makeStyles((theme) => ({
}));
export default function SchemaDiffComponent({params}) {
const classes = useStyles();
const eventBus = useRef(new EventBus());
const containerRef = React.useRef(null);
const api = getApiInstance();
@ -85,14 +85,14 @@ export default function SchemaDiffComponent({params}) {
return (
<SchemaDiffContext.Provider value={schemaDiffContextValue}>
<SchemaDiffEventsContext.Provider value={eventBus.current}>
<Box display="flex" flexDirection="column" flexGrow="1" height="100%" tabIndex="0" style={{minHeight: 80}}>
<StyledBox display="flex" flexDirection="column" flexGrow="1" height="100%" tabIndex="0" style={{minHeight: 80}}>
<DividerBox mode='vertical' style={{flexGrow: 1}}>
<div className={classes.comparePanel} id="schema-diff-compare-container" ref={containerRef}><SchemaDiffCompare params={params} /></div>
<div className={classes.resultPanle} id="schema-diff-result-container">
<div className='SchemaDiff-comparePanel' id="schema-diff-compare-container" ref={containerRef}><SchemaDiffCompare params={params} /></div>
<div className='SchemaDiff-resultPanel' id="schema-diff-result-container">
<Results />
</div>
</DividerBox>
</Box>
</StyledBox>
</SchemaDiffEventsContext.Provider>
</SchemaDiffContext.Provider>
);

View File

@ -7,7 +7,7 @@
//
//////////////////////////////////////////////////////////////
import { Box } from '@mui/material';
import { makeStyles } from '@mui/styles';
import { styled } from '@mui/material/styles';
import React, { useState, useMemo, useCallback } from 'react';
import PropTypes from 'prop-types';
import HelpIcon from '@mui/icons-material/HelpRounded';
@ -16,84 +16,87 @@ import pgAdmin from 'sources/pgadmin';
import gettext from 'sources/gettext';
import url_for from 'sources/url_for';
import Loader from 'sources/components/Loader';
import clsx from 'clsx';
import getApiInstance, { parseApiError } from '../../../../static/js/api_instance';
import { PrimaryButton, PgIconButton } from '../../../../static/js/components/Buttons';
import { useModalStyles } from '../../../../static/js/helpers/ModalProvider';
import { ModalContent } from '../../../../static/js/components/ModalContent';
import { FormFooterMessage, InputSelect, InputText, MESSAGE_TYPE } from '../../../../static/js/components/FormComponents';
import PgReactDataGrid from '../../../../static/js/components/PgReactDataGrid';
const pgBrowser = pgAdmin.Browser;
const useStyles = makeStyles((theme)=>({
grid: {
fontSize: '13px',
'& .rdg-header-row': {
'& .rdg-cell': {
padding: '0px 4px',
}
},
'& .rdg-cell': {
padding: '0px 4px',
'&[aria-colindex="1"]': {
padding: '0px 4px',
'&.rdg-editor-container': {
padding: '0px',
},
}
}
},
toolbar: {
const StyledBox = styled(Box)(({theme}) => ({
'& .SearchObjects-toolbar': {
padding: '4px',
display: 'flex',
...theme.mixins.panelBorder?.bottom,
'& .SearchObjects-inputSearch': {
lineHeight: 1,
},
'& .SearchObjects-Btnmargin': {
marginLeft: '0.25rem',
},
},
inputSearch: {
lineHeight: 1,
},
footer1: {
'& .SearchObjects-footer1': {
justifyContent: 'space-between',
padding: '4px 8px',
display: 'flex',
alignItems: 'center',
borderTop: `1px solid ${theme.otherVars.inputBorderColor}`,
},
footer: {
'&.SearchObjects-footer': {
borderTop: `1px solid ${theme.otherVars.inputBorderColor} !important`,
padding: '0.5rem',
display: 'flex',
width: '100%',
background: theme.otherVars.headerBg,
},
gridCell: {
display: 'inline-block',
height: '1.3rem',
width: '1.3rem',
'& .SearchObjects-grid': {
fontSize: '13px !important',
'& .rdg-header-row': {
'& .rdg-cell': {
padding: '0px 4px !important',
}
},
'& .rdg-cell': {
padding: '0px 4px',
'&[aria-colindex="1"]': {
padding: '0px 4px !important',
'&.rdg-editor-container': {
padding: '0px',
},
},
'& .SearchObjects-textWrap': {
textOverflow: 'ellipsis',
overflow: 'hidden'
},
'& .SearchObjects-cellMuted': {
color: `${theme.otherVars.textMuted} !important`,
cursor: 'default !important',
},
'& .SearchObjects-gridCell': {
display: 'inline-block',
height: '1.3rem',
width: '1.3rem',
'& .SearchObjects-funcArgs': {
cursor: 'pointer',
},
},
}
},
funcArgs: {
cursor: 'pointer',
},
cellMuted: {
color: `${theme.otherVars.textMuted} !important`,
cursor: 'default !important',
},
textWrap: {
textOverflow: 'ellipsis',
overflow: 'hidden'
}
}));
const pgBrowser = pgAdmin.Browser;
function ObjectNameFormatter({row}) {
const classes = useStyles();
return (
<div className='rdg-cell-value'>
<Box className={row.show_node ? '' : classes.cellMuted}>
<span className={clsx(classes.gridCell, row.icon)}></span>
<StyledBox className={row.show_node ? '' : 'SearchObjects-cellMuted'}>
<span className={('SearchObjects-gridCell ' + row.icon)}></span>
{row.name}
{row.other_info != null && row.other_info != '' && (
<span tabIndex="-1" className={classes.funcArgs} onClick={()=>{row.showArgs = true;}} onKeyDown={()=>{/* no need */}}> {row?.showArgs ? `(${row.other_info})` : '(...)'}</span>
<span tabIndex="-1" className='SearchObjects-funcArgs' onClick={()=>{row.showArgs = true;}} onKeyDown={()=>{/* no need */}}> {row?.showArgs ? `(${row.other_info})` : '(...)'}</span>
)}
</Box>
</StyledBox>
</div>
);
}
@ -102,7 +105,7 @@ ObjectNameFormatter.propTypes = {
};
function TypePathFormatter({row, column}) {
const classes = useStyles();
let val = '';
if(column.key == 'type') {
@ -112,7 +115,7 @@ function TypePathFormatter({row, column}) {
}
return (
<Box className={clsx(classes.textWrap, row.show_node ? '' : classes.cellMuted)}>{val}</Box>
<Box className={'SearchObjects-textWrap ' + (row.show_node ? '' : 'SearchObjects-cellMuted')}>{val}</Box>
);
}
TypePathFormatter.propTypes = {
@ -270,8 +273,6 @@ function getComparator(sortColumn) {
};
}
export default function SearchObjects({nodeData}) {
const classes = useStyles();
const modalClasses = useModalStyles();
const [type, setType] = React.useState('all');
const [loaderText, setLoaderText] = useState('');
const [search, setSearch] = useState('');
@ -392,21 +393,21 @@ export default function SearchObjects({nodeData}) {
};
return (
<Box display="flex" flexDirection="column" height="100%" className={modalClasses.container}>
<Box flexGrow="1" display="flex" flexDirection="column" position="relative" overflow="hidden">
<ModalContent>
<StyledBox>
<Loader message={loaderText} />
<Box className={classes.toolbar}>
<InputText type="search" className={classes.inputSearch} data-label="search" placeholder={gettext('Type at least 3 characters')} value={search} onChange={setSearch} onKeyPress={onEnterPress}/>
<Box style={{marginLeft: '4px', width: '50%'}}>
<Box className='SearchObjects-toolbar'>
<InputText type="search" className='SearchObjects-inputSearch' data-label="search" placeholder={gettext('Type at least 3 characters')} value={search} onChange={setSearch} onKeyPress={onEnterPress}/>
<Box sx={{marginLeft: '4px', width: '50%'}}>
<InputSelect value={type} controlProps={{allowClear: false}} options={typeOptions} onChange={(v)=>setType(v)}/>
</Box>
<PrimaryButton style={{width: '120px'}} data-test="search" className={modalClasses.margin} startIcon={<SearchRoundedIcon />}
<PrimaryButton style={{width: '120px'}} data-test="search" className='SearchObjects-Btnmargin' startIcon={<SearchRoundedIcon />}
onClick={onSearch} disabled={search.length < 3}>{gettext('Search')}</PrimaryButton>
</Box>
<Box flexGrow="1" display="flex" flexDirection="column" position="relative" overflow="hidden">
<PgReactDataGrid
id="searchobjects"
className={classes.grid}
className='SearchObjects-grid'
hasSelectColumn={false}
columns={columns}
rows={sortedItems}
@ -423,17 +424,15 @@ export default function SearchObjects({nodeData}) {
onItemEnter={onItemEnter}
/>
</Box>
<Box className={classes.footer1}>
<Box className='SearchObjects-footer1'>
<Box>{footerText}</Box>
</Box>
<FormFooterMessage type={MESSAGE_TYPE.ERROR} message={errorMsg} closable onClose={()=>setErrorMsg('')} />
</Box>
<Box className={classes.footer}>
<Box>
<PgIconButton data-test="dialog-help" onClick={onDialogHelp} icon={<HelpIcon />} title={gettext('Help for this dialog.')} />
</Box>
</Box>
</Box>
</StyledBox>
<StyledBox className='SearchObjects-footer'>
<PgIconButton data-test="dialog-help" onClick={onDialogHelp} icon={<HelpIcon />} title={gettext('Help for this dialog.')} />
</StyledBox>
</ModalContent>
);
}

Some files were not shown because too many files have changed in this diff Show More