mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Added support for mounting shared storage in server mode. #5014
This commit is contained in:
BIN
docs/en_US/images/shared_storage.png
Normal file
BIN
docs/en_US/images/shared_storage.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 113 KiB |
BIN
docs/en_US/images/sm_ss.png
Normal file
BIN
docs/en_US/images/sm_ss.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 124 KiB |
@@ -7,6 +7,7 @@
|
|||||||
*Storage Manager* is a feature that helps you manage your systems storage device. You can use *Storage Manager* to:
|
*Storage Manager* is a feature that helps you manage your systems storage device. You can use *Storage Manager* to:
|
||||||
|
|
||||||
* Download, upload, or manage operating system files. To use this feature, *pgAdmin* must be running in *Server Mode* on your client machine.
|
* Download, upload, or manage operating system files. To use this feature, *pgAdmin* must be running in *Server Mode* on your client machine.
|
||||||
|
* The shared storage option allows users to access the shared storages that are shared by admin users.
|
||||||
* Download *backup* or *export* files (custom, tar and plain text format) on a client machine.
|
* Download *backup* or *export* files (custom, tar and plain text format) on a client machine.
|
||||||
* Download *export* dump files of tables.
|
* Download *export* dump files of tables.
|
||||||
|
|
||||||
@@ -18,6 +19,12 @@ You can access *Storage Manager* from the *Tools* Menu.
|
|||||||
|
|
||||||
Use icons on the top of the *Storage Manager* window to manage storage:
|
Use icons on the top of the *Storage Manager* window to manage storage:
|
||||||
|
|
||||||
|
Use the ``Folder`` icon |Shared Storage| to access shared storage. In order to enable shared storage,
|
||||||
|
admins need to add the SHARED_STORAGE variable to the config file. Users can access the shared storage
|
||||||
|
with this and share files with one another.
|
||||||
|
|
||||||
|
.. |Shared Storage| image:: images/sm_ss.png
|
||||||
|
|
||||||
Use the ``Home`` icon |home| to return to the home directory.
|
Use the ``Home`` icon |home| to return to the home directory.
|
||||||
|
|
||||||
.. |home| image:: images/sm_home.png
|
.. |home| image:: images/sm_home.png
|
||||||
@@ -40,6 +47,17 @@ Use the ``New Folder`` icon |folder| to add a new folder.
|
|||||||
|
|
||||||
Use the *Format* drop down list to select the format of the files to be displayed; choose from *sql*, *csv*, or *All Files*.
|
Use the *Format* drop down list to select the format of the files to be displayed; choose from *sql*, *csv*, or *All Files*.
|
||||||
|
|
||||||
|
Shared Storage
|
||||||
|
*********************
|
||||||
|
.. image:: images/shared_storage.png
|
||||||
|
: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.
|
||||||
|
|
||||||
Other Options
|
Other Options
|
||||||
*********************
|
*********************
|
||||||
|
|
||||||
|
|||||||
@@ -832,6 +832,18 @@ ENABLE_PSQL = False
|
|||||||
##########################################################################
|
##########################################################################
|
||||||
ENABLE_BINARY_PATH_BROWSING = False
|
ENABLE_BINARY_PATH_BROWSING = False
|
||||||
|
|
||||||
|
##########################################################################
|
||||||
|
# In server mode, the SHARED_STORAGE setting is used to enable shared storage.
|
||||||
|
# Specify the name, path, and restricted_access values that should be shared
|
||||||
|
# between users. When restricted_access is set to True, non-admin users cannot
|
||||||
|
# upload/add, delete, or rename files/folders in shared storage, only admins
|
||||||
|
# can do that. Users must provide the absolute path to the folder, and the name
|
||||||
|
# can be anything they see on the user interface.
|
||||||
|
# [{ 'name': 'Shared 1', 'path': '/shared_folder',
|
||||||
|
# 'restricted_access': True/False}]
|
||||||
|
##########################################################################
|
||||||
|
SHARED_STORAGE = []
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
# AUTO_DISCOVER_SERVERS setting is used to enable the pgAdmin to discover the
|
# AUTO_DISCOVER_SERVERS setting is used to enable the pgAdmin to discover the
|
||||||
# database server automatically on the local machine.
|
# database server automatically on the local machine.
|
||||||
|
|||||||
@@ -532,6 +532,37 @@ def index():
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def validate_shared_storage_config(data, shared_storage_keys):
|
||||||
|
"""
|
||||||
|
Validate the config values are correct or not
|
||||||
|
"""
|
||||||
|
if shared_storage_keys.issubset(data.keys()):
|
||||||
|
if isinstance(data['name'], str) and isinstance(
|
||||||
|
data['path'], str) and \
|
||||||
|
isinstance(data['restricted_access'], bool):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_shared_storage_list():
|
||||||
|
"""
|
||||||
|
Return the shared storage list after checking all required keys are present
|
||||||
|
or not in config. This is for server mode only.
|
||||||
|
"""
|
||||||
|
shared_storage_config = []
|
||||||
|
shared_storage_list = []
|
||||||
|
if config.SERVER_MODE:
|
||||||
|
shared_storage_keys = set(['name', 'path', 'restricted_access'])
|
||||||
|
shared_storage_config = [
|
||||||
|
sdir for sdir in config.SHARED_STORAGE if
|
||||||
|
validate_shared_storage_config(sdir, shared_storage_keys)]
|
||||||
|
|
||||||
|
config.SHARED_STORAGE = shared_storage_config
|
||||||
|
shared_storage_list = [sdir['name'] for sdir in shared_storage_config]
|
||||||
|
|
||||||
|
return shared_storage_list
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/js/utils.js")
|
@blueprint.route("/js/utils.js")
|
||||||
@pgCSRFProtect.exempt
|
@pgCSRFProtect.exempt
|
||||||
@login_required
|
@login_required
|
||||||
@@ -599,6 +630,8 @@ def utils():
|
|||||||
auth_source = session['auth_source_manager'][
|
auth_source = session['auth_source_manager'][
|
||||||
'source_friendly_name']
|
'source_friendly_name']
|
||||||
|
|
||||||
|
shared_storage_list = get_shared_storage_list()
|
||||||
|
|
||||||
return make_response(
|
return make_response(
|
||||||
render_template(
|
render_template(
|
||||||
'browser/js/utils.js',
|
'browser/js/utils.js',
|
||||||
@@ -630,7 +663,8 @@ def utils():
|
|||||||
auth_source=auth_source,
|
auth_source=auth_source,
|
||||||
heartbeat_timeout=config.SERVER_HEARTBEAT_TIMEOUT,
|
heartbeat_timeout=config.SERVER_HEARTBEAT_TIMEOUT,
|
||||||
password_length_min=config.PASSWORD_LENGTH_MIN,
|
password_length_min=config.PASSWORD_LENGTH_MIN,
|
||||||
current_ui_lock=current_ui_lock
|
current_ui_lock=current_ui_lock,
|
||||||
|
shared_storage_list=shared_storage_list,
|
||||||
),
|
),
|
||||||
200, {'Content-Type': MIMETYPE_APP_JS})
|
200, {'Content-Type': MIMETYPE_APP_JS})
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ define('pgadmin.browser.utils',
|
|||||||
|
|
||||||
/* GET PSQL Tool related config */
|
/* GET PSQL Tool related config */
|
||||||
pgAdmin['enable_psql'] = '{{enable_psql}}' == 'True';
|
pgAdmin['enable_psql'] = '{{enable_psql}}' == 'True';
|
||||||
|
pgAdmin['shared_storage'] = {{shared_storage_list}}
|
||||||
pgAdmin['platform'] = '{{platform}}';
|
pgAdmin['platform'] = '{{platform}}';
|
||||||
pgAdmin['qt_default_placeholder'] = '{{qt_default_placeholder}}'
|
pgAdmin['qt_default_placeholder'] = '{{qt_default_placeholder}}'
|
||||||
pgAdmin['vw_edt_default_placeholder'] = '{{vw_edt_default_placeholder}}'
|
pgAdmin['vw_edt_default_placeholder'] = '{{vw_edt_default_placeholder}}'
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import string
|
|||||||
import time
|
import time
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
from sys import platform as _platform
|
from sys import platform as _platform
|
||||||
|
from flask_security import current_user
|
||||||
import config
|
import config
|
||||||
import codecs
|
import codecs
|
||||||
import pathlib
|
import pathlib
|
||||||
@@ -30,7 +31,8 @@ from pgadmin.utils import get_storage_directory
|
|||||||
from pgadmin.utils.ajax import make_json_response, unauthorized, \
|
from pgadmin.utils.ajax import make_json_response, unauthorized, \
|
||||||
internal_server_error
|
internal_server_error
|
||||||
from pgadmin.utils.preferences import Preferences
|
from pgadmin.utils.preferences import Preferences
|
||||||
from pgadmin.utils.constants import PREF_LABEL_OPTIONS, MIMETYPE_APP_JS
|
from pgadmin.utils.constants import PREF_LABEL_OPTIONS, MIMETYPE_APP_JS, \
|
||||||
|
MY_STORAGE
|
||||||
from pgadmin.settings.utils import get_file_type_setting
|
from pgadmin.settings.utils import get_file_type_setting
|
||||||
|
|
||||||
# Checks if platform is Windows
|
# Checks if platform is Windows
|
||||||
@@ -152,6 +154,12 @@ class FileManagerModule(PgAdminModule):
|
|||||||
gettext("Last directory visited"), 'text', '/',
|
gettext("Last directory visited"), 'text', '/',
|
||||||
category_label=PREF_LABEL_OPTIONS
|
category_label=PREF_LABEL_OPTIONS
|
||||||
)
|
)
|
||||||
|
self.last_storage = self.preference.register(
|
||||||
|
'options', 'last_storage',
|
||||||
|
gettext("Last storage"), 'text', '',
|
||||||
|
category_label=PREF_LABEL_OPTIONS,
|
||||||
|
hidden=True
|
||||||
|
)
|
||||||
self.file_dialog_view = self.preference.register(
|
self.file_dialog_view = self.preference.register(
|
||||||
'options', 'file_dialog_view',
|
'options', 'file_dialog_view',
|
||||||
gettext("File dialog view"), 'select', 'list',
|
gettext("File dialog view"), 'select', 'list',
|
||||||
@@ -228,6 +236,7 @@ def init_filemanager():
|
|||||||
"platform_type": data['platform_type'],
|
"platform_type": data['platform_type'],
|
||||||
"show_volumes": data['show_volumes'],
|
"show_volumes": data['show_volumes'],
|
||||||
"homedir": data['homedir'],
|
"homedir": data['homedir'],
|
||||||
|
'storage_folder': data['storage_folder'],
|
||||||
"last_selected_format": last_selected_format
|
"last_selected_format": last_selected_format
|
||||||
},
|
},
|
||||||
"security": {
|
"security": {
|
||||||
@@ -263,6 +272,7 @@ def delete_trans_id(trans_id):
|
|||||||
@login_required
|
@login_required
|
||||||
def save_last_directory_visited(trans_id):
|
def save_last_directory_visited(trans_id):
|
||||||
blueprint.last_directory_visited.set(req.json['path'])
|
blueprint.last_directory_visited.set(req.json['path'])
|
||||||
|
blueprint.last_storage.set(req.json['storage_folder'])
|
||||||
return make_json_response(status=200)
|
return make_json_response(status=200)
|
||||||
|
|
||||||
|
|
||||||
@@ -297,9 +307,10 @@ class Filemanager():
|
|||||||
'Code': 0
|
'Code': 0
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, trans_id):
|
def __init__(self, trans_id, ss=''):
|
||||||
self.trans_id = trans_id
|
self.trans_id = trans_id
|
||||||
self.dir = get_storage_directory()
|
self.dir = get_storage_directory()
|
||||||
|
self.sharedDir = get_storage_directory(shared_storage=ss)
|
||||||
|
|
||||||
if self.dir is not None and isinstance(self.dir, list):
|
if self.dir is not None and isinstance(self.dir, list):
|
||||||
self.dir = ""
|
self.dir = ""
|
||||||
@@ -394,6 +405,20 @@ class Filemanager():
|
|||||||
if 'init_path' in params:
|
if 'init_path' in params:
|
||||||
blueprint.last_directory_visited.get(params['init_path'])
|
blueprint.last_directory_visited.get(params['init_path'])
|
||||||
last_dir = blueprint.last_directory_visited.get()
|
last_dir = blueprint.last_directory_visited.get()
|
||||||
|
last_ss_name = blueprint.last_storage.get()
|
||||||
|
if last_ss_name and last_ss_name != MY_STORAGE \
|
||||||
|
and len(config.SHARED_STORAGE) > 0:
|
||||||
|
selectedDir = [sdir for sdir in config.SHARED_STORAGE if
|
||||||
|
sdir['name'] == last_ss_name]
|
||||||
|
last_ss = selectedDir[0]['path'] if len(
|
||||||
|
selectedDir) == 1 else storage_dir
|
||||||
|
else:
|
||||||
|
if last_ss_name != MY_STORAGE:
|
||||||
|
last_dir = '/'
|
||||||
|
blueprint.last_storage.set(MY_STORAGE)
|
||||||
|
|
||||||
|
last_ss = storage_dir
|
||||||
|
|
||||||
check_dir_exists = False
|
check_dir_exists = False
|
||||||
if last_dir is None:
|
if last_dir is None:
|
||||||
last_dir = "/"
|
last_dir = "/"
|
||||||
@@ -404,12 +429,13 @@ class Filemanager():
|
|||||||
last_dir = homedir
|
last_dir = homedir
|
||||||
|
|
||||||
if check_dir_exists:
|
if check_dir_exists:
|
||||||
last_dir = Filemanager.get_closest_parent(storage_dir, last_dir)
|
last_dir = Filemanager.get_closest_parent(last_ss, last_dir)
|
||||||
|
|
||||||
# create configs using above configs
|
# create configs using above configs
|
||||||
configs = {
|
configs = {
|
||||||
"fileroot": last_dir,
|
"fileroot": last_dir,
|
||||||
"homedir": homedir,
|
"homedir": homedir,
|
||||||
|
'storage_folder': last_ss_name,
|
||||||
"dialog_type": fm_type,
|
"dialog_type": fm_type,
|
||||||
"title": title,
|
"title": title,
|
||||||
"upload": {
|
"upload": {
|
||||||
@@ -743,7 +769,11 @@ class Filemanager():
|
|||||||
trans_data = Filemanager.get_trasaction_selection(self.trans_id)
|
trans_data = Filemanager.get_trasaction_selection(self.trans_id)
|
||||||
the_dir = None
|
the_dir = None
|
||||||
if config.SERVER_MODE:
|
if config.SERVER_MODE:
|
||||||
the_dir = self.dir
|
if self.sharedDir and len(config.SHARED_STORAGE) > 0:
|
||||||
|
the_dir = self.sharedDir
|
||||||
|
else:
|
||||||
|
the_dir = self.dir
|
||||||
|
|
||||||
if the_dir is not None and not the_dir.endswith('/'):
|
if the_dir is not None and not the_dir.endswith('/'):
|
||||||
the_dir += '/'
|
the_dir += '/'
|
||||||
|
|
||||||
@@ -751,6 +781,23 @@ class Filemanager():
|
|||||||
the_dir, path, trans_data, file_type, show_hidden)
|
the_dir, path, trans_data, file_type, show_hidden)
|
||||||
return filelist
|
return filelist
|
||||||
|
|
||||||
|
def check_access(self, ss, mode):
|
||||||
|
if self.sharedDir:
|
||||||
|
selectedDirList = [sdir for sdir in config.SHARED_STORAGE if
|
||||||
|
sdir['name'] == ss]
|
||||||
|
selectedDir = selectedDirList[0] if len(
|
||||||
|
selectedDirList) == 1 else None
|
||||||
|
|
||||||
|
if selectedDir:
|
||||||
|
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."))
|
||||||
|
|
||||||
def rename(self, old=None, new=None):
|
def rename(self, old=None, new=None):
|
||||||
"""
|
"""
|
||||||
Rename file or folder
|
Rename file or folder
|
||||||
@@ -758,7 +805,10 @@ class Filemanager():
|
|||||||
if not self.validate_request('rename'):
|
if not self.validate_request('rename'):
|
||||||
return unauthorized(self.ERROR_NOT_ALLOWED['Error'])
|
return unauthorized(self.ERROR_NOT_ALLOWED['Error'])
|
||||||
|
|
||||||
the_dir = self.dir if self.dir is not None else ''
|
if self.sharedDir:
|
||||||
|
the_dir = self.sharedDir
|
||||||
|
else:
|
||||||
|
the_dir = self.dir if self.dir is not None else ''
|
||||||
|
|
||||||
Filemanager.check_access_permission(the_dir, old)
|
Filemanager.check_access_permission(the_dir, old)
|
||||||
Filemanager.check_access_permission(the_dir, new)
|
Filemanager.check_access_permission(the_dir, new)
|
||||||
@@ -801,8 +851,10 @@ class Filemanager():
|
|||||||
"""
|
"""
|
||||||
if not self.validate_request('delete'):
|
if not self.validate_request('delete'):
|
||||||
return unauthorized(self.ERROR_NOT_ALLOWED['Error'])
|
return unauthorized(self.ERROR_NOT_ALLOWED['Error'])
|
||||||
|
if self.sharedDir:
|
||||||
the_dir = self.dir if self.dir is not None else ''
|
the_dir = self.sharedDir
|
||||||
|
else:
|
||||||
|
the_dir = self.dir if self.dir is not None else ''
|
||||||
orig_path = "{0}{1}".format(the_dir, path)
|
orig_path = "{0}{1}".format(the_dir, path)
|
||||||
|
|
||||||
Filemanager.check_access_permission(the_dir, path)
|
Filemanager.check_access_permission(the_dir, path)
|
||||||
@@ -825,7 +877,10 @@ class Filemanager():
|
|||||||
if not self.validate_request('upload'):
|
if not self.validate_request('upload'):
|
||||||
return unauthorized(self.ERROR_NOT_ALLOWED['Error'])
|
return unauthorized(self.ERROR_NOT_ALLOWED['Error'])
|
||||||
|
|
||||||
the_dir = self.dir if self.dir is not None else ''
|
if self.sharedDir:
|
||||||
|
the_dir = self.sharedDir
|
||||||
|
else:
|
||||||
|
the_dir = self.dir if self.dir is not None else ''
|
||||||
|
|
||||||
try:
|
try:
|
||||||
path = req.form.get('currentpath')
|
path = req.form.get('currentpath')
|
||||||
@@ -990,7 +1045,10 @@ class Filemanager():
|
|||||||
if not self.validate_request('create'):
|
if not self.validate_request('create'):
|
||||||
return unauthorized(self.ERROR_NOT_ALLOWED['Error'])
|
return unauthorized(self.ERROR_NOT_ALLOWED['Error'])
|
||||||
|
|
||||||
user_dir = self.dir if self.dir is not None else ''
|
if self.sharedDir and len(config.SHARED_STORAGE) > 0:
|
||||||
|
user_dir = self.sharedDir
|
||||||
|
else:
|
||||||
|
user_dir = self.dir if self.dir is not None else ''
|
||||||
|
|
||||||
Filemanager.check_access_permission(user_dir, "{}{}".format(
|
Filemanager.check_access_permission(user_dir, "{}{}".format(
|
||||||
path, name))
|
path, name))
|
||||||
@@ -1018,7 +1076,11 @@ class Filemanager():
|
|||||||
if not self.validate_request('download'):
|
if not self.validate_request('download'):
|
||||||
return unauthorized(self.ERROR_NOT_ALLOWED['Error'])
|
return unauthorized(self.ERROR_NOT_ALLOWED['Error'])
|
||||||
|
|
||||||
the_dir = self.dir if self.dir is not None else ''
|
if self.sharedDir and len(config.SHARED_STORAGE) > 0:
|
||||||
|
the_dir = self.sharedDir
|
||||||
|
else:
|
||||||
|
the_dir = self.dir if self.dir is not None else ''
|
||||||
|
|
||||||
orig_path = "{0}{1}".format(the_dir, path)
|
orig_path = "{0}{1}".format(the_dir, path)
|
||||||
|
|
||||||
Filemanager.check_access_permission(
|
Filemanager.check_access_permission(
|
||||||
@@ -1057,13 +1119,13 @@ def file_manager(trans_id):
|
|||||||
It gets unique transaction id from post request and
|
It gets unique transaction id from post request and
|
||||||
rotate it into Filemanager class.
|
rotate it into Filemanager class.
|
||||||
"""
|
"""
|
||||||
my_fm = Filemanager(trans_id)
|
|
||||||
mode = ''
|
mode = ''
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
if req.method == 'POST':
|
if req.method == 'POST':
|
||||||
if req.files:
|
if req.files:
|
||||||
mode = 'add'
|
mode = 'add'
|
||||||
kwargs = {'req': req}
|
kwargs = {'req': req,
|
||||||
|
'storage_folder': req.form.get('storage_folder', None)}
|
||||||
else:
|
else:
|
||||||
kwargs = json.loads(req.data)
|
kwargs = json.loads(req.data)
|
||||||
kwargs['req'] = req
|
kwargs['req'] = req
|
||||||
@@ -1075,14 +1137,23 @@ def file_manager(trans_id):
|
|||||||
'name': req.args['name'] if 'name' in req.args else ''
|
'name': req.args['name'] if 'name' in req.args else ''
|
||||||
}
|
}
|
||||||
mode = req.args['mode']
|
mode = req.args['mode']
|
||||||
|
ss = kwargs['storage_folder'] if 'storage_folder' in kwargs else None
|
||||||
|
my_fm = Filemanager(trans_id, ss)
|
||||||
|
|
||||||
|
if ss and mode in ['upload', 'rename', 'delete', 'addfolder', 'add']:
|
||||||
|
my_fm.check_access(ss, mode)
|
||||||
func = getattr(my_fm, mode)
|
func = getattr(my_fm, mode)
|
||||||
try:
|
try:
|
||||||
if mode in ['getfolder', 'download']:
|
if mode in ['getfolder', 'download']:
|
||||||
kwargs.pop('name', None)
|
kwargs.pop('name', None)
|
||||||
|
|
||||||
|
if mode in ['add']:
|
||||||
|
kwargs.pop('storage_folder', None)
|
||||||
|
|
||||||
if mode in ['addfolder', 'getfolder', 'rename', 'delete',
|
if mode in ['addfolder', 'getfolder', 'rename', 'delete',
|
||||||
'is_file_exist', 'req', 'permission', 'download']:
|
'is_file_exist', 'req', 'permission', 'download']:
|
||||||
kwargs.pop('req', None)
|
kwargs.pop('req', None)
|
||||||
|
kwargs.pop('storage_folder', None)
|
||||||
|
|
||||||
res = func(**kwargs)
|
res = func(**kwargs)
|
||||||
except PermissionError as e:
|
except PermissionError as e:
|
||||||
|
|||||||
@@ -11,13 +11,17 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|||||||
import { DefaultButton, PgButtonGroup, PgIconButton, PrimaryButton } from '../../../../../static/js/components/Buttons';
|
import { DefaultButton, PgButtonGroup, PgIconButton, PrimaryButton } from '../../../../../static/js/components/Buttons';
|
||||||
import { useModalStyles } from '../../../../../static/js/helpers/ModalProvider';
|
import { useModalStyles } from '../../../../../static/js/helpers/ModalProvider';
|
||||||
import CloseIcon from '@material-ui/icons/CloseRounded';
|
import CloseIcon from '@material-ui/icons/CloseRounded';
|
||||||
|
import FolderSharedIcon from '@material-ui/icons/FolderShared';
|
||||||
|
import FolderIcon from '@material-ui/icons/Folder';
|
||||||
import CheckRoundedIcon from '@material-ui/icons/CheckRounded';
|
import CheckRoundedIcon from '@material-ui/icons/CheckRounded';
|
||||||
import HomeRoundedIcon from '@material-ui/icons/HomeRounded';
|
import HomeRoundedIcon from '@material-ui/icons/HomeRounded';
|
||||||
import ArrowUpwardRoundedIcon from '@material-ui/icons/ArrowUpwardRounded';
|
import ArrowUpwardRoundedIcon from '@material-ui/icons/ArrowUpwardRounded';
|
||||||
|
import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown';
|
||||||
import MoreHorizRoundedIcon from '@material-ui/icons/MoreHorizRounded';
|
import MoreHorizRoundedIcon from '@material-ui/icons/MoreHorizRounded';
|
||||||
import SyncRoundedIcon from '@material-ui/icons/SyncRounded';
|
import SyncRoundedIcon from '@material-ui/icons/SyncRounded';
|
||||||
import CreateNewFolderRoundedIcon from '@material-ui/icons/CreateNewFolderRounded';
|
import CreateNewFolderRoundedIcon from '@material-ui/icons/CreateNewFolderRounded';
|
||||||
import GetAppRoundedIcon from '@material-ui/icons/GetAppRounded';
|
import GetAppRoundedIcon from '@material-ui/icons/GetAppRounded';
|
||||||
|
import pgAdmin from 'sources/pgadmin';
|
||||||
import gettext from 'sources/gettext';
|
import gettext from 'sources/gettext';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { FormFooterMessage, InputSelectNonSearch, InputText, MESSAGE_TYPE } from '../../../../../static/js/components/FormComponents';
|
import { FormFooterMessage, InputSelectNonSearch, InputText, MESSAGE_TYPE } from '../../../../../static/js/components/FormComponents';
|
||||||
@@ -32,6 +36,7 @@ import convert from 'convert-units';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { downloadBlob } from '../../../../../static/js/utils';
|
import { downloadBlob } from '../../../../../static/js/utils';
|
||||||
import ErrorBoundary from '../../../../../static/js/helpers/ErrorBoundary';
|
import ErrorBoundary from '../../../../../static/js/helpers/ErrorBoundary';
|
||||||
|
import { MY_STORAGE } from './FileManagerConstants';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
const useStyles = makeStyles((theme)=>({
|
const useStyles = makeStyles((theme)=>({
|
||||||
@@ -85,6 +90,15 @@ const useStyles = makeStyles((theme)=>({
|
|||||||
backgroundColor: theme.palette.background.default,
|
backgroundColor: theme.palette.background.default,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
...theme.mixins.panelBorder.all,
|
...theme.mixins.panelBorder.all,
|
||||||
|
},
|
||||||
|
sharedStorage: {
|
||||||
|
width: '3rem !important',
|
||||||
|
},
|
||||||
|
storageName: {
|
||||||
|
paddingLeft: '0.2rem'
|
||||||
|
},
|
||||||
|
sharedIcon: {
|
||||||
|
width: '1.3rem'
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -137,6 +151,7 @@ export class FileManagerUtils {
|
|||||||
this.config = {};
|
this.config = {};
|
||||||
this.currPath = '';
|
this.currPath = '';
|
||||||
this.separator = '/';
|
this.separator = '/';
|
||||||
|
this.storage_folder = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
get transId() {
|
get transId() {
|
||||||
@@ -197,23 +212,25 @@ export class FileManagerUtils {
|
|||||||
return filename.split('.').pop();
|
return filename.split('.').pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getFolder(path) {
|
async getFolder(path, sharedFolder=null) {
|
||||||
const newPath = path || this.fileRoot;
|
const newPath = path || this.fileRoot;
|
||||||
let res = await this.api.post(this.fileConnectorUrl, {
|
let res = await this.api.post(this.fileConnectorUrl, {
|
||||||
'path': newPath,
|
'path': newPath,
|
||||||
'mode': 'getfolder',
|
'mode': 'getfolder',
|
||||||
'file_type': this.config.options.last_selected_format || '*',
|
'file_type': this.config.options.last_selected_format || '*',
|
||||||
'show_hidden': this.showHiddenFiles,
|
'show_hidden': this.showHiddenFiles,
|
||||||
|
'storage_folder': sharedFolder,
|
||||||
});
|
});
|
||||||
this.currPath = newPath;
|
this.currPath = newPath;
|
||||||
return res.data.data.result;
|
return res.data.data.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async addFolder(row) {
|
async addFolder(row, ss) {
|
||||||
let res = await this.api.post(this.fileConnectorUrl, {
|
let res = await this.api.post(this.fileConnectorUrl, {
|
||||||
'path': this.currPath,
|
'path': this.currPath,
|
||||||
'mode': 'addfolder',
|
'mode': 'addfolder',
|
||||||
'name': row.Filename,
|
'name': row.Filename,
|
||||||
|
'storage_folder': ss
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
Filename: res.data.data.result.Name,
|
Filename: res.data.data.result.Name,
|
||||||
@@ -225,11 +242,12 @@ export class FileManagerUtils {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async renameItem(row) {
|
async renameItem(row, ss) {
|
||||||
let res = await this.api.post(this.fileConnectorUrl, {
|
let res = await this.api.post(this.fileConnectorUrl, {
|
||||||
'mode': 'rename',
|
'mode': 'rename',
|
||||||
'old': row.Path,
|
'old': row.Path,
|
||||||
'new': row.Filename,
|
'new': row.Filename,
|
||||||
|
'storage_folder': ss
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
...row,
|
...row,
|
||||||
@@ -238,20 +256,22 @@ export class FileManagerUtils {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteItem(row, fileName) {
|
async deleteItem(row, ss, fileName) {
|
||||||
const path = fileName ? this.join(row.Path, fileName) : row.Path;
|
const path = fileName ? this.join(row.Path, fileName) : row.Path;
|
||||||
await this.api.post(this.fileConnectorUrl, {
|
await this.api.post(this.fileConnectorUrl, {
|
||||||
'mode': 'delete',
|
'mode': 'delete',
|
||||||
'path': path,
|
'path': path,
|
||||||
|
'storage_folder': ss
|
||||||
});
|
});
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
async uploadItem(fileObj, onUploadProgress) {
|
async uploadItem(fileObj, ss, onUploadProgress) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('newfile', fileObj);
|
formData.append('newfile', fileObj);
|
||||||
formData.append('mode', 'add');
|
formData.append('mode', 'add');
|
||||||
formData.append('currentpath', this.join(this.currPath, ''));
|
formData.append('currentpath', this.join(this.currPath, ''));
|
||||||
|
formData.append('storage_folder', ss);
|
||||||
return this.api({
|
return this.api({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: this.fileConnectorUrl,
|
url: this.fileConnectorUrl,
|
||||||
@@ -263,15 +283,16 @@ export class FileManagerUtils {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async setLastVisitedDir(path) {
|
async setLastVisitedDir(path, ss) {
|
||||||
return this.api.post(url_for('file_manager.save_last_dir', {
|
return this.api.post(url_for('file_manager.save_last_dir', {
|
||||||
trans_id: this.transId,
|
trans_id: this.transId,
|
||||||
}), {
|
}), {
|
||||||
'path': path,
|
'path': path,
|
||||||
|
'storage_folder': ss
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async downloadFile(row) {
|
async downloadFile(row, ss) {
|
||||||
let res = await this.api({
|
let res = await this.api({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: this.fileConnectorUrl,
|
url: this.fileConnectorUrl,
|
||||||
@@ -279,6 +300,7 @@ export class FileManagerUtils {
|
|||||||
data: {
|
data: {
|
||||||
'mode': 'download',
|
'mode': 'download',
|
||||||
'path': row.Path,
|
'path': row.Path,
|
||||||
|
'storage_folder': ss,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
downloadBlob(res.data, res.headers.filename);
|
downloadBlob(res.data, res.headers.filename);
|
||||||
@@ -354,6 +376,7 @@ export class FileManagerUtils {
|
|||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ConfirmFile({text, onYes, onNo}) {
|
function ConfirmFile({text, onYes, onNo}) {
|
||||||
@@ -401,6 +424,8 @@ export default function FileManager({params, closeModal, onOK, onCancel}) {
|
|||||||
const selectedRowIdx = useRef();
|
const selectedRowIdx = useRef();
|
||||||
const optionsRef = React.useRef(null);
|
const optionsRef = React.useRef(null);
|
||||||
const saveAsRef = React.useRef(null);
|
const saveAsRef = React.useRef(null);
|
||||||
|
const sharedSRef = React.useRef(null);
|
||||||
|
const [selectedSS, setSelectedSS] = React.useState(MY_STORAGE);
|
||||||
const [operation, setOperation] = useState({
|
const [operation, setOperation] = useState({
|
||||||
type: null, idx: null
|
type: null, idx: null
|
||||||
});
|
});
|
||||||
@@ -421,24 +446,30 @@ export default function FileManager({params, closeModal, onOK, onCancel}) {
|
|||||||
return `${filteredItems.length} of ${items.length} ${suffix}`;
|
return `${filteredItems.length} of ${items.length} ${suffix}`;
|
||||||
}, [items, filteredItems]);
|
}, [items, filteredItems]);
|
||||||
|
|
||||||
const openDir = async (dirPath)=>{
|
const changeDir = async(storage) => {
|
||||||
|
setSelectedSS(storage);
|
||||||
|
fmUtilsObj.storage_folder = storage;
|
||||||
|
await openDir('/', storage);
|
||||||
|
};
|
||||||
|
const openDir = async (dirPath, changeStoragePath=null)=>{
|
||||||
setErrorMsg('');
|
setErrorMsg('');
|
||||||
setLoaderText('Loading...');
|
setLoaderText('Loading...');
|
||||||
try {
|
try {
|
||||||
if(fmUtilsObj.isWinDrive(dirPath)) {
|
if(fmUtilsObj.isWinDrive(dirPath)) {
|
||||||
dirPath += fmUtilsObj.separator;
|
dirPath += fmUtilsObj.separator;
|
||||||
}
|
}
|
||||||
let newItems = await fmUtilsObj.getFolder(dirPath || fmUtilsObj.currPath);
|
let newItems = await fmUtilsObj.getFolder(dirPath || fmUtilsObj.currPath, changeStoragePath);
|
||||||
setItems(newItems);
|
setItems(newItems);
|
||||||
setPath(fmUtilsObj.currPath);
|
setPath(fmUtilsObj.currPath);
|
||||||
params.dialog_type == 'storage_dialog' && fmUtilsObj.setLastVisitedDir(fmUtilsObj.currPath);
|
params.dialog_type == 'storage_dialog' && fmUtilsObj.setLastVisitedDir(fmUtilsObj.currPath, selectedSS);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
setErrorMsg(parseApiError(error));
|
setErrorMsg(parseApiError(error));
|
||||||
}
|
}
|
||||||
setLoaderText('');
|
setLoaderText('');
|
||||||
};
|
};
|
||||||
const completeOperation = async (oldRow, newRow, rowIdx, func)=>{
|
|
||||||
|
const completeOperation = async (oldRow, newRow, rowIdx, selectedSS, func)=>{
|
||||||
setOperation({});
|
setOperation({});
|
||||||
if(oldRow?.Filename == newRow.Filename) {
|
if(oldRow?.Filename == newRow.Filename) {
|
||||||
setItems((prev)=>[
|
setItems((prev)=>[
|
||||||
@@ -454,7 +485,7 @@ export default function FileManager({params, closeModal, onOK, onCancel}) {
|
|||||||
...prev.slice(rowIdx+1)
|
...prev.slice(rowIdx+1)
|
||||||
]);
|
]);
|
||||||
try {
|
try {
|
||||||
const actualRow = await func(newRow);
|
const actualRow = await func(newRow, selectedSS);
|
||||||
setItems((prev)=>[
|
setItems((prev)=>[
|
||||||
...prev.slice(0, rowIdx),
|
...prev.slice(0, rowIdx),
|
||||||
actualRow,
|
actualRow,
|
||||||
@@ -479,7 +510,7 @@ export default function FileManager({params, closeModal, onOK, onCancel}) {
|
|||||||
const onDownload = async ()=>{
|
const onDownload = async ()=>{
|
||||||
setLoaderText('Downloading...');
|
setLoaderText('Downloading...');
|
||||||
try {
|
try {
|
||||||
await fmUtilsObj.downloadFile(filteredItems[selectedRowIdx.current]);
|
await fmUtilsObj.downloadFile(filteredItems[selectedRowIdx.current], selectedSS);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setErrorMsg(parseApiError(error));
|
setErrorMsg(parseApiError(error));
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -497,7 +528,7 @@ export default function FileManager({params, closeModal, onOK, onCancel}) {
|
|||||||
onComplete: async (row, rowIdx)=>{
|
onComplete: async (row, rowIdx)=>{
|
||||||
setErrorMsg('');
|
setErrorMsg('');
|
||||||
setLoaderText('Creating folder...');
|
setLoaderText('Creating folder...');
|
||||||
await completeOperation(null, row, rowIdx, fmUtilsObj.addFolder.bind(fmUtilsObj));
|
await completeOperation(null, row, rowIdx, selectedSS, fmUtilsObj.addFolder.bind(fmUtilsObj));
|
||||||
setLoaderText('');
|
setLoaderText('');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -515,7 +546,7 @@ export default function FileManager({params, closeModal, onOK, onCancel}) {
|
|||||||
setErrorMsg('');
|
setErrorMsg('');
|
||||||
setLoaderText('Renaming...');
|
setLoaderText('Renaming...');
|
||||||
let oldRow = items[rowIdx];
|
let oldRow = items[rowIdx];
|
||||||
await completeOperation(oldRow, row, rowIdx, fmUtilsObj.renameItem.bind(fmUtilsObj));
|
await completeOperation(oldRow, row, rowIdx, selectedSS,fmUtilsObj.renameItem.bind(fmUtilsObj));
|
||||||
setLoaderText('');
|
setLoaderText('');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -530,7 +561,7 @@ export default function FileManager({params, closeModal, onOK, onCancel}) {
|
|||||||
setConfirmFile([null, null]);
|
setConfirmFile([null, null]);
|
||||||
setLoaderText('Deleting...');
|
setLoaderText('Deleting...');
|
||||||
try {
|
try {
|
||||||
await fmUtilsObj.deleteItem(items[selectedRowIdx.current]);
|
await fmUtilsObj.deleteItem(items[selectedRowIdx.current],selectedSS);
|
||||||
setItems((prev)=>[
|
setItems((prev)=>[
|
||||||
...prev.slice(0, selectedRowIdx.current),
|
...prev.slice(0, selectedRowIdx.current),
|
||||||
...prev.slice(selectedRowIdx.current+1),
|
...prev.slice(selectedRowIdx.current+1),
|
||||||
@@ -567,8 +598,8 @@ export default function FileManager({params, closeModal, onOK, onCancel}) {
|
|||||||
if(exists) {
|
if(exists) {
|
||||||
setLoaderText('');
|
setLoaderText('');
|
||||||
setConfirmFile([gettext('Are you sure you want to replace this file?'), async ()=>{
|
setConfirmFile([gettext('Are you sure you want to replace this file?'), async ()=>{
|
||||||
await fmUtilsObj.setLastVisitedDir(fmUtilsObj.currPath);
|
await fmUtilsObj.setLastVisitedDir(fmUtilsObj.currPath, selectedSS);
|
||||||
onOK?.(onOkPath);
|
onOK?.(onOkPath, selectedSS);
|
||||||
closeModal();
|
closeModal();
|
||||||
}]);
|
}]);
|
||||||
return;
|
return;
|
||||||
@@ -576,13 +607,13 @@ export default function FileManager({params, closeModal, onOK, onCancel}) {
|
|||||||
} else if(selectedRowIdx?.current >= 0 && filteredItems[selectedRowIdx?.current]) {
|
} else if(selectedRowIdx?.current >= 0 && filteredItems[selectedRowIdx?.current]) {
|
||||||
onOkPath = filteredItems[selectedRowIdx?.current]['Path'];
|
onOkPath = filteredItems[selectedRowIdx?.current]['Path'];
|
||||||
}
|
}
|
||||||
await fmUtilsObj.setLastVisitedDir(fmUtilsObj.currPath);
|
await fmUtilsObj.setLastVisitedDir(fmUtilsObj.currPath, selectedSS);
|
||||||
onOK?.(onOkPath);
|
onOK?.(onOkPath, selectedSS);
|
||||||
closeModal();
|
closeModal();
|
||||||
}, [filteredItems, saveAs, fileType]);
|
}, [filteredItems, saveAs, fileType]);
|
||||||
const onItemEnter = useCallback(async (row)=>{
|
const onItemEnter = useCallback(async (row)=>{
|
||||||
if(row.file_type == 'dir' || row.file_type == 'drive') {
|
if(row.file_type == 'dir' || row.file_type == 'drive') {
|
||||||
await openDir(row.Path);
|
await openDir(row.Path, selectedSS);
|
||||||
} else {
|
} else {
|
||||||
if(params.dialog_type == 'select_file') {
|
if(params.dialog_type == 'select_file') {
|
||||||
onOkClick();
|
onOkClick();
|
||||||
@@ -625,8 +656,15 @@ export default function FileManager({params, closeModal, onOK, onCancel}) {
|
|||||||
} else {
|
} else {
|
||||||
setViewMode('list');
|
setViewMode('list');
|
||||||
}
|
}
|
||||||
openDir(params?.path);
|
if (fmUtilsObj.config.options.storage_folder == '') {
|
||||||
params?.path && fmUtilsObj.setLastVisitedDir(params?.path);
|
setSelectedSS(MY_STORAGE);
|
||||||
|
} else {
|
||||||
|
fmUtilsObj.storage_folder = fmUtilsObj.config.options.storage_folder;
|
||||||
|
setSelectedSS(fmUtilsObj.config.options.storage_folder);
|
||||||
|
}
|
||||||
|
|
||||||
|
openDir(params?.path, fmUtilsObj.config.options.storage_folder);
|
||||||
|
params?.path && fmUtilsObj.setLastVisitedDir(params?.path, selectedSS);
|
||||||
};
|
};
|
||||||
init();
|
init();
|
||||||
setTimeout(()=>{
|
setTimeout(()=>{
|
||||||
@@ -649,6 +687,7 @@ export default function FileManager({params, closeModal, onOK, onCancel}) {
|
|||||||
okBtnText = gettext('Create');
|
okBtnText = gettext('Create');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<Box display="flex" flexDirection="column" height="100%" className={modalClasses.container}>
|
<Box display="flex" flexDirection="column" height="100%" className={modalClasses.container}>
|
||||||
@@ -657,11 +696,16 @@ export default function FileManager({params, closeModal, onOK, onCancel}) {
|
|||||||
{Boolean(confirmText) && <ConfirmFile text={confirmText} onNo={()=>setConfirmFile([null, null])} onYes={onConfirmYes}/>}
|
{Boolean(confirmText) && <ConfirmFile text={confirmText} onNo={()=>setConfirmFile([null, null])} onYes={onConfirmYes}/>}
|
||||||
<Box className={classes.toolbar}>
|
<Box className={classes.toolbar}>
|
||||||
<PgButtonGroup size="small" style={{flexGrow: 1}}>
|
<PgButtonGroup size="small" style={{flexGrow: 1}}>
|
||||||
|
{ pgAdmin.server_mode == 'True' && pgAdmin.shared_storage.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}/>
|
||||||
|
: <></>
|
||||||
|
}
|
||||||
<PgIconButton title={gettext('Home')} onClick={async ()=>{
|
<PgIconButton title={gettext('Home')} onClick={async ()=>{
|
||||||
await openDir(fmUtilsObj.config?.options?.homedir);
|
await openDir(fmUtilsObj.config?.options?.homedir, selectedSS);
|
||||||
}} icon={<HomeRoundedIcon />} disabled={showUploader} />
|
}} icon={<HomeRoundedIcon />} disabled={showUploader} />
|
||||||
<PgIconButton title={gettext('Go Back')} onClick={async ()=>{
|
<PgIconButton title={gettext('Go Back')} onClick={async ()=>{
|
||||||
await openDir(fmUtilsObj.dirname(fmUtilsObj.currPath));
|
await openDir(fmUtilsObj.dirname(fmUtilsObj.currPath), selectedSS);
|
||||||
}} icon={<ArrowUpwardRoundedIcon />} disabled={!fmUtilsObj.dirname(fmUtilsObj.currPath) || showUploader} />
|
}} icon={<ArrowUpwardRoundedIcon />} disabled={!fmUtilsObj.dirname(fmUtilsObj.currPath) || showUploader} />
|
||||||
<InputText className={classes.inputFilename}
|
<InputText className={classes.inputFilename}
|
||||||
data-label="file-path"
|
data-label="file-path"
|
||||||
@@ -672,8 +716,9 @@ export default function FileManager({params, closeModal, onOK, onCancel}) {
|
|||||||
await openDir(path);
|
await openDir(path);
|
||||||
}
|
}
|
||||||
}} value={path} onChange={setPath} readonly={showUploader} />
|
}} value={path} onChange={setPath} readonly={showUploader} />
|
||||||
|
|
||||||
<PgIconButton title={gettext('Refresh')} onClick={async ()=>{
|
<PgIconButton title={gettext('Refresh')} onClick={async ()=>{
|
||||||
await openDir();
|
await openDir(path, selectedSS);
|
||||||
}} icon={<SyncRoundedIcon />} disabled={showUploader} />
|
}} icon={<SyncRoundedIcon />} disabled={showUploader} />
|
||||||
</PgButtonGroup>
|
</PgButtonGroup>
|
||||||
<InputText type="search" className={classes.inputSearch} data-label="search" placeholder={gettext('Search')} value={search} onChange={setSearch} />
|
<InputText type="search" className={classes.inputSearch} data-label="search" placeholder={gettext('Search')} value={search} onChange={setSearch} />
|
||||||
@@ -714,9 +759,33 @@ export default function FileManager({params, closeModal, onOK, onCancel}) {
|
|||||||
<PgMenuItem hasCheck checked={fmUtilsObj.showHiddenFiles} onClick={async (e)=>{
|
<PgMenuItem hasCheck checked={fmUtilsObj.showHiddenFiles} onClick={async (e)=>{
|
||||||
e.keepOpen = false;
|
e.keepOpen = false;
|
||||||
fmUtilsObj.showHiddenFiles = !fmUtilsObj.showHiddenFiles;
|
fmUtilsObj.showHiddenFiles = !fmUtilsObj.showHiddenFiles;
|
||||||
await openDir();
|
await openDir(fmUtilsObj.currPath, selectedSS);
|
||||||
}}>{gettext('Show Hidden Files')}</PgMenuItem>
|
}}>{gettext('Show Hidden Files')}</PgMenuItem>
|
||||||
</PgMenu>
|
</PgMenu>
|
||||||
|
<PgMenu
|
||||||
|
anchorRef={sharedSRef}
|
||||||
|
open={openMenuName=='menu-shared-storage'}
|
||||||
|
onClose={onMenuClose}
|
||||||
|
label={gettext(`${selectedSS}`)}
|
||||||
|
>
|
||||||
|
<PgMenuItem hasCheck value="my_storage" checked={selectedSS == MY_STORAGE}
|
||||||
|
onClick={async (option)=> {
|
||||||
|
option.keepOpen = false;
|
||||||
|
await changeDir(option.value);
|
||||||
|
}}><FolderIcon className={classes.sharedIcon}/><Box className={classes.storageName}>{gettext('My Storage')}</Box></PgMenuItem>
|
||||||
|
|
||||||
|
{
|
||||||
|
pgAdmin.shared_storage.map((ss)=> {
|
||||||
|
return (
|
||||||
|
<PgMenuItem key={ss} hasCheck value={ss} checked={selectedSS == ss}
|
||||||
|
onClick={async(option)=> {
|
||||||
|
option.keepOpen = false;
|
||||||
|
await changeDir(option.value);
|
||||||
|
}}><FolderSharedIcon className={classes.sharedIcon}/><Box className={classes.storageName}>{gettext(ss)}</Box></PgMenuItem>);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</PgMenu>
|
||||||
</Box>
|
</Box>
|
||||||
<Box flexGrow="1" display="flex" flexDirection="column" position="relative" overflow="hidden">
|
<Box flexGrow="1" display="flex" flexDirection="column" position="relative" overflow="hidden">
|
||||||
{showUploader &&
|
{showUploader &&
|
||||||
@@ -724,7 +793,7 @@ export default function FileManager({params, closeModal, onOK, onCancel}) {
|
|||||||
onClose={async (filesUploaded)=>{
|
onClose={async (filesUploaded)=>{
|
||||||
setShowUploader(false);
|
setShowUploader(false);
|
||||||
if(filesUploaded) {
|
if(filesUploaded) {
|
||||||
await openDir();
|
await openDir(fmUtilsObj.currPath, selectedSS);
|
||||||
}
|
}
|
||||||
}}/>}
|
}}/>}
|
||||||
{viewMode == 'list' &&
|
{viewMode == 'list' &&
|
||||||
|
|||||||
@@ -9,3 +9,5 @@
|
|||||||
export const FILE_MANGER_EVENTS = {
|
export const FILE_MANGER_EVENTS = {
|
||||||
ADD_FOLDER: 'ADD_FOLDER'
|
ADD_FOLDER: 'ADD_FOLDER'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const MY_STORAGE = 'my_storage';
|
||||||
@@ -148,7 +148,7 @@ export default function Uploader({fmUtilsObj, onClose}) {
|
|||||||
type: 'started',
|
type: 'started',
|
||||||
id: upfile.id,
|
id: upfile.id,
|
||||||
});
|
});
|
||||||
await fmUtilsObj.uploadItem(upfile.file, (progressEvent)=>{
|
await fmUtilsObj.uploadItem(upfile.file, fmUtilsObj.storage_folder, (progressEvent)=>{
|
||||||
const {loaded, total} = progressEvent;
|
const {loaded, total} = progressEvent;
|
||||||
const percent = Math.floor((loaded * 100) / total);
|
const percent = Math.floor((loaded * 100) / total);
|
||||||
dispatchFileAction({
|
dispatchFileAction({
|
||||||
|
|||||||
@@ -369,7 +369,7 @@ export default function PreferencesComponent({ ...props }) {
|
|||||||
pgAdmin.Browser.Events.on('preferences:tree:selected', (event, item) => {
|
pgAdmin.Browser.Events.on('preferences:tree:selected', (event, item) => {
|
||||||
if (item.type == FileType.File) {
|
if (item.type == FileType.File) {
|
||||||
prefSchema.current.schemaFields.forEach((field) => {
|
prefSchema.current.schemaFields.forEach((field) => {
|
||||||
field.visible = field.parentId === item._metadata.data.id;
|
field.visible = field.parentId === item._metadata.data.id && !field?.hidden ;
|
||||||
if(field.visible && _.isNull(firstElement)) {
|
if(field.visible && _.isNull(firstElement)) {
|
||||||
firstElement = field;
|
firstElement = field;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ const ALLOWED_PROPS_FIELD_COMMON = [
|
|||||||
'mode', 'value', 'readonly', 'disabled', 'hasError', 'id',
|
'mode', 'value', 'readonly', 'disabled', 'hasError', 'id',
|
||||||
'label', 'options', 'optionsLoaded', 'controlProps', 'schema', 'inputRef',
|
'label', 'options', 'optionsLoaded', 'controlProps', 'schema', 'inputRef',
|
||||||
'visible', 'autoFocus', 'helpMessage', 'className', 'optionsReloadBasis',
|
'visible', 'autoFocus', 'helpMessage', 'className', 'optionsReloadBasis',
|
||||||
'orientation', 'isvalidate', 'fields', 'radioType', 'hideBrowseButton', 'btnName'
|
'orientation', 'isvalidate', 'fields', 'radioType', 'hideBrowseButton', 'btnName', 'hidden'
|
||||||
];
|
];
|
||||||
|
|
||||||
const ALLOWED_PROPS_FIELD_FORM = [
|
const ALLOWED_PROPS_FIELD_FORM = [
|
||||||
@@ -208,7 +208,7 @@ const ALLOWED_PROPS_FIELD_FORM = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const ALLOWED_PROPS_FIELD_CELL = [
|
const ALLOWED_PROPS_FIELD_CELL = [
|
||||||
'cell', 'onCellChange', 'row', 'reRenderRow', 'validate', 'disabled', 'readonly', 'radioType', 'hideBrowseButton'
|
'cell', 'onCellChange', 'row', 'reRenderRow', 'validate', 'disabled', 'readonly', 'radioType', 'hideBrowseButton', 'hidden'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ from pgadmin.tools.sqleditor.utils.query_history import QueryHistory
|
|||||||
from pgadmin.tools.sqleditor.utils.macros import get_macros,\
|
from pgadmin.tools.sqleditor.utils.macros import get_macros,\
|
||||||
get_user_macros, set_macros
|
get_user_macros, set_macros
|
||||||
from pgadmin.utils.constants import MIMETYPE_APP_JS, \
|
from pgadmin.utils.constants import MIMETYPE_APP_JS, \
|
||||||
SERVER_CONNECTION_CLOSED, ERROR_MSG_TRANS_ID_NOT_FOUND, ERROR_FETCHING_DATA
|
SERVER_CONNECTION_CLOSED, ERROR_MSG_TRANS_ID_NOT_FOUND, \
|
||||||
|
ERROR_FETCHING_DATA, MY_STORAGE
|
||||||
from pgadmin.model import Server, ServerGroup
|
from pgadmin.model import Server, ServerGroup
|
||||||
from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry
|
from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry
|
||||||
from pgadmin.settings import get_setting
|
from pgadmin.settings import get_setting
|
||||||
@@ -1808,7 +1809,9 @@ def load_file():
|
|||||||
file_path = unquote(file_data['file_name'])
|
file_path = unquote(file_data['file_name'])
|
||||||
|
|
||||||
# retrieve storage directory path
|
# retrieve storage directory path
|
||||||
storage_manager_path = get_storage_directory()
|
storage_manager_path = get_storage_directory(
|
||||||
|
shared_storage=file_data['storage'])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
Filemanager.check_access_permission(storage_manager_path, file_path)
|
Filemanager.check_access_permission(storage_manager_path, file_path)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -1850,7 +1853,13 @@ def save_file():
|
|||||||
file_data = json.loads(request.data)
|
file_data = json.loads(request.data)
|
||||||
|
|
||||||
# retrieve storage directory path
|
# retrieve storage directory path
|
||||||
storage_manager_path = get_storage_directory()
|
last_storage = Preferences.module('file_manager').preference(
|
||||||
|
'last_storage').get()
|
||||||
|
if last_storage != MY_STORAGE:
|
||||||
|
storage_manager_path = get_storage_directory(
|
||||||
|
shared_storage=last_storage)
|
||||||
|
else:
|
||||||
|
storage_manager_path = get_storage_directory()
|
||||||
|
|
||||||
# generate full path of file
|
# generate full path of file
|
||||||
file_path = unquote(file_data['file_name'])
|
file_path = unquote(file_data['file_name'])
|
||||||
|
|||||||
@@ -439,8 +439,8 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN
|
|||||||
'supported_types': ['*', 'sql'], // file types allowed
|
'supported_types': ['*', 'sql'], // file types allowed
|
||||||
'dialog_type': 'select_file', // open select file dialog
|
'dialog_type': 'select_file', // open select file dialog
|
||||||
};
|
};
|
||||||
pgAdmin.Tools.FileManager.show(fileParams, (fileName)=>{
|
pgAdmin.Tools.FileManager.show(fileParams, (fileName, storage)=>{
|
||||||
eventBus.current.fireEvent(QUERY_TOOL_EVENTS.LOAD_FILE, fileName);
|
eventBus.current.fireEvent(QUERY_TOOL_EVENTS.LOAD_FILE, fileName, storage);
|
||||||
}, null, modal);
|
}, null, modal);
|
||||||
}],
|
}],
|
||||||
[QUERY_TOOL_EVENTS.TRIGGER_SAVE_FILE, (isSaveAs=false)=>{
|
[QUERY_TOOL_EVENTS.TRIGGER_SAVE_FILE, (isSaveAs=false)=>{
|
||||||
|
|||||||
@@ -350,9 +350,10 @@ export default function Query() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
eventBus.registerListener(QUERY_TOOL_EVENTS.LOAD_FILE, (fileName)=>{
|
eventBus.registerListener(QUERY_TOOL_EVENTS.LOAD_FILE, (fileName, storage)=>{
|
||||||
queryToolCtx.api.post(url_for('sqleditor.load_file'), {
|
queryToolCtx.api.post(url_for('sqleditor.load_file'), {
|
||||||
'file_name': decodeURI(fileName),
|
'file_name': decodeURI(fileName),
|
||||||
|
'storage': storage
|
||||||
}, {transformResponse: [(data, headers) => {
|
}, {transformResponse: [(data, headers) => {
|
||||||
if(headers['content-type'].includes('application/json')) {
|
if(headers['content-type'].includes('application/json')) {
|
||||||
return JSON.parse(data);
|
return JSON.parse(data);
|
||||||
|
|||||||
@@ -120,3 +120,6 @@ DATABASE_LAST_SYSTEM_OID = 16383
|
|||||||
# Drivers
|
# Drivers
|
||||||
PSYCOPG2 = 'psycopg2'
|
PSYCOPG2 = 'psycopg2'
|
||||||
PSYCOPG3 = 'psycopg3'
|
PSYCOPG3 = 'psycopg3'
|
||||||
|
|
||||||
|
# Shared storage
|
||||||
|
MY_STORAGE = 'my_storage'
|
||||||
|
|||||||
@@ -14,21 +14,32 @@ import os
|
|||||||
from flask import current_app, url_for
|
from flask import current_app, url_for
|
||||||
from flask_security import current_user
|
from flask_security import current_user
|
||||||
from werkzeug.exceptions import InternalServerError
|
from werkzeug.exceptions import InternalServerError
|
||||||
|
from pgadmin.utils.constants import MY_STORAGE
|
||||||
|
|
||||||
|
|
||||||
def get_storage_directory(user=current_user):
|
def get_storage_directory(user=current_user, shared_storage=''):
|
||||||
import config
|
import config
|
||||||
if config.SERVER_MODE is not True:
|
if config.SERVER_MODE is not True:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
storage_dir = getattr(
|
is_shared_storage = False
|
||||||
config, 'STORAGE_DIR',
|
if shared_storage != MY_STORAGE and shared_storage:
|
||||||
os.path.join(
|
is_shared_storage = True
|
||||||
os.path.realpath(
|
selectedDir = [sdir for sdir in config.SHARED_STORAGE if
|
||||||
os.path.expanduser('~/.pgadmin/')
|
sdir['name'] == shared_storage]
|
||||||
), 'storage'
|
storage_dir = None
|
||||||
|
if len(selectedDir) > 0:
|
||||||
|
the_dir = selectedDir[0]['path']
|
||||||
|
storage_dir = the_dir
|
||||||
|
else:
|
||||||
|
storage_dir = getattr(
|
||||||
|
config, 'STORAGE_DIR',
|
||||||
|
os.path.join(
|
||||||
|
os.path.realpath(
|
||||||
|
os.path.expanduser('~/.pgadmin/')
|
||||||
|
), 'storage'
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
if storage_dir is None:
|
if storage_dir is None:
|
||||||
return None
|
return None
|
||||||
@@ -55,12 +66,19 @@ def get_storage_directory(user=current_user):
|
|||||||
|
|
||||||
username = _preprocess_username(user.username)
|
username = _preprocess_username(user.username)
|
||||||
|
|
||||||
# Figure out the new style storage directory name
|
if is_shared_storage:
|
||||||
storage_dir = os.path.join(
|
# Figure out the new style storage directory name
|
||||||
storage_dir.decode('utf-8') if hasattr(storage_dir, 'decode')
|
storage_dir = os.path.join(
|
||||||
else storage_dir,
|
storage_dir.decode('utf-8') if hasattr(storage_dir, 'decode')
|
||||||
username
|
else storage_dir
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
# Figure out the new style storage directory name
|
||||||
|
storage_dir = os.path.join(
|
||||||
|
storage_dir.decode('utf-8') if hasattr(storage_dir, 'decode')
|
||||||
|
else storage_dir,
|
||||||
|
username
|
||||||
|
)
|
||||||
|
|
||||||
# Rename an old-style storage directory, if the new style doesn't exist
|
# Rename an old-style storage directory, if the new style doesn't exist
|
||||||
if os.path.exists(old_storage_dir) and not os.path.exists(storage_dir):
|
if os.path.exists(old_storage_dir) and not os.path.exists(storage_dir):
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ class _Preference():
|
|||||||
self.options = kwargs.get('options', None)
|
self.options = kwargs.get('options', None)
|
||||||
self.select = kwargs.get('select', None)
|
self.select = kwargs.get('select', None)
|
||||||
self.fields = kwargs.get('fields', None)
|
self.fields = kwargs.get('fields', None)
|
||||||
|
self.hidden = kwargs.get('hidden', None)
|
||||||
self.allow_blanks = kwargs.get('allow_blanks', None)
|
self.allow_blanks = kwargs.get('allow_blanks', None)
|
||||||
self.disabled = kwargs.get('disabled', False)
|
self.disabled = kwargs.get('disabled', False)
|
||||||
self.dependents = kwargs.get('dependents', None)
|
self.dependents = kwargs.get('dependents', None)
|
||||||
@@ -262,6 +263,7 @@ class _Preference():
|
|||||||
'select': self.select,
|
'select': self.select,
|
||||||
'value': self.get(),
|
'value': self.get(),
|
||||||
'fields': self.fields,
|
'fields': self.fields,
|
||||||
|
'hidden': self.hidden,
|
||||||
'disabled': self.disabled,
|
'disabled': self.disabled,
|
||||||
'dependents': self.dependents
|
'dependents': self.dependents
|
||||||
}
|
}
|
||||||
@@ -436,6 +438,7 @@ class Preferences():
|
|||||||
category_label = kwargs.get('category_label', None)
|
category_label = kwargs.get('category_label', None)
|
||||||
select = kwargs.get('select', None)
|
select = kwargs.get('select', None)
|
||||||
fields = kwargs.get('fields', None)
|
fields = kwargs.get('fields', None)
|
||||||
|
hidden = kwargs.get('hidden', None)
|
||||||
allow_blanks = kwargs.get('allow_blanks', None)
|
allow_blanks = kwargs.get('allow_blanks', None)
|
||||||
disabled = kwargs.get('disabled', False)
|
disabled = kwargs.get('disabled', False)
|
||||||
dependents = kwargs.get('dependents', None)
|
dependents = kwargs.get('dependents', None)
|
||||||
@@ -457,7 +460,7 @@ class Preferences():
|
|||||||
min_val=min_val, max_val=max_val, options=options,
|
min_val=min_val, max_val=max_val, options=options,
|
||||||
select=select, fields=fields, allow_blanks=allow_blanks,
|
select=select, fields=fields, allow_blanks=allow_blanks,
|
||||||
disabled=disabled, dependents=dependents,
|
disabled=disabled, dependents=dependents,
|
||||||
control_props=control_props
|
control_props=control_props, hidden=hidden
|
||||||
)
|
)
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|||||||
Reference in New Issue
Block a user