Port change ownership dialog to React. Fixes #7590

pull/90/head
Akshay Joshi 2022-08-05 14:05:53 +05:30
parent 060d7ba46b
commit 53887c32bf
18 changed files with 264 additions and 390 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -15,6 +15,7 @@ Housekeeping
************ ************
| `Issue #7567 <https://redmine.postgresql.org/issues/7567>`_ - Port About dialog to React. | `Issue #7567 <https://redmine.postgresql.org/issues/7567>`_ - Port About dialog to React.
| `Issue #7590 <https://redmine.postgresql.org/issues/7590>`_ - Port change ownership dialog to React.
| `Issue #7595 <https://redmine.postgresql.org/issues/7595>`_ - Update the container base image to Alpine 3.16 (with Python 3.10.5). | `Issue #7595 <https://redmine.postgresql.org/issues/7595>`_ - Update the container base image to Alpine 3.16 (with Python 3.10.5).
Bug fixes Bug fixes

View File

@ -967,7 +967,7 @@ class ServerNode(PGChildNodeView):
'shared': server.shared if config.SERVER_MODE else None, 'shared': server.shared if config.SERVER_MODE else None,
'username': server.username, 'username': server.username,
'gid': str(server.servergroup_id), 'gid': str(server.servergroup_id),
'group-name': sg.name, 'group-name': sg.name if (sg and sg.name) else gettext('Servers'),
'comment': server.comment, 'comment': server.comment,
'role': server.role, 'role': server.role,
'connected': connected, 'connected': connected,

View File

@ -12,7 +12,7 @@ import { getNodePrivilegeRoleSchema } from '../../../static/js/privilege.ui';
import { getNodeVariableSchema } from '../../../static/js/variable.ui'; import { getNodeVariableSchema } from '../../../static/js/variable.ui';
import DatabaseSchema from './database.ui'; import DatabaseSchema from './database.ui';
import Notify from '../../../../../../static/js/helpers/Notifier'; import Notify from '../../../../../../static/js/helpers/Notifier';
import { showServerPassword } from '../../../../../static/js/password_dialogs'; import { showServerPassword } from '../../../../../../static/js/Dialogs/index';
define('pgadmin.node.database', [ define('pgadmin.node.database', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'sources/gettext', 'sources/url_for', 'jquery', 'underscore',

View File

@ -10,7 +10,7 @@
import { getNodeListById } from '../../../../static/js/node_ajax'; import { getNodeListById } from '../../../../static/js/node_ajax';
import ServerSchema from './server.ui'; import ServerSchema from './server.ui';
import Notify from '../../../../../static/js/helpers/Notifier'; import Notify from '../../../../../static/js/helpers/Notifier';
import { showServerPassword, showChangeServerPassword, showNamedRestorePoint } from '../../../../static/js/password_dialogs'; import { showServerPassword, showChangeServerPassword, showNamedRestorePoint } from '../../../../../static/js/Dialogs/index';
define('pgadmin.node.server', [ define('pgadmin.node.server', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'sources/gettext', 'sources/url_for', 'jquery', 'underscore',

View File

@ -9,7 +9,7 @@
import { generateNodeUrl } from './node_ajax'; import { generateNodeUrl } from './node_ajax';
import Notify, {initializeModalProvider, initializeNotifier} from '../../../static/js/helpers/Notifier'; import Notify, {initializeModalProvider, initializeNotifier} from '../../../static/js/helpers/Notifier';
import { checkMasterPassword } from './password_dialogs'; import { checkMasterPassword } from '../../../static/js/Dialogs/index';
define('pgadmin.browser', [ define('pgadmin.browser', [
'sources/gettext', 'sources/url_for', 'require', 'jquery', 'underscore', 'sources/gettext', 'sources/url_for', 'require', 'jquery', 'underscore',

View File

@ -0,0 +1,88 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import { makeStyles } from '@material-ui/core';
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';
import { isEmptyString } from 'sources/validators';
class ChangeOwnershipSchema extends BaseUISchema {
constructor(deletedUser, adminUserList, noOfSharedServers) {
super({
newUser: '',
});
this.deletedUser = deletedUser;
this.adminUserList = adminUserList;
this.noOfSharedServers = noOfSharedServers;
}
get baseFields() {
let self = this;
return [
{
id: 'note', type: 'note',
text: gettext('Select the user that will take ownership of the shared servers created by <b>' + self.deletedUser + '</b>. <b>' + self.noOfSharedServers + '</b> shared servers are currently owned by this user.'),
}, {
id: 'newUser', label: gettext('User'),
type: 'select', controlProps: {allowClear: true},
options: self.adminUserList,
helpMessage: gettext('Note: If no user is selected, the shared servers will be deleted.')
}
];
}
validate(state) {
let obj = this;
/* mview definition validation*/
if (isEmptyString(state.newUser)) {
obj.warningText = gettext('The shared servers owned by <b>'+ obj.deletedUser +'</b> will be deleted. Do you wish to continue?');
} else {
obj.warningText = null;
}
}
}
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
formType={'dialog'}
getInitData={() => { /*This is intentional (SonarQube)*/ }}
schema={objChangeOwnership}
viewHelperProps={{
mode: 'create',
}}
customSaveBtnName={'Change'}
onSave={onSave}
onClose={onClose}
hasSQL={false}
disableSqlHelp={true}
disableDialogHelp={true}
isTabView={false}
formClassName={classes.root}
/>;
}
ChangeOwnershipContent.propTypes = {
onSave: PropTypes.func,
onClose: PropTypes.func,
currentUser: PropTypes.string,
userList: PropTypes.array,
noOfSharedServers: PropTypes.number,
deletedUser: PropTypes.string
};

View File

@ -11,8 +11,8 @@ import { makeStyles } from '@material-ui/core';
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import gettext from 'sources/gettext'; import gettext from 'sources/gettext';
import BaseUISchema from '../../../static/js/SchemaView/base_schema.ui'; import BaseUISchema from '../SchemaView/base_schema.ui';
import SchemaView from '../../../static/js/SchemaView'; import SchemaView from '../SchemaView';
class ChangePasswordSchema extends BaseUISchema { class ChangePasswordSchema extends BaseUISchema {
constructor(user, isPgpassFileUsed) { constructor(user, isPgpassFileUsed) {
@ -82,6 +82,7 @@ export default function ChangePasswordContent({onSave, onClose, userName, isPgpa
viewHelperProps={{ viewHelperProps={{
mode: 'create', mode: 'create',
}} }}
customSaveBtnName={'Change'}
onSave={onSave} onSave={onSave}
onClose={onClose} onClose={onClose}
hasSQL={false} hasSQL={false}

View File

@ -10,12 +10,12 @@
import React, { useState, useRef, useEffect } from 'react'; import React, { useState, useRef, useEffect } from 'react';
import gettext from 'sources/gettext'; import gettext from 'sources/gettext';
import { Box } from '@material-ui/core'; import { Box } from '@material-ui/core';
import { DefaultButton, PrimaryButton } from '../../../static/js/components/Buttons'; import { DefaultButton, PrimaryButton } from '../components/Buttons';
import CloseIcon from '@material-ui/icons/CloseRounded'; import CloseIcon from '@material-ui/icons/CloseRounded';
import CheckRoundedIcon from '@material-ui/icons/CheckRounded'; import CheckRoundedIcon from '@material-ui/icons/CheckRounded';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useModalStyles } from '../../../static/js/helpers/ModalProvider'; import { useModalStyles } from '../helpers/ModalProvider';
import { FormFooterMessage, InputCheckbox, InputText, MESSAGE_TYPE } from '../../../static/js/components/FormComponents'; import { FormFooterMessage, InputCheckbox, InputText, MESSAGE_TYPE } from '../components/FormComponents';
export default function ConnectServerContent({closeModal, data, onOK, setHeight}) { export default function ConnectServerContent({closeModal, data, onOK, setHeight}) {
const classes = useModalStyles(); const classes = useModalStyles();

View File

@ -18,9 +18,9 @@ import DeleteForeverIcon from '@material-ui/icons/DeleteForever';
import CheckRoundedIcon from '@material-ui/icons/CheckRounded'; import CheckRoundedIcon from '@material-ui/icons/CheckRounded';
import HelpIcon from '@material-ui/icons/Help'; import HelpIcon from '@material-ui/icons/Help';
import { DefaultButton, PrimaryButton, PgIconButton } from '../../../static/js/components/Buttons'; import { DefaultButton, PrimaryButton, PgIconButton } from '../components/Buttons';
import { useModalStyles } from '../../../static/js/helpers/ModalProvider'; import { useModalStyles } from '../helpers/ModalProvider';
import { FormFooterMessage, InputText, MESSAGE_TYPE } from '../../../static/js/components/FormComponents'; import { FormFooterMessage, InputText, MESSAGE_TYPE } from '../components/FormComponents';
export default function MasterPasswordContent({ closeModal, onResetPassowrd, onOK, onCancel, setHeight, isPWDPresent, data}) { export default function MasterPasswordContent({ closeModal, onResetPassowrd, onOK, onCancel, setHeight, isPWDPresent, data}) {
const classes = useModalStyles(); const classes = useModalStyles();

View File

@ -10,12 +10,12 @@
import React, { useState, useRef, useEffect } from 'react'; import React, { useState, useRef, useEffect } from 'react';
import gettext from 'sources/gettext'; import gettext from 'sources/gettext';
import { Box } from '@material-ui/core'; import { Box } from '@material-ui/core';
import { DefaultButton, PrimaryButton } from '../../../static/js/components/Buttons'; import { DefaultButton, PrimaryButton } from '../components/Buttons';
import CloseIcon from '@material-ui/icons/CloseRounded'; import CloseIcon from '@material-ui/icons/CloseRounded';
import CheckRoundedIcon from '@material-ui/icons/CheckRounded'; import CheckRoundedIcon from '@material-ui/icons/CheckRounded';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useModalStyles } from '../../../static/js/helpers/ModalProvider'; import { useModalStyles } from '../helpers/ModalProvider';
import { InputText } from '../../../static/js/components/FormComponents'; import { InputText } from '../components/FormComponents';
import { isEmptyString } from '../../../static/js/validators'; import { isEmptyString } from '../../../static/js/validators';
export default function NamedRestoreContent({closeModal, onOK, setHeight}) { export default function NamedRestoreContent({closeModal, onOK, setHeight}) {

View File

@ -15,29 +15,51 @@ import Theme from 'sources/Theme';
import url_for from 'sources/url_for'; import url_for from 'sources/url_for';
import gettext from 'sources/gettext'; import gettext from 'sources/gettext';
import getApiInstance from '../../../static/js/api_instance'; import getApiInstance from '../api_instance';
import Notify from '../helpers/Notifier';
import MasterPasswordContent from './MasterPassowrdContent'; import MasterPasswordContent from './MasterPassowrdContent';
import ChangePasswordContent from './ChangePassowrdContent'; import ChangePasswordContent from './ChangePassowrdContent';
import NamedRestoreContent from './NamedRestoreContent'; import NamedRestoreContent from './NamedRestoreContent';
import Notify from '../../../static/js/helpers/Notifier'; import ChangeOwnershipContent from './ChangeOwnershipContent';
function setNewSize(panel, width, height) { function mountDialog(title, getDialogContent, docker=undefined) {
// Add height of the header // Register dialog panel
let newHeight = height + 31; var panel;
// Set min and max size of the panel if (docker) {
panel.minSize(width, newHeight); pgAdmin.Browser.Node.registerUtilityPanel(docker);
panel.maxSize(width, newHeight); panel = pgAdmin.Browser.Node.addUtilityPanel(pgAdmin.Browser.stdW.md, undefined, docker);
panel.maximisable(false); } else {
/* No other way to update size, below is the only way */ pgAdmin.Browser.Node.registerUtilityPanel();
panel._parent._size.x = width; panel = pgAdmin.Browser.Node.addUtilityPanel(pgAdmin.Browser.stdW.md);
panel._parent._size.y = newHeight; }
panel._parent.__update();
var j = panel.$container.find('.obj_properties').first();
panel.title(title);
const onClose = ()=> {
ReactDOM.unmountComponentAtNode(j[0]);
panel.close();
};
const setNewSize = (width, height)=> {
// Add height of the header
let newHeight = height + 31;
// Set min and max size of the panel
panel.minSize(width, newHeight);
panel.maxSize(width, newHeight);
panel.maximisable(false);
/* No other way to update size, below is the only way */
panel._parent._size.x = width;
panel._parent._size.y = newHeight;
panel._parent.__update();
};
ReactDOM.render(getDialogContent(onClose, setNewSize), j[0]);
} }
// This functions is used to show the connect server password dialog. // This functions is used to show the connect server password dialog.
export function showServerPassword() { export function showServerPassword() {
var pgBrowser = pgAdmin.Browser, var title = arguments[0],
title = arguments[0],
formJson = arguments[1], formJson = arguments[1],
nodeObj = arguments[2], nodeObj = arguments[2],
nodeData = arguments[3], nodeData = arguments[3],
@ -47,20 +69,14 @@ export function showServerPassword() {
onSuccess = arguments[7], onSuccess = arguments[7],
onFailure = arguments[8]; onFailure = arguments[8];
// Register dialog panel mountDialog(title, (onClose, setNewSize)=> {
pgBrowser.Node.registerUtilityPanel(); return <Theme>
var panel = pgBrowser.Node.addUtilityPanel(pgBrowser.stdW.md),
j = panel.$container.find('.obj_properties').first();
panel.title(title);
ReactDOM.render(
<Theme>
<ConnectServerContent <ConnectServerContent
setHeight={(containerHeight)=>{ setHeight={(containerHeight)=>{
setNewSize(panel, pgBrowser.stdW.md, containerHeight); setNewSize(pgAdmin.Browser.stdW.md, containerHeight);
}} }}
closeModal={()=>{ closeModal={()=>{
panel.close(); onClose();
}} }}
data={formJson} data={formJson}
onOK={(formData)=>{ onOK={(formData)=>{
@ -74,7 +90,7 @@ export function showServerPassword() {
api.post(_url, formData) api.post(_url, formData)
.then(res=>{ .then(res=>{
panel.close(); onClose();
return onSuccess( return onSuccess(
res.data, nodeObj, nodeData, treeNodeInfo, itemNodeData, status res.data, nodeObj, nodeData, treeNodeInfo, itemNodeData, status
); );
@ -87,14 +103,14 @@ export function showServerPassword() {
}); });
}} }}
/> />
</Theme>, j[0]); </Theme>;
});
} }
// This functions is used to show the connect server password dialog when // This functions is used to show the connect server password dialog when
// launch from Schema Diff tool. // launch from Schema Diff tool.
export function showSchemaDiffServerPassword() { export function showSchemaDiffServerPassword() {
var pgBrowser = pgAdmin.Browser, var docker = arguments[0],
docker = arguments[0],
title = arguments[1], title = arguments[1],
formJson = arguments[2], formJson = arguments[2],
serverID = arguments[3], serverID = arguments[3],
@ -102,20 +118,14 @@ export function showSchemaDiffServerPassword() {
onSuccess = arguments[5], onSuccess = arguments[5],
onFailure = arguments[6]; onFailure = arguments[6];
// Register dialog panel mountDialog(title, (onClose, setNewSize)=> {
pgBrowser.Node.registerUtilityPanel(docker); return <Theme>
var panel = pgBrowser.Node.addUtilityPanel(pgBrowser.stdW.md, undefined, docker),
j = panel.$container.find('.obj_properties').first();
panel.title(title);
ReactDOM.render(
<Theme>
<ConnectServerContent <ConnectServerContent
setHeight={(containerHeight)=>{ setHeight={(containerHeight)=>{
setNewSize(panel, pgBrowser.stdW.md, containerHeight); setNewSize(pgAdmin.Browser.stdW.md, containerHeight);
}} }}
closeModal={()=>{ closeModal={()=>{
panel.close(); onClose();
}} }}
data={formJson} data={formJson}
onOK={(formData)=>{ onOK={(formData)=>{
@ -124,7 +134,7 @@ export function showSchemaDiffServerPassword() {
api.post(_url, formData) api.post(_url, formData)
.then(res=>{ .then(res=>{
panel.close(); onClose();
return onSuccess(res.data, successCallback); return onSuccess(res.data, successCallback);
}) })
.catch((err)=>{ .catch((err)=>{
@ -134,7 +144,8 @@ export function showSchemaDiffServerPassword() {
}); });
}} }}
/> />
</Theme>, j[0]); </Theme>;
}, docker);
} }
function masterPassCallbacks(masterpass_callback_queue) { function masterPassCallbacks(masterpass_callback_queue) {
@ -160,26 +171,18 @@ export function checkMasterPassword(data, masterpass_callback_queue, cancel_call
// This functions is used to show the master password dialog. // This functions is used to show the master password dialog.
export function showMasterPassword(isPWDPresent, errmsg=null, masterpass_callback_queue, cancel_callback) { export function showMasterPassword(isPWDPresent, errmsg=null, masterpass_callback_queue, cancel_callback) {
const api = getApiInstance(); const api = getApiInstance();
var pgBrowser = pgAdmin.Browser;
// Register dialog panel
pgBrowser.Node.registerUtilityPanel();
var panel = pgBrowser.Node.addUtilityPanel(pgBrowser.stdW.md),
j = panel.$container.find('.obj_properties').first();
let title = isPWDPresent ? gettext('Unlock Saved Passwords') : gettext('Set Master Password'); let title = isPWDPresent ? gettext('Unlock Saved Passwords') : gettext('Set Master Password');
panel.title(title);
ReactDOM.render( mountDialog(title, (onClose, setNewSize)=> {
<Theme> return <Theme>
<MasterPasswordContent <MasterPasswordContent
isPWDPresent= {isPWDPresent} isPWDPresent= {isPWDPresent}
data={{'errmsg': errmsg}} data={{'errmsg': errmsg}}
setHeight={(containerHeight) => { setHeight={(containerHeight) => {
setNewSize(panel, pgBrowser.stdW.md, containerHeight); setNewSize(pgAdmin.Browser.stdW.md, containerHeight);
}} }}
closeModal={() => { closeModal={() => {
panel.close(); onClose();
}} }}
onResetPassowrd={()=>{ onResetPassowrd={()=>{
Notify.confirm(gettext('Reset Master Password'), Notify.confirm(gettext('Reset Master Password'),
@ -190,7 +193,7 @@ export function showMasterPassword(isPWDPresent, errmsg=null, masterpass_callbac
api.delete(_url) api.delete(_url)
.then(() => { .then(() => {
panel.close(); onClose();
showMasterPassword(false, null, masterpass_callback_queue, cancel_callback); showMasterPassword(false, null, masterpass_callback_queue, cancel_callback);
}) })
.catch((err) => { .catch((err) => {
@ -205,32 +208,26 @@ export function showMasterPassword(isPWDPresent, errmsg=null, masterpass_callbac
cancel_callback?.(); cancel_callback?.();
}} }}
onOK={(formData) => { onOK={(formData) => {
panel.close(); onClose();
checkMasterPassword(formData, masterpass_callback_queue, cancel_callback); checkMasterPassword(formData, masterpass_callback_queue, cancel_callback);
}} }}
/> />
</Theme>, j[0]); </Theme>;
});
} }
export function showChangeServerPassword() { export function showChangeServerPassword() {
var pgBrowser = pgAdmin.Browser, var title = arguments[0],
title = arguments[0],
nodeData = arguments[1], nodeData = arguments[1],
nodeObj = arguments[2], nodeObj = arguments[2],
itemNodeData = arguments[3], itemNodeData = arguments[3],
isPgPassFileUsed = arguments[4]; isPgPassFileUsed = arguments[4];
// Register dialog panel mountDialog(title, (onClose)=> {
pgBrowser.Node.registerUtilityPanel(); return <Theme>
var panel = pgBrowser.Node.addUtilityPanel(pgBrowser.stdW.md),
j = panel.$container.find('.obj_properties').first();
panel.title(title);
ReactDOM.render(
<Theme>
<ChangePasswordContent <ChangePasswordContent
onClose={()=>{ onClose={()=>{
panel.close(); onClose();
}} }}
onSave={(isNew, data)=>{ onSave={(isNew, data)=>{
return new Promise((resolve, reject)=>{ return new Promise((resolve, reject)=>{
@ -251,7 +248,7 @@ export function showChangeServerPassword() {
} }
resolve(respData.data); resolve(respData.data);
panel.close(); onClose();
}) })
.catch((error)=>{ .catch((error)=>{
reject(error); reject(error);
@ -261,30 +258,24 @@ export function showChangeServerPassword() {
userName={nodeData.user.name} userName={nodeData.user.name}
isPgpassFileUsed={isPgPassFileUsed} isPgpassFileUsed={isPgPassFileUsed}
/> />
</Theme>, j[0]); </Theme>;
});
} }
export function showNamedRestorePoint() { export function showNamedRestorePoint() {
var pgBrowser = pgAdmin.Browser, var title = arguments[0],
title = arguments[0],
nodeData = arguments[1], nodeData = arguments[1],
nodeObj = arguments[2], nodeObj = arguments[2],
itemNodeData = arguments[3]; itemNodeData = arguments[3];
// Register dialog panel mountDialog(title, (onClose, setNewSize)=> {
pgBrowser.Node.registerUtilityPanel(); return <Theme>
var panel = pgBrowser.Node.addUtilityPanel(pgBrowser.stdW.md),
j = panel.$container.find('.obj_properties').first();
panel.title(title);
ReactDOM.render(
<Theme>
<NamedRestoreContent <NamedRestoreContent
setHeight={(containerHeight)=>{ setHeight={(containerHeight)=>{
setNewSize(panel, pgBrowser.stdW.md, containerHeight); setNewSize(pgAdmin.Browser.stdW.md, containerHeight);
}} }}
closeModal={()=>{ closeModal={()=>{
panel.close(); onClose();
}} }}
onOK={(formData)=>{ onOK={(formData)=>{
const api = getApiInstance(); const api = getApiInstance();
@ -292,7 +283,7 @@ export function showNamedRestorePoint() {
api.post(_url, formData) api.post(_url, formData)
.then(res=>{ .then(res=>{
panel.close(); onClose();
Notify.success(res.data.data.result); Notify.success(res.data.data.result);
}) })
.catch(function(xhr, status, error) { .catch(function(xhr, status, error) {
@ -300,5 +291,59 @@ export function showNamedRestorePoint() {
}); });
}} }}
/> />
</Theme>, j[0]); </Theme>;
});
}
export function showChangeOwnership() {
var title = arguments[0],
userList = arguments[1],
noOfSharedServers = arguments[2],
deletedUser = arguments[3],
destroyUserManagement = arguments[4];
// Render Preferences component
Notify.showModal(title, (onClose) => {
return <ChangeOwnershipContent
onClose={()=>{
onClose();
}}
onSave={(isNew, data)=>{
const api = getApiInstance();
return new Promise((resolve, reject)=>{
if (data.newUser == '') {
api.delete(url_for('user_management.user', {uid: deletedUser['uid']}))
.then(() => {
Notify.success(gettext('User deleted.'));
onClose();
destroyUserManagement();
resolve();
})
.catch((err)=>{
Notify.error(err);
reject(err);
});
} else {
let newData = {'new_owner': `${data.newUser}`, 'old_owner': `${deletedUser['uid']}`};
api.post(url_for('user_management.change_owner'), newData)
.then(({data: respData})=>{
Notify.success(gettext(respData.info));
onClose();
destroyUserManagement();
resolve(respData.data);
})
.catch((err)=>{
reject(err);
});
}
});
}}
userList = {userList}
noOfSharedServers = {noOfSharedServers}
deletedUser = {deletedUser['name']}
/>;
},
{ isFullScreen: false, isResizeable: true, showFullScreen: true, isFullWidth: true,
dialogWidth: pgAdmin.Browser.stdW.md, dialogHeight: pgAdmin.Browser.stdH.md});
} }

View File

@ -8,7 +8,7 @@
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
import { Box, Dialog, DialogContent, DialogTitle, makeStyles, Paper } from '@material-ui/core'; import { Box, Dialog, DialogContent, DialogTitle, makeStyles, Paper } from '@material-ui/core';
import React, { useState } from 'react'; import React, { useState, useMemo } from 'react';
import clsx from 'clsx'; import clsx from 'clsx';
import { getEpoch } from 'sources/utils'; import { getEpoch } from 'sources/utils';
import { DefaultButton, PgIconButton, PrimaryButton } from '../components/Buttons'; import { DefaultButton, PgIconButton, PrimaryButton } from '../components/Buttons';
@ -296,7 +296,7 @@ function ModalContainer({ id, title, content, dialogHeight, dialogWidth, onClose
</Box> </Box>
</DialogTitle> </DialogTitle>
<DialogContent height="100%"> <DialogContent height="100%">
{content(closeModal)} {useMemo(()=>{ return content(closeModal); }, [])}
</DialogContent> </DialogContent>
</Dialog> </Dialog>
); );

View File

@ -19,7 +19,7 @@ import 'pgadmin.tools.sqleditor';
import pgWindow from 'sources/window'; import pgWindow from 'sources/window';
import _ from 'underscore'; import _ from 'underscore';
import Notify from '../../../../static/js/helpers/Notifier'; import Notify from '../../../../static/js/helpers/Notifier';
import { showSchemaDiffServerPassword } from '../../../../browser/static/js/password_dialogs'; import { showSchemaDiffServerPassword } from '../../../../static/js/Dialogs/index';
import { SchemaDiffSelect2Control, SchemaDiffHeaderView, import { SchemaDiffSelect2Control, SchemaDiffHeaderView,

View File

@ -37,7 +37,7 @@ import PropTypes from 'prop-types';
import { retrieveNodeName } from '../show_view_data'; import { retrieveNodeName } from '../show_view_data';
import 'wcdocker'; import 'wcdocker';
import { useModal } from '../../../../../static/js/helpers/ModalProvider'; import { useModal } from '../../../../../static/js/helpers/ModalProvider';
import ConnectServerContent from '../../../../../browser/static/js/ConnectServerContent'; import ConnectServerContent from '../../../../../static/js/Dialogs/ConnectServerContent';
export const QueryToolContext = React.createContext(); export const QueryToolContext = React.createContext();
export const QueryToolConnectionContext = React.createContext(); export const QueryToolConnectionContext = React.createContext();
@ -362,10 +362,6 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN
const handleApiError = (error, handleParams)=>{ const handleApiError = (error, handleParams)=>{
if(error.response && pgAdmin.Browser?.UserManagement?.isPgaLoginRequired(error.response)) {
return pgAdmin.Browser.UserManagement.pgaLogin();
}
if(error.response?.status == 503 && error.response.data?.info == 'CONNECTION_LOST') { if(error.response?.status == 503 && error.response.data?.info == 'CONNECTION_LOST') {
// We will display re-connect dialog, no need to display error message again // We will display re-connect dialog, no need to display error message again
modal.confirm( modal.confirm(

View File

@ -16,7 +16,7 @@ import url_for from 'sources/url_for';
import _ from 'lodash'; import _ from 'lodash';
import { flattenSelectOptions } from '../../../../../../static/js/components/FormComponents'; import { flattenSelectOptions } from '../../../../../../static/js/components/FormComponents';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ConnectServerContent from '../../../../../../browser/static/js/ConnectServerContent'; import ConnectServerContent from '../../../../../../static/js/Dialogs/ConnectServerContent';
class NewConnectionSchema extends BaseUISchema { class NewConnectionSchema extends BaseUISchema {
constructor(api, params, connectServer) { constructor(api, params, connectServer) {

View File

@ -496,9 +496,6 @@ def get_shared_servers(uid):
return internal_server_error(errormsg=str(e)) return internal_server_error(errormsg=str(e))
# @blueprint.route(
# '/admin_users', methods=['GET'], endpoint='admin_users'
# )
@blueprint.route( @blueprint.route(
'/admin_users/<int:uid>', methods=['GET'], endpoint='admin_users' '/admin_users/<int:uid>', methods=['GET'], endpoint='admin_users'
) )

View File

@ -8,6 +8,7 @@
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
import Notify from '../../../../static/js/helpers/Notifier'; import Notify from '../../../../static/js/helpers/Notifier';
import { showChangeOwnership } from '../../../../static/js/Dialogs/index';
define([ define([
'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'pgadmin.alertifyjs', 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'pgadmin.alertifyjs',
@ -94,104 +95,6 @@ define([
); );
}, },
isPgaLoginRequired(xhr) {
/* If responseJSON is undefined then it could be object of
* axios(Promise HTTP) response, so we should check accordingly.
*/
if (xhr.responseJSON === undefined && xhr.data !== undefined) {
return xhr.status === 401 && xhr.data &&
xhr.data.info &&
xhr.data.info === 'PGADMIN_LOGIN_REQUIRED';
}
return xhr.status === 401 && xhr.responseJSON &&
xhr.responseJSON.info &&
xhr.responseJSON.info === 'PGADMIN_LOGIN_REQUIRED';
},
// Callback to draw pgAdmin4 login dialog.
pgaLogin: function(url) {
var title = gettext('pgAdmin 4 login');
url = url || url_for('security.login');
if(!alertify.PgaLogin) {
alertify.dialog('PgaLogin' ,function factory() {
return {
main: function(alertTitle, alertUrl) {
this.set({
'title': alertTitle,
'url': alertUrl,
});
},
build: function() {
alertify.pgDialogBuild.apply(this);
},
settings:{
url: undefined,
},
setup:function() {
return {
buttons: [{
text: gettext('Close'), key: 27,
className: 'btn btn-secondary fa fa-lg fa-times pg-alertify-button',
attrs:{name:'close', type:'button'},
}],
// Set options for dialog
options: {
//disable both padding and overflow control.
padding : !1,
overflow: !1,
modal: true,
resizable: true,
maximizable: true,
pinnable: false,
closableByDimmer: false,
closable: false,
},
};
},
hooks: {
// Triggered when the dialog is closed
onclose: function() {
// Clear the view
return setTimeout((function() {
return alertify.PgaLogin().destroy();
}));
},
},
prepare: function() {
// create the iframe element
var self = this,
iframe = document.createElement('iframe'),
frameUrl = this.setting('url');
iframe.onload = function() {
var doc = this.contentDocument || this.contentWindow.document;
if (doc.location.href.indexOf(frameUrl) == -1) {
// login successful.
this.contentWindow.stop();
this.onload = null;
// close the dialog.
self.close();
pgBrowser.Events.trigger('pgadmin:user:logged-in');
}
};
iframe.frameBorder = 'no';
iframe.width = '100%';
iframe.height = '100%';
iframe.src = frameUrl;
// add it to the dialog
self.elements.content.appendChild(iframe);
},
};
});
}
alertify.PgaLogin(title, url).resizeTo(pgBrowser.stdW.md, pgBrowser.stdH.md);
},
is_editable: function(m) { is_editable: function(m) {
if (m instanceof Backbone.Collection) { if (m instanceof Backbone.Collection) {
return true; return true;
@ -551,191 +454,34 @@ define([
}, },
changeOwnership: function(res, uid) { changeOwnership: function(res, uid) {
let self = this; let self = this,
url = url_for('user_management.admin_users', {'uid': uid});
let ownershipSelect2Control = Backform.Select2Control.extend({ const destroyUserManagement = ()=>{
fetchData: function(){ alertify.UserManagement().destroy();
let that = this; };
let url = that.field.get('url');
url = url_for(url, {'uid': uid});
$.ajax({
url: url,
headers: {
'Cache-Control' : 'no-cache',
},
}).done(function (res_data) {
var transform = that.field.get('transform');
if(res_data.data.status){
let data = res_data.data.result.data;
if (transform && _.isFunction(transform)) {
that.field.set('options', transform.bind(that, data));
} else {
that.field.set('options', data);
}
} else {
if (transform && _.isFunction(transform)) {
that.field.set('options', transform.bind(that, []));
} else {
that.field.set('options', []);
}
}
Backform.Select2Control.prototype.render.apply(that, arguments);
}).fail(function(e){
let msg = '';
if(e.status == 404) {
msg = 'Unable to find url.';
} else {
msg = e.responseJSON.errormsg;
}
Notify.error(msg);
});
},
render: function() {
this.fetchData();
return Backform.Select2Control.prototype.render.apply(this, arguments);
},
onChange: function() {
Backform.Select2Control.prototype.onChange.apply(this, arguments);
},
});
let ownershipModel = pgBrowser.DataModel.extend({
schema: [
{
id: 'note_text_ch_owner',
control: Backform.NoteControl,
text: 'Select the user that will take ownership of the shared servers created by <b>' + self.model.get('username') + '</b>. <b>' + res['data'].shared_servers + '</b> shared servers are currently owned by this user.',
group: gettext('General'),
},
{
id: 'user',
name: 'user',
label: gettext('User'),
type: 'text',
editable: true,
select2: {
allowClear: true,
width: '100%',
first_empty: true,
},
control: ownershipSelect2Control,
url: 'user_management.admin_users',
helpMessage: gettext('Note: If no user is selected, the shared servers will be deleted.'),
}],
});
// Change shared server ownership before deleting the admin user
if (!alertify.changeOwnershipDialog) {
alertify.dialog('changeOwnershipDialog', function factory() {
let $container = $('<div class=\'change-ownership\'></div>');
return {
main: function(message) {
this.msg = message;
},
build: function() {
this.elements.content.appendChild($container.get(0));
alertify.pgDialogBuild.apply(this);
},
setup: function(){
return {
buttons: [
{
text: gettext('Cancel'),
key: 27,
className: 'btn btn-secondary fa fa-times pg-alertify-button',
'data-btn-name': 'cancel',
}, {
text: gettext('OK'),
key: 13,
className: 'btn btn-primary fa fa-check pg-alertify-button',
'data-btn-name': 'ok',
},
],
// Set options for dialog
options: {
title: 'Change ownership',
//disable both padding and overflow control.
padding: !1,
overflow: !1,
model: 0,
resizable: true,
maximizable: false,
pinnable: false,
closableByDimmer: false,
modal: false,
autoReset: false,
closable: true,
},
};
},
prepare: function() {
let that = this;
$container.html('');
that.ownershipModel = new ownershipModel();
let fields = pgBackform.generateViewSchema(null, that.ownershipModel, 'create', null, null, true, null);
let view = this.view = new pgBackform.Dialog({
el: '<div></div>',
model: that.ownershipModel,
schema: fields,
});
//Render change ownership dialog.
$container.append(view.render().$el[0]);
},
callback: function(e) {
if(e.button['data-btn-name'] === 'ok') {
e.cancel = true; // Do not close dialog
let newOwnershipModel = this.ownershipModel.toJSON();
if (newOwnershipModel.user == '' || newOwnershipModel.user == undefined) {
Notify.confirm(
gettext('Delete user?'),
gettext('The shared servers owned by <b>'+ self.model.get('username') +'</b> will be deleted. Do you wish to continue?'),
function() {
self.model.destroy({
wait: true,
success: function() {
Notify.success(gettext('User deleted.'));
alertify.changeOwnershipDialog().destroy();
alertify.UserManagement().destroy();
},
error: self.raiseError,
});
alertify.changeOwnershipDialog().destroy();
},
function() {
return true;
}
);
} else {
self.changeOwner(newOwnershipModel.user, uid);
}
} else {
alertify.changeOwnershipDialog().destroy();
}
},
};
});
}
alertify.changeOwnershipDialog('Change ownership').resizeTo(pgBrowser.stdW.md, pgBrowser.stdH.md);
},
changeOwner: function(user_id, old_user) {
$.ajax({ $.ajax({
url: url_for('user_management.change_owner'), url: url,
method: 'POST', headers: {
data:{'new_owner': user_id, 'old_owner': old_user}, 'Cache-Control' : 'no-cache',
}) },
.done(function(res) { }).done(function (result) {
alertify.changeOwnershipDialog().destroy(); showChangeOwnership(gettext('Change ownership'),
alertify.UserManagement().destroy(); result.data.result.data,
Notify.success(gettext(res.info)); res['data'].shared_servers,
}) {uid: uid, name: self.model.get('username')},
.fail(function() { destroyUserManagement
Notify.error(gettext('Unable to change owner.')); );
}); }).fail(function(e) {
let msg = '';
if(e.status == 404) {
msg = 'Unable to find url.';
} else {
msg = e.responseJSON.errormsg;
}
Notify.error(msg);
});
}, },
deleteUser: function() { deleteUser: function() {
let self = this; let self = this;