Fixed the UI related issues reported during testing for Shared Storage in Server Mode. #5014

This commit is contained in:
Nikhil Mohite 2023-03-24 11:08:27 +05:30 committed by GitHub
parent 8b815d4aac
commit e4eeba2aa7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 53 additions and 23 deletions

View File

@ -53,12 +53,14 @@ Shared Storage
:alt: Other options
:align: center
In shared storage the ``My Storage`` is the user's storage directory, and other directories are shared storage set by
the admin user. Using this shared storage users can share the files with other users through pgAdmin.
Admin users can mark the shared storage as restricted to restrict non-admin users from deleting, uploading,
adding, and renaming files/folders in shared storage by setting the restricted_access flag in config.
*NOTE: You must ensure the directories specified are writeable by the user that the web server processes will be running as, e.g. apache or www-data.*
In the storage manager, ``My Storage`` is the pgAdmin users storage directory, and other listed directories are shared
storages set by the pgAdmin server administrator. Using these, pgAdmin users can have common storages to share files.
pgAdmin server administrator can configure the shared storages using the <link>config file</link>. Storages can be
marked as restricted to give read-only access to non-admin pgAdmin users.
.. note:: You must ensure the directories specified are writeable by the user that the web server processes will be running as, e.g. apache or www-data.*
Other Options

View File

@ -559,8 +559,11 @@ def get_shared_storage_list():
config.SHARED_STORAGE = shared_storage_config
shared_storage_list = [sdir['name'] for sdir in shared_storage_config]
restricted_shared_storage_list = [sdir['name'] for sdir in
shared_storage_config if
sdir['restricted_access']]
return shared_storage_list
return shared_storage_list, restricted_shared_storage_list
@blueprint.route("/js/utils.js")
@ -630,7 +633,8 @@ def utils():
auth_source = session['auth_source_manager'][
'source_friendly_name']
shared_storage_list = get_shared_storage_list()
shared_storage_list, \
restricted_shared_storage_list = get_shared_storage_list()
return make_response(
render_template(
@ -665,6 +669,8 @@ def utils():
password_length_min=config.PASSWORD_LENGTH_MIN,
current_ui_lock=current_ui_lock,
shared_storage_list=shared_storage_list,
restricted_shared_storage_list=[] if current_user.has_role(
"Administrator") else restricted_shared_storage_list,
),
200, {'Content-Type': MIMETYPE_APP_JS})

View File

@ -57,6 +57,7 @@ define('pgadmin.browser.utils',
/* GET PSQL Tool related config */
pgAdmin['enable_psql'] = '{{enable_psql}}' == 'True';
pgAdmin['shared_storage'] = {{shared_storage_list}}
pgAdmin['restricted_shared_storage'] = {{restricted_shared_storage_list}}
pgAdmin['platform'] = '{{platform}}';
pgAdmin['qt_default_placeholder'] = '{{qt_default_placeholder}}'
pgAdmin['vw_edt_default_placeholder'] = '{{vw_edt_default_placeholder}}'

View File

@ -17,6 +17,7 @@ import time
from urllib.parse import unquote
from sys import platform as _platform
from flask_security import current_user
from pgadmin.utils.constants import ACCESS_DENIED_MESSAGE
import config
import codecs
import pathlib
@ -792,11 +793,10 @@ class Filemanager():
if selectedDir[
'restricted_access'] and not current_user.has_role(
"Administrator"):
raise PermissionError(gettext(
"Access denied: This shared folder has restricted "
"access, you are not allowed to rename, delete, "
"or upload any files/folders. Please contact "
"Administrator to gain access."))
return make_json_response(success=0,
errormsg=ACCESS_DENIED_MESSAGE,
info='ACCESS_DENIED',
status=403)
def rename(self, old=None, new=None):
"""

View File

@ -75,6 +75,7 @@ export default class FileManagerModule {
onCancel={onCancel}
onOK={onOK}
sharedStorages={this.pgAdmin.server_mode == 'True' ? this.pgAdmin.shared_storage: []}
restrictedSharedStorage={this.pgAdmin.server_mode == 'True' ? this.pgAdmin.restricted_shared_storage: []}
/>
);
}, {

View File

@ -402,7 +402,7 @@ ConfirmFile.propTypes = {
onNo: PropTypes.func
};
export default function FileManager({params, closeModal, onOK, onCancel, sharedStorages=[]}) {
export default function FileManager({params, closeModal, onOK, onCancel, sharedStorages=[], restrictedSharedStorage=[]}) {
const classes = useStyles();
const modalClasses = useModalStyles();
const apiObj = useMemo(()=>getApiInstance(), []);
@ -462,7 +462,7 @@ export default function FileManager({params, closeModal, onOK, onCancel, sharedS
let newItems = await fmUtilsObj.getFolder(dirPath || fmUtilsObj.currPath, changeStoragePath);
setItems(newItems);
setPath(fmUtilsObj.currPath);
params.dialog_type == 'storage_dialog' && fmUtilsObj.setLastVisitedDir(fmUtilsObj.currPath, selectedSS);
setTimeout(()=>{fmUtilsObj.setLastVisitedDir(dirPath || fmUtilsObj.currPath, changeStoragePath);}, 100);
} catch (error) {
console.error(error);
setErrorMsg(parseApiError(error));
@ -726,7 +726,7 @@ export default function FileManager({params, closeModal, onOK, onCancel, sharedS
{params.dialog_type == 'storage_dialog' &&
<PgIconButton title={gettext('Download')} icon={<GetAppRoundedIcon />}
onClick={onDownload} disabled={showUploader || isNoneSelected || selectedRow?.file_type == 'dir' || selectedRow?.file_type == 'drive'} />}
{fmUtilsObj.hasCapability('create') && <PgIconButton title={gettext('New Folder')} icon={<CreateNewFolderRoundedIcon />}
{fmUtilsObj.hasCapability('create') && !restrictedSharedStorage.includes(selectedSS) && <PgIconButton title={gettext('New Folder')} icon={<CreateNewFolderRoundedIcon />}
onClick={onAddFolder} disabled={showUploader} />}
</PgButtonGroup>
<PgButtonGroup size="small" style={{marginLeft: '4px'}}>
@ -739,13 +739,13 @@ export default function FileManager({params, closeModal, onOK, onCancel, sharedS
onClose={onMenuClose}
label={gettext('Options')}
>
{fmUtilsObj.hasCapability('rename') && <PgMenuItem hasCheck onClick={renameSelectedItem} disabled={isNoneSelected}>
{fmUtilsObj.hasCapability('rename') && !restrictedSharedStorage.includes(selectedSS) && <PgMenuItem hasCheck onClick={renameSelectedItem} disabled={isNoneSelected}>
{gettext('Rename')}
</PgMenuItem>}
{fmUtilsObj.hasCapability('delete') && <PgMenuItem hasCheck onClick={deleteSelectedItem} disabled={isNoneSelected}>
{fmUtilsObj.hasCapability('delete') && !restrictedSharedStorage.includes(selectedSS) && <PgMenuItem hasCheck onClick={deleteSelectedItem} disabled={isNoneSelected}>
{gettext('Delete')}
</PgMenuItem>}
{fmUtilsObj.hasCapability('upload') && <>
{fmUtilsObj.hasCapability('upload') && !restrictedSharedStorage.includes(selectedSS) && <>
<PgMenuDivider />
<PgMenuItem hasCheck onClick={(e)=>{
e.keepOpen = false;
@ -820,7 +820,7 @@ export default function FileManager({params, closeModal, onOK, onCancel, sharedS
onChange={(e)=>{
let val = e.target.value;
fmUtilsObj.setFileType(val);
openDir(fmUtilsObj.currPath);
openDir(fmUtilsObj.currPath, selectedSS);
setFileType(val);
}}
options={fmUtilsObj.allowedFileTypes?.map((type)=>({
@ -852,4 +852,5 @@ FileManager.propTypes = {
onOK: PropTypes.func,
onCancel: PropTypes.func,
sharedStorages: PropTypes.array,
restrictedSharedStorage: PropTypes.array,
};

View File

@ -17,7 +17,7 @@ from threading import Lock
import json
from config import PG_DEFAULT_DRIVER, ON_DEMAND_RECORD_COUNT,\
ALLOW_SAVE_PASSWORD
ALLOW_SAVE_PASSWORD, SHARED_STORAGE
from werkzeug.user_agent import UserAgent
from flask import Response, url_for, render_template, session, current_app
from flask import request
@ -52,7 +52,7 @@ from pgadmin.tools.sqleditor.utils.macros import get_macros,\
get_user_macros, set_macros
from pgadmin.utils.constants import MIMETYPE_APP_JS, \
SERVER_CONNECTION_CLOSED, ERROR_MSG_TRANS_ID_NOT_FOUND, \
ERROR_FETCHING_DATA, MY_STORAGE
ERROR_FETCHING_DATA, MY_STORAGE, ACCESS_DENIED_MESSAGE
from pgadmin.model import Server, ServerGroup
from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry
from pgadmin.settings import get_setting
@ -1856,6 +1856,18 @@ def save_file():
last_storage = Preferences.module('file_manager').preference(
'last_storage').get()
if last_storage != MY_STORAGE:
selectedDirList = [sdir for sdir in SHARED_STORAGE if
sdir['name'] == last_storage]
selectedDir = selectedDirList[0] if len(
selectedDirList) == 1 else None
if selectedDir:
if selectedDir['restricted_access'] and \
not current_user.has_role("Administrator"):
return make_json_response(success=0,
errormsg=ACCESS_DENIED_MESSAGE,
info='ACCESS_DENIED',
status=403)
storage_manager_path = get_storage_directory(
shared_storage=last_storage)
else:

View File

@ -422,7 +422,9 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN
}).catch((err)=>{
eventBus.fireEvent(QUERY_TOOL_EVENTS.HANDLE_API_ERROR, err);
});
} else {
} else if(error.response?.status == 403 && error.response?.data.info == 'ACCESS_DENIED') {
Notifier.error(error.response.data.errormsg);
}else {
let msg = parseApiError(error);
eventBus.current.fireEvent(QUERY_TOOL_EVENTS.SET_MESSAGE, msg, true);
eventBus.current.fireEvent(QUERY_TOOL_EVENTS.FOCUS_PANEL, PANELS.MESSAGES);

View File

@ -122,3 +122,6 @@ PSYCOPG3 = 'psycopg3'
# Shared storage
MY_STORAGE = 'my_storage'
ACCESS_DENIED_MESSAGE = gettext(
"Access denied: Youre having limited access. Youre not allowed to "
"Rename, Delete or Create any files/folders")

View File

@ -42,7 +42,6 @@ const files = [
}
}
];
const transId = 140391;
const configData = {
'transId': transId,
@ -87,6 +86,7 @@ const configData = {
};
const sharedStorageConfig = ['Shared Storage'];
const restrictedSharedStorage = [];
const params={
dialog_type: 'select_file',
@ -127,6 +127,7 @@ describe('FileManger', ()=>{
onOK={onOK}
onCancel={onCancel}
sharedStorages={sharedStorageConfig}
restrictedSharedStorage={restrictedSharedStorage}
{...props}
/>
</Theme>);
@ -134,6 +135,7 @@ describe('FileManger', ()=>{
it('init', (done)=>{
networkMock.onPost('/file_manager/init').reply(200, {'data': configData});
networkMock.onPost(`/file_manager/save_last_dir/${transId}`).reply(200, {'success':1,'errormsg':'','info':'','result':null,'data':null});
let ctrl = ctrlMount({});
setTimeout(()=>{
ctrl.update();