Port Role Reassign dialog to React. Fixes #7344
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 107 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 107 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 42 KiB |
|
@ -14,6 +14,7 @@ New features
|
|||
Housekeeping
|
||||
************
|
||||
|
||||
| `Issue #7344 <https://redmine.postgresql.org/issues/7344>`_ - Port Role Reassign 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).
|
||||
|
|
|
@ -10,15 +10,12 @@ import RoleSchema from './role.ui';
|
|||
import { getNodeVariableSchema } from '../../../static/js/variable.ui';
|
||||
import { getNodeListByName } from '../../../../../static/js/node_ajax';
|
||||
import { getMembershipSchema } from '../../../static/js/membership.ui';
|
||||
import Notify from '../../../../../../static/js/helpers/Notifier';
|
||||
import { showRoleReassign } from './roleReassign';
|
||||
|
||||
define('pgadmin.node.role', [
|
||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
|
||||
'sources/pgadmin', 'pgadmin.browser', 'alertify',
|
||||
'pgadmin.backform', 'axios', 'sources/utils', 'backbone', 'select2',
|
||||
'pgadmin.browser.collection', 'pgadmin.browser.node.ui',
|
||||
'pgadmin.browser.server.variable',
|
||||
], function(gettext, url_for, $, _, pgAdmin, pgBrowser, alertify, Backform, axios, utils, Backbone) {
|
||||
'sources/gettext', 'sources/url_for', 'underscore',
|
||||
'sources/pgadmin', 'pgadmin.browser'
|
||||
], function(gettext, url_for, _, pgAdmin, pgBrowser) {
|
||||
|
||||
if (!pgBrowser.Nodes['coll-role']) {
|
||||
pgAdmin.Browser.Nodes['coll-role'] =
|
||||
|
@ -127,450 +124,7 @@ define('pgadmin.node.role', [
|
|||
return server.connected && node.can_login;
|
||||
},
|
||||
reassign_role: function() {
|
||||
|
||||
var tree = pgBrowser.tree,
|
||||
_i = tree.selected(),
|
||||
_d = _i ? tree.itemData(_i) : undefined,
|
||||
obj = this, finalUrl, old_role_name;
|
||||
|
||||
//RoleReassign Model (Objects like role, database)
|
||||
var RoleReassignObjectModel = Backbone.Model.extend({
|
||||
idAttribute: 'id',
|
||||
defaults: {
|
||||
role_op: undefined,
|
||||
did: undefined,
|
||||
new_role_id: undefined,
|
||||
new_role_name: undefined,
|
||||
old_role_name: undefined,
|
||||
drop_with_cascade: false
|
||||
},
|
||||
|
||||
// Default values!
|
||||
initialize: function() {
|
||||
// Set default options according to node type selection by user
|
||||
Backbone.Model.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
id: 'role_op',
|
||||
label: gettext('Operation'),
|
||||
cell: 'string',
|
||||
type: 'radioModern',
|
||||
controlsClassName: 'pgadmin-controls col-12 col-sm-8',
|
||||
controlLabelClassName: 'control-label col-sm-4 col-12',
|
||||
group: gettext('General'),
|
||||
options: [{
|
||||
'label': 'Reassign',
|
||||
'value': 'reassign',
|
||||
},
|
||||
{
|
||||
'label': 'Drop',
|
||||
'value': 'drop',
|
||||
},
|
||||
],
|
||||
helpMessage: gettext('Change the ownership or\ndrop the database objects owned by a database role'),
|
||||
},
|
||||
{
|
||||
id: 'new_role_name',
|
||||
label: gettext('Reassign objects to'),
|
||||
controlsClassName: 'pgadmin-controls col-12 col-sm-8',
|
||||
controlLabelClassName: 'control-label col-sm-4 col-12',
|
||||
url: 'nodes',
|
||||
helpMessage: gettext('New owner of the affected objects'),
|
||||
transform: function(data, cell) {
|
||||
var res = [],
|
||||
control = cell || this,
|
||||
node = control.field.get('schema_node');
|
||||
|
||||
// remove the current role from list
|
||||
let current_label = control.field.attributes.node_info.role.label;
|
||||
if (data && _.isArray(data)) {
|
||||
|
||||
let CURRENT_USER = {
|
||||
label: 'CURRENT_USER', value: 'CURRENT_USER',
|
||||
image: 'icon-' + node.type, _id: null,
|
||||
},
|
||||
SESSION_USER = {
|
||||
label: 'SESSION_USER', value: 'SESSION_USER', image: 'icon-' + node.type, _id: null,
|
||||
};
|
||||
CURRENT_USER.value = JSON.stringify(CURRENT_USER);
|
||||
SESSION_USER.value = JSON.stringify(SESSION_USER);
|
||||
|
||||
res.push(CURRENT_USER, SESSION_USER);
|
||||
|
||||
if(control.field.attributes.node_data.version >= 140000) {
|
||||
let CURRENT_ROLE = {
|
||||
label: 'CURRENT_ROLE', value: 'CURRENT_ROLE',
|
||||
image: 'icon-' + node.type, _id: null,
|
||||
};
|
||||
CURRENT_ROLE.value = JSON.stringify(CURRENT_ROLE);
|
||||
res.push(CURRENT_ROLE);
|
||||
}
|
||||
|
||||
_.each(data, function(d) {
|
||||
/*
|
||||
* d contains json data and sets into
|
||||
* select's option control
|
||||
*
|
||||
* We need to stringify data because formatter will
|
||||
* convert Array Object as [Object] string
|
||||
*/
|
||||
if (current_label != d.label)
|
||||
res.push({label: d.label, image: d.icon, value: JSON.stringify(d)});
|
||||
});
|
||||
}
|
||||
return res;
|
||||
},
|
||||
control: Backform.NodeListByIdControl.extend({
|
||||
getValueFromDOM: function() {
|
||||
var data = this.formatter.toRaw(
|
||||
_.unescape(this.$el.find('select').val()), this.model);
|
||||
/*
|
||||
* return null if data is empty to prevent it from
|
||||
* throwing parsing error. Adds check as name can be empty
|
||||
*/
|
||||
if (data === '') {
|
||||
return null;
|
||||
}
|
||||
else if (typeof(data) === 'string') {
|
||||
data=JSON.parse(data);
|
||||
}
|
||||
return data.label;
|
||||
},
|
||||
/*
|
||||
* When name is changed, extract value from its select option and
|
||||
* set attributes values into the model
|
||||
*/
|
||||
onChange: function() {
|
||||
Backform.NodeAjaxOptionsControl.prototype.onChange.apply(
|
||||
this, arguments
|
||||
);
|
||||
var selectedValue = this.$el.find('select').val();
|
||||
if (selectedValue.trim() != '') {
|
||||
var d = this.formatter.toRaw(selectedValue, this.model);
|
||||
if(typeof(d) === 'string')
|
||||
d=JSON.parse(d);
|
||||
this.model.set({
|
||||
'new_role_id' : d._id,
|
||||
'new_role_name': d.label,
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
node: 'role',
|
||||
group: gettext('General'),
|
||||
select2: {
|
||||
allowClear: false,
|
||||
},
|
||||
disabled: 'isDisabled',
|
||||
deps: ['role_op'],
|
||||
filter: function(d) {
|
||||
// Exclude the currently selected
|
||||
let ltree = pgBrowser.tree,
|
||||
_idx = ltree.selected(),
|
||||
_data = _idx && _idx.length == 1 ? ltree.itemData(_idx) : undefined;
|
||||
|
||||
if(!_data)
|
||||
return true;
|
||||
|
||||
return d && (d.label != _data.label);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'drop_with_cascade',
|
||||
label: gettext('Cascade?'),
|
||||
cell: 'string',
|
||||
type: 'switch',
|
||||
controlsClassName: 'pgadmin-controls col-12 col-sm-8',
|
||||
controlLabelClassName: 'control-label col-sm-4 col-12',
|
||||
disabled: 'isDisabled',
|
||||
group: gettext('General'),
|
||||
options: {
|
||||
'onText': gettext('Yes'),
|
||||
'offText': gettext('No'), 'size': 'mini'
|
||||
},
|
||||
deps: ['role_op'],
|
||||
helpMessage: gettext('Note: CASCADE will automatically drop objects that depend on the affected objects, and in turn all objects that depend on those objects'),
|
||||
},
|
||||
{
|
||||
id: 'did',
|
||||
label: gettext('From database'),
|
||||
controlsClassName: 'pgadmin-controls col-12 col-sm-8',
|
||||
controlLabelClassName: 'control-label col-sm-4 col-12',
|
||||
node: 'database',
|
||||
group: gettext('General'),
|
||||
disabled: 'isDisabled',
|
||||
control: Backform.NodeListByIdControl.extend({
|
||||
onChange: function() {
|
||||
Backform.NodeListByIdControl.prototype.onChange.apply(
|
||||
this, arguments
|
||||
);
|
||||
let did = this.model.get('did');
|
||||
this.model.set('did', parseInt(did));
|
||||
},
|
||||
}),
|
||||
select2: {
|
||||
allowClear: false,
|
||||
},
|
||||
events: {
|
||||
'select2:select': 'onChange'
|
||||
},
|
||||
first_empty: false,
|
||||
helpMessage: gettext('Target database on which the operation will be carried out'),
|
||||
},
|
||||
{
|
||||
id: 'sqltab', label: gettext('SQL'), group: gettext('SQL'),
|
||||
type: 'text', disabled: false, control: Backform.SqlTabControl.extend({
|
||||
initialize: function() {
|
||||
// Initialize parent class
|
||||
Backform.SqlTabControl.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
onTabChange: function(sql_tab_obj) {
|
||||
// Fetch the information only if the SQL tab is visible at the moment.
|
||||
if (this.dialog && sql_tab_obj.shown == this.tabIndex) {
|
||||
var self = this,
|
||||
roleReassignData = self.model.toJSON(),
|
||||
getUrl;
|
||||
// Add existing role
|
||||
roleReassignData.old_role_name = old_role_name;
|
||||
|
||||
getUrl = obj.generate_url(_i, 'reassign' , _d, true);
|
||||
|
||||
$.ajax({
|
||||
url: getUrl,
|
||||
type: 'GET',
|
||||
cache: false,
|
||||
data: roleReassignData,
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
}).done(function(res) {
|
||||
self.sqlCtrl.setValue(res.data);
|
||||
});
|
||||
}
|
||||
},
|
||||
}),
|
||||
}
|
||||
],
|
||||
validate: function() {
|
||||
return null;
|
||||
},
|
||||
isDisabled: function(m) {
|
||||
|
||||
let self_local = this;
|
||||
switch(this.name) {
|
||||
case 'new_role_name':
|
||||
return (m.get('role_op') != 'reassign');
|
||||
case 'drop_with_cascade':
|
||||
return (m.get('role_op') != 'drop');
|
||||
case 'did':
|
||||
setTimeout(function() {
|
||||
if(_.isUndefined(m.get('did'))) {
|
||||
let db = self_local.options[0];
|
||||
m.set('did', db.value);
|
||||
}
|
||||
}, 10);
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (!_d)
|
||||
return;
|
||||
|
||||
if (!alertify.roleReassignDialog) {
|
||||
alertify.dialog('roleReassignDialog', function factory() {
|
||||
return {
|
||||
main: function(title) {
|
||||
this.set('title', title);
|
||||
},
|
||||
setup: function() {
|
||||
return {
|
||||
buttons:[{
|
||||
text: '', key: 112,
|
||||
className: 'btn btn-primary-icon pull-left fa fa-question pg-alertify-icon-button',
|
||||
attrs:{name:'dialog_help', type:'button', label: gettext('Users'),
|
||||
url: url_for('help.static', {'filename': 'role_reassign_dialog.html'})},
|
||||
},{
|
||||
text: gettext('Cancel'),
|
||||
key: 27,
|
||||
className: 'btn btn-secondary fa fa-lg fa-times pg-alertify-button',
|
||||
}, {
|
||||
text: gettext('OK'),
|
||||
key: 13,
|
||||
className: 'btn btn-primary fa fa-lg fa-save pg-alertify-button',
|
||||
}],
|
||||
focus: {
|
||||
element: 0,
|
||||
},
|
||||
options: {
|
||||
//disable both padding and overflow control.
|
||||
padding : !1,
|
||||
overflow: !1,
|
||||
modal: false,
|
||||
resizable: true,
|
||||
maximizable: true,
|
||||
pinnable: false,
|
||||
closableByDimmer: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
build: function() {
|
||||
alertify.pgDialogBuild.apply(this);
|
||||
},
|
||||
hooks:{
|
||||
onclose: function() {
|
||||
if (this.view) {
|
||||
// clear our backform model/view
|
||||
this.view.remove({data: true, internal: true, silent: true});
|
||||
}
|
||||
},
|
||||
},
|
||||
prepare:function() {
|
||||
|
||||
var self = this,
|
||||
$container = $('<div class=\'role_reassign_own\'></div>');
|
||||
//Disable Okay button
|
||||
self.__internal.buttons[2].element.disabled = true;
|
||||
// Find current/selected node
|
||||
var ltree = pgBrowser.tree,
|
||||
_idx = ltree.selected(),
|
||||
_data = _idx ? ltree.itemData(_idx) : undefined,
|
||||
node = _data && pgBrowser.Nodes[_data._type];
|
||||
|
||||
finalUrl = obj.generate_url(_idx, 'reassign' , _data, true);
|
||||
old_role_name = _data.label;
|
||||
|
||||
if (!_data)
|
||||
return;
|
||||
// Create treeInfo
|
||||
var treeInfo = pgBrowser.tree.getTreeNodeHierarchy(_idx);
|
||||
// Instance of backbone model
|
||||
var newModel = new RoleReassignObjectModel({}, {node_info: treeInfo}),
|
||||
fields = Backform.generateViewSchema(
|
||||
treeInfo, newModel, 'create', node,
|
||||
treeInfo.server, true
|
||||
);
|
||||
|
||||
var view = self.view = new Backform.Dialog({
|
||||
el: $container, model: newModel, schema: fields,
|
||||
});
|
||||
// Add our class to alertify
|
||||
$(self.elements.body.childNodes[0]).addClass(
|
||||
'alertify_tools_dialog_properties obj_properties'
|
||||
);
|
||||
|
||||
// Render dialog
|
||||
view.render();
|
||||
self.elements.content.append($container.get(0));
|
||||
|
||||
const statusBar = $(
|
||||
'<div class=\'pg-prop-status-bar pg-prop-status-bar-absolute pg-el-xs-12 d-none\'>' +
|
||||
' <div class="error-in-footer"> ' +
|
||||
' <div class="d-flex px-2 py-1"> ' +
|
||||
' <div class="pr-2"> ' +
|
||||
' <i class="fa fa-exclamation-triangle text-danger" aria-hidden="true"></i> ' +
|
||||
' </div> ' +
|
||||
' <div class="alert-text" role="alert"></div> ' +
|
||||
' <div class="ml-auto close-error-bar"> ' +
|
||||
' <a aria-label="' + gettext('Close error bar') + '" class="close-error fa fa-times text-danger"></a> ' +
|
||||
' </div> ' +
|
||||
' </div> ' +
|
||||
' </div> ' +
|
||||
'</div>').appendTo($container);
|
||||
|
||||
// Listen to model & if filename is provided then enable Backup button
|
||||
this.view.model.on('change', function() {
|
||||
|
||||
const ctx = this;
|
||||
|
||||
const showError = function(errorField, errormsg) {
|
||||
ctx.errorModel.set(errorField, errormsg);
|
||||
statusBar.removeClass('d-none');
|
||||
statusBar.find('.alert-text').html(errormsg);
|
||||
self.elements.dialog.querySelector('.close-error').addEventListener('click', ()=>{
|
||||
statusBar.addClass('d-none');
|
||||
ctx.errorModel.set(errorField, errormsg);
|
||||
});
|
||||
};
|
||||
statusBar.addClass('d-none');
|
||||
|
||||
if ((this.get('role_op') == 'reassign')
|
||||
&& !_.isUndefined(this.get('new_role_name')
|
||||
&& this.get('new_role_name') !== '')
|
||||
) {
|
||||
this.errorModel.clear();
|
||||
self.__internal.buttons[2].element.disabled = false;
|
||||
} else if(this.get('role_op') == 'drop') {
|
||||
this.errorModel.clear();
|
||||
this.set({'new_role_name': undefined, silent: true});
|
||||
this.set({'new_role_id': undefined, silent: true});
|
||||
self.__internal.buttons[2].element.disabled = false;
|
||||
} else if(_.isUndefined(this.get('new_role_name'))) {
|
||||
let errmsg = gettext('Please provide a new role name');
|
||||
this.errorModel.set('new_role_name', errmsg);
|
||||
showError('new_role_name', errmsg);
|
||||
self.__internal.buttons[2].element.disabled = true;
|
||||
}
|
||||
else {
|
||||
self.__internal.buttons[2].element.disabled = true;
|
||||
}
|
||||
});
|
||||
|
||||
// set default role operation as reassign
|
||||
this.view.model.set({'role_op': 'reassign'});
|
||||
},
|
||||
// Callback functions when click on the buttons of the alertify dialogs
|
||||
callback: function(e) {
|
||||
if (e.button.element.name == 'dialog_help') {
|
||||
e.cancel = true;
|
||||
pgBrowser.showHelp(e.button.element.name, e.button.element.getAttribute('url'),
|
||||
null, null);
|
||||
return;
|
||||
}
|
||||
if (e.button.text === gettext('OK')) {
|
||||
|
||||
let roleReassignData = this.view.model.toJSON(),
|
||||
roleOp = roleReassignData.role_op,
|
||||
confirmBoxTitle = utils.titleize(roleOp);
|
||||
|
||||
Notify.confirm(
|
||||
gettext('%s Objects', confirmBoxTitle),
|
||||
gettext('Are you sure you wish to %s all the objects owned by the selected role?', roleOp),
|
||||
function() {
|
||||
axios.post(
|
||||
finalUrl,
|
||||
roleReassignData
|
||||
).then(function (response) {
|
||||
if(response.data)
|
||||
Notify.success(response.data.info);
|
||||
}).catch(function (error) {
|
||||
try {
|
||||
const err = error.response.data;
|
||||
Notify.alert(
|
||||
gettext('Role reassign/drop failed.'),
|
||||
err.errormsg
|
||||
);
|
||||
} catch (ex) {
|
||||
console.warn(ex.stack || ex);
|
||||
}
|
||||
});
|
||||
},
|
||||
function() { return true; }
|
||||
).set('labels', {
|
||||
ok: gettext('Yes'),
|
||||
cancel: gettext('No'),
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
alertify.roleReassignDialog(
|
||||
gettext('Reassign/Drop Owned - \'%s\'', _d.label)
|
||||
).resizeTo(pgAdmin.Browser.stdW.md, pgAdmin.Browser.stdH.lg);
|
||||
showRoleReassign();
|
||||
},
|
||||
getSchema: function(treeNodeInfo, itemNodeData) {
|
||||
return new RoleSchema(
|
||||
|
|
|
@ -0,0 +1,232 @@
|
|||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import gettext from 'sources/gettext';
|
||||
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
|
||||
import url_for from 'sources/url_for';
|
||||
import { getNodeListByName, generateNodeUrl } from '../../../../../static/js/node_ajax';
|
||||
import pgBrowser from 'top/browser/static/js/browser';
|
||||
import { getUtilityView } from '../../../../../static/js/utility_view';
|
||||
import Notify from '../../../../../../static/js/helpers/Notifier';
|
||||
import { isEmptyString } from 'sources/validators';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
|
||||
export default class RoleReassign extends BaseUISchema{
|
||||
constructor(fieldOptions={}, initValues={}){
|
||||
super({
|
||||
role_op: 'reassign',
|
||||
did: undefined,
|
||||
new_role_id: undefined,
|
||||
new_role_name: undefined,
|
||||
drop_with_cascade: false,
|
||||
old_role_name: initValues.old_role_name,
|
||||
...initValues
|
||||
});
|
||||
|
||||
this.fieldOptions = {
|
||||
roleList: fieldOptions.roleList,
|
||||
databaseList: fieldOptions.databaseList,
|
||||
nodeInfo: fieldOptions.nodeInfo,
|
||||
...fieldOptions,
|
||||
};
|
||||
|
||||
this.nodeInfo = this.fieldOptions.nodeInfo;
|
||||
this.warningText = null;
|
||||
}
|
||||
|
||||
get idAttribute() {
|
||||
return 'oid';
|
||||
}
|
||||
|
||||
get baseFields(){
|
||||
let obj = this;
|
||||
return [
|
||||
{
|
||||
id: 'role_op',
|
||||
label: gettext('Operation'),
|
||||
group: gettext('General'),
|
||||
type: 'toggle',
|
||||
options: [
|
||||
{ 'label': gettext('Reassign'), 'value': 'reassign' },
|
||||
{ 'label': gettext('Drop'), 'value': 'drop' },
|
||||
],
|
||||
helpMessage: gettext('Change the ownership or\ndrop the database objects owned by a database role')
|
||||
},
|
||||
{
|
||||
id: 'new_role_id',
|
||||
label: gettext('Reassign objects to'),
|
||||
group: gettext('General'),
|
||||
type: ()=>{
|
||||
return{
|
||||
type: 'select',
|
||||
options: this.fieldOptions.roleList,
|
||||
optionsLoaded: (options) => { obj.roleNameIdList = options; },
|
||||
controlProps: {
|
||||
allowClear: false,
|
||||
filter: (options)=>{
|
||||
let data = [];
|
||||
let CURRENT_USER = {
|
||||
label: 'CURRENT_USER', value: 'CURRENT_USER', image: 'icon-role'
|
||||
},
|
||||
SESSION_USER = {
|
||||
label: 'SESSION_USER', value: 'SESSION_USER', image: 'icon-role'
|
||||
};
|
||||
data.push(CURRENT_USER, SESSION_USER);
|
||||
|
||||
if (obj.getServerVersion() >= 140000){
|
||||
let CURRENT_ROLE = {
|
||||
label: 'CURRENT_ROLE', value: 'CURRENT_ROLE', image: 'icon-role'
|
||||
};
|
||||
data.push(CURRENT_ROLE);
|
||||
}
|
||||
if (options && _.isArray(options)){
|
||||
_.each(options, function(d) {
|
||||
// omit currently selected role
|
||||
if(d._id != obj.nodeInfo.role._id){
|
||||
data.push({label: d.label, value: d._id, image: d.image});
|
||||
}
|
||||
});
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
helpMessage: gettext('New owner of the affected objects'),
|
||||
deps: ['role_op'],
|
||||
disabled: (state)=>{
|
||||
return state.role_op == 'drop'? true: false;
|
||||
},
|
||||
depChange: (state) =>{
|
||||
if (state.role_op == 'drop'){
|
||||
return {new_role_id:''};
|
||||
}
|
||||
}
|
||||
},
|
||||
{ /* this is dummy field not shown on UI but added as API require this value */
|
||||
id: 'new_role_name',
|
||||
visible: false,
|
||||
type: '',
|
||||
deps:['new_role_id'],
|
||||
depChange: (state)=>{
|
||||
let new_role_name;
|
||||
if (['CURRENT_USER','SESSION_USER','CURRENT_ROLE'].includes(state.new_role_id)){
|
||||
new_role_name = state.new_role_id;
|
||||
}else{
|
||||
new_role_name = obj.roleNameIdList.find(o=> o._id === state.new_role_id).label;
|
||||
}
|
||||
return {new_role_name: new_role_name};
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'drop_with_cascade',
|
||||
label: gettext('Cascade?'),
|
||||
group: gettext('General'),
|
||||
type: 'switch',
|
||||
deps: ['role_op'],
|
||||
helpMessage: gettext('Note: CASCADE will automatically drop objects that depend on the affected objects, and in turn all objects that depend on those objects')
|
||||
},
|
||||
{
|
||||
id: 'did',
|
||||
label: gettext('From database'),
|
||||
group: gettext('General'),
|
||||
helpMessage: gettext('Target database on which the operation will be carried out'),
|
||||
type: ()=>{
|
||||
return {
|
||||
type: 'select',
|
||||
options: this.fieldOptions.databaseList,
|
||||
controlProps: {
|
||||
allowClear: false,
|
||||
filter: (options)=>{
|
||||
let data = [];
|
||||
if (options && _.isArray(options)){
|
||||
_.each(options, function(d) {
|
||||
data.push({label: d.label, value: d._id, image: d.image});
|
||||
});
|
||||
}
|
||||
return data;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
validate(state, setError) {
|
||||
let errmsg = null;
|
||||
let obj = this;
|
||||
|
||||
if (state.role_op == 'reassign' && isEmptyString(state.new_role_id)) {
|
||||
errmsg = gettext('\'Reassign objects to\' can not be empty');
|
||||
setError('new_role_id', errmsg);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isEmptyString(state.did)) {
|
||||
errmsg = gettext('\'From database \' can not be empty');
|
||||
setError('did', errmsg);
|
||||
return true;
|
||||
}
|
||||
|
||||
obj.warningText = gettext(`Are you sure you wish to ${state.role_op} all the objects owned by the selected role?`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function saveCallBack (data) {
|
||||
if (data.errormsg) {
|
||||
Notify.alert(
|
||||
gettext('Error'),
|
||||
gettext(data.errormsg)
|
||||
);
|
||||
} else {
|
||||
Notify.success(gettext(data.info));
|
||||
}
|
||||
}
|
||||
|
||||
function getUISchema(treeNodeInfo, itemNodeData ) {
|
||||
return new RoleReassign(
|
||||
{
|
||||
roleList: ()=>getNodeListByName('role', treeNodeInfo, itemNodeData, {includeItemKeys: ['_id']}),
|
||||
databaseList: ()=>getNodeListByName('database', treeNodeInfo, itemNodeData, {cacheLevel: 'database', cacheNode: 'database', includeItemKeys: ['_id']}),
|
||||
nodeInfo: treeNodeInfo
|
||||
},
|
||||
{
|
||||
old_role_name: itemNodeData.label
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function showRoleReassign() {
|
||||
let tree = pgBrowser.tree,
|
||||
item = tree.selected(),
|
||||
data = item ? tree.itemData(item) : undefined,
|
||||
treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item),
|
||||
itemNodeData = pgBrowser.tree.findNodeByDomElement(item).getData();
|
||||
|
||||
pgBrowser.Node.registerUtilityPanel();
|
||||
let panel = pgBrowser.Node.addUtilityPanel(pgBrowser.stdW.md, 480),
|
||||
j = panel.$container.find('.obj_properties').first();
|
||||
panel.title(gettext(`Reassign/Drop Owned - ${data.label}`));
|
||||
panel.focus();
|
||||
|
||||
const baseUrl = generateNodeUrl.call( pgAdmin.Browser.Nodes[data._type], treeNodeInfo, 'reassign', data, true);
|
||||
|
||||
let schema = getUISchema(treeNodeInfo, itemNodeData),
|
||||
sqlHelpUrl = '',
|
||||
msqlurl = generateNodeUrl.call( pgAdmin.Browser.Nodes[data._type], treeNodeInfo, 'reassign', data, true),
|
||||
extraData = {nodeType: data._type, msqlurl:msqlurl},
|
||||
helpUrl = url_for('help.static', {
|
||||
'filename': 'role_reassign_dialog.html',
|
||||
});
|
||||
|
||||
getUtilityView(
|
||||
schema, treeNodeInfo, 'create', 'dialog', j[0], panel, saveCallBack, extraData, 'Reassign/Drop', baseUrl, sqlHelpUrl, helpUrl);
|
||||
}
|
|
@ -16,6 +16,7 @@ import SchemaView from 'sources/SchemaView';
|
|||
import 'wcdocker';
|
||||
import Theme from '../../../static/js/Theme';
|
||||
import url_for from 'sources/url_for';
|
||||
import { generateNodeUrl } from './node_ajax';
|
||||
|
||||
/* The entry point for rendering React based view in properties, called in node.js */
|
||||
export function getUtilityView(schema, treeNodeInfo, actionType, formType, container, containerPanel,
|
||||
|
@ -33,6 +34,10 @@ export function getUtilityView(schema, treeNodeInfo, actionType, formType, conta
|
|||
/* button icons */
|
||||
const saveBtnIcon = extraData.save_btn_icon;
|
||||
|
||||
/* Node type & Noen obj*/
|
||||
let nodeObj = extraData.nodeType? pgAdmin.Browser.Nodes[extraData.nodeType]: undefined;
|
||||
let itemNodeData = extraData?.itemNodeData ? itemNodeData: undefined;
|
||||
|
||||
/* on save button callback, promise required */
|
||||
const onSaveClick = (isNew, data)=>new Promise((resolve, reject)=>{
|
||||
return api({
|
||||
|
@ -49,6 +54,23 @@ export function getUtilityView(schema, treeNodeInfo, actionType, formType, conta
|
|||
});
|
||||
});
|
||||
|
||||
/* Called when switched to SQL tab, promise required */
|
||||
const getSQLValue = (isNew, changedData)=>{
|
||||
const msqlUrl = extraData?.msqlurl ? extraData.msqlurl: generateNodeUrl.call(nodeObj, treeNodeInfo, 'msql', itemNodeData, !isNew, nodeObj.url_jump_after_node);
|
||||
return new Promise((resolve, reject)=>{
|
||||
api({
|
||||
url: msqlUrl,
|
||||
method: 'GET',
|
||||
params: changedData,
|
||||
}).then((res)=>{
|
||||
resolve(res.data.data);
|
||||
}).catch((err)=>{
|
||||
onError(err);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/* Callback for help button */
|
||||
const onHelp = (isSqlHelp=false)=>{
|
||||
if(isSqlHelp) {
|
||||
|
@ -100,6 +122,15 @@ export function getUtilityView(schema, treeNodeInfo, actionType, formType, conta
|
|||
|
||||
});
|
||||
|
||||
let onError = (err)=> {
|
||||
if(err.response){
|
||||
console.error('error resp', err.response);
|
||||
} else if(err.request){
|
||||
console.error('error req', err.request);
|
||||
} else if(err.message){
|
||||
console.error('error msg', err.message);
|
||||
}
|
||||
};
|
||||
let _schema = schema;
|
||||
|
||||
/* Fire at will, mount the DOM */
|
||||
|
@ -117,7 +148,8 @@ export function getUtilityView(schema, treeNodeInfo, actionType, formType, conta
|
|||
onHelp={onHelp}
|
||||
onDataChange={()=>{/*This is intentional (SonarQube)*/}}
|
||||
confirmOnCloseReset={confirmOnReset}
|
||||
hasSQL={false}
|
||||
hasSQL={nodeObj?nodeObj.hasSQL:false && (actionType === 'create' || actionType === 'edit')}
|
||||
getSQLValue={getSQLValue}
|
||||
isTabView={isTabView}
|
||||
disableSqlHelp={sqlHelpUrl == undefined || sqlHelpUrl == ''}
|
||||
disableDialogHelp={helpUrl == undefined || helpUrl == ''}
|
||||
|
|
|
@ -27,7 +27,6 @@ const useStyles = makeStyles(() =>
|
|||
}
|
||||
}),
|
||||
);
|
||||
const classes = useStyles();
|
||||
|
||||
// Azure credentials
|
||||
export function AzureCredentials(props) {
|
||||
|
@ -113,6 +112,7 @@ AzureCredentials.propTypes = {
|
|||
// Azure Instance
|
||||
export function AzureInstanceDetails(props) {
|
||||
const [azureInstanceSchema, setAzureInstanceSchema] = React.useState();
|
||||
const classes = useStyles();
|
||||
|
||||
React.useMemo(() => {
|
||||
const AzureSchema = new AzureClusterSchema({
|
||||
|
@ -213,6 +213,7 @@ AzureInstanceDetails.propTypes = {
|
|||
// Azure Database Details
|
||||
export function AzureDatabaseDetails(props) {
|
||||
const [azureDBInstance, setAzureDBInstance] = React.useState();
|
||||
const classes = useStyles();
|
||||
|
||||
React.useMemo(() => {
|
||||
const azureDBSchema = new AzureDatabaseSchema({
|
||||
|
|