Added support for the server side file manager, which will be useful in

selection, creation, upload/download files/directories resides on the
server side.

This will be useful for file selection/creation for different server
side utilites like pg_dump, pg_dumpall, pg_restore.
pull/3/head
Ashesh Vashi 2016-05-13 00:04:28 +05:30
parent 180630ce0e
commit 9cdd1f8098
16 changed files with 6108 additions and 1 deletions

View File

@ -27,3 +27,4 @@ backgrid-filter 01b2b21 MIT https://github.com/wyuenho/backgrid
backbone.paginator 2.0.3 MIT http://github.com/backbone-paginator/backbone.paginator
backgrid-paginator 03632df MIT https://github.com/wyuenho/backgrid-paginator
backgrid-select-all 1a00053 MIT https://github.com/wyuenho/backgrid-select-all
dropzone 4e20bd4 MIT https://github.com/enyo/dropzone

View File

@ -226,6 +226,22 @@ UPGRADE_CHECK_ENABLED = True
# Where should we get the data from?
UPGRADE_CHECK_URL = 'http://www.pgadmin.org/versions.json'
##########################################################################
# Storage Manager storage url config settings
# If user sets STORAGE_DIR to empty it will show all volumes if platform
# is Windows, '/' if it is Linux, Mac or any other unix type system.
# For example:
# 1. STORAGE_DIR = get_drive("C") or get_drive() # return C:/ by default
# where C can be any drive character such as "D", "E", "G" etc
# 2. Set path manually like
# STORAGE_DIR = "/path/to/directory/"
##########################################################################
STORAGE_DIR = os.path.join(
os.path.realpath(os.path.expanduser('~/.pgadmin/')),
'storage'
)
##########################################################################
# Local config settings
##########################################################################

View File

@ -191,6 +191,9 @@ def create_app(app_name=config.APP_NAME):
db.init_app(app)
Mail(app)
import pgadmin.utils.storage as storage
storage.init_app(app)
# Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)

View File

@ -0,0 +1,845 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""Implements File Manager"""
from pgadmin.utils import PgAdminModule
from flask.ext.babel import gettext
from flask.ext.security import login_required
from flask import render_template, Response, session, request as req, url_for
from pgadmin.utils.ajax import make_json_response
import random
import os
import os.path
import time
import simplejson as json
import string
from sys import platform as _platform
from pgadmin.utils import get_storage_directory
# Checks if platform is Windows
if _platform == "win32":
import ctypes
file_root = ""
# uppercase supported in py2, ascii_uppercase supported in py3
try:
letters = string.uppercase
except Exception:
letters = string.ascii_uppercase
# import unquote from urlib for python2.x and python3.x
try:
from urllib import unquote
except Exception as e:
from urllib.parse import unquote
MODULE_NAME = 'file_manager'
global transid
path_exists = os.path.exists
split_path = os.path.split
encode_json = json.JSONEncoder().encode
# utility functions
# convert bytes type to human readable format
def sizeof_fmt(num, suffix='B'):
for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']:
if abs(num) < 1024.0:
return "%3.1f %s%s" % (num, unit, suffix)
num /= 1024.0
return "%.1f %s%s" % (num, 'Y', suffix)
# return size of file
def getSize(path):
st = os.stat(path)
return st.st_size
def getDriveSize(path):
if _platform == "win32":
free_bytes = ctypes.c_ulonglong(0)
ctypes.windll.kernel32.GetDiskFreeSpaceExW(
ctypes.c_wchar_p(path), None, None, ctypes.pointer(free_bytes))
return free_bytes.value
# split extension for files
def splitext(path):
for ext in ['.tar.gz', '.tar.bz2']:
if path.endswith(ext):
path, ext = path[:-len(ext)], path[-len(ext):]
break
else:
path, ext = os.path.splitext(path)
return ext[1:]
# check if file is hidden in windows platform
def is_folder_hidden(filepath):
if _platform == "win32":
try:
attrs = ctypes.windll.kernel32.GetFileAttributesW(
unicode(filepath))
assert attrs != -1
result = bool(attrs & 2)
except (AttributeError, AssertionError):
result = False
return result
return False
class FileManagerModule(PgAdminModule):
"""
FileManager lists files and folders and does
following operations:
- File selection
- Folder selection
- Open file
- Create File
and also supports:
- Rename file
- Delete file
- Upload file
- Create folder
"""
LABEL = gettext("Storage")
def get_own_javascripts(self):
return [
{
'name': 'pgadmin.file_manager',
'path': url_for('file_manager.index') + 'file_manager',
'when': None
},
]
def get_own_stylesheets(self):
return [
url_for('static', filename='css/jquery.dropzone/dropzone.css'),
url_for('file_manager.static', filename='css/file_manager.css')
]
def get_own_menuitems(self):
return {
'file_items': []
}
def get_file_size_preference(self):
return self.file_upload_size
def register_preferences(self):
# Register 'file upload size' preference
self.file_upload_size = self.preference.register(
'options', 'file_upload_size',
gettext("Maximum file upload size(MB)"), 'integer', 50,
category_label=gettext('Options')
)
# Initialise the module
blueprint = FileManagerModule(MODULE_NAME, __name__)
@blueprint.route("/")
@login_required
def index():
"""Render the preferences dialog."""
return render_template(
MODULE_NAME + "/index.html", _=gettext)
@blueprint.route("/utility.js")
@login_required
def utility():
"""render the required javascript"""
return Response(response=render_template(
"file_manager/js/utility.js", _=gettext),
status=200,
mimetype="application/javascript")
@blueprint.route("/file_manager.js")
@login_required
def file_manager_js():
"""render the required javascript"""
return Response(response=render_template(
"file_manager/js/file_manager.js", _=gettext),
status=200,
mimetype="application/javascript")
@blueprint.route("/en.js")
@login_required
def language():
"""render the required javascript"""
return Response(response=render_template(
"file_manager/js/languages/en.js", _=gettext),
status=200,
mimetype="application/javascript")
@blueprint.route("/file_manager_config.js")
@login_required
def file_manager_config_js():
"""render the required javascript"""
return Response(response=render_template(
"file_manager/js/file_manager_config.js", _=gettext),
status=200,
mimetype="application/javascript")
@blueprint.route("/<int:trans_id>/file_manager_config.json")
@login_required
def file_manager_config(trans_id):
"""render the required json"""
# trans_id = Filemanager.create_new_transaction()
data = Filemanager.get_trasaction_selection(trans_id)
return Response(response=render_template(
"file_manager/js/file_manager_config.json", _=gettext,
data=data),
status=200,
mimetype="application/json")
@blueprint.route("/get_trans_id", methods=["GET", "POST"])
@login_required
def get_trans_id():
if len(req.data) != 0:
configs = json.loads(req.data)
trans_id = Filemanager.create_new_transaction(configs)
global transid
transid = trans_id
return make_json_response(
data={'fileTransId': transid, 'status': True}
)
@blueprint.route("/del_trans_id/<int:trans_id>", methods=["GET", "POST"])
@login_required
def delete_trans_id(trans_id):
Filemanager.release_transaction(trans_id)
return make_json_response(
data={'status': True}
)
def __get_drives(drive_name=None):
"""
This is a generic function which returns the default path for storage
manager dialog irrespective of any Platform type to list all
files and directories.
Platform windows:
if no path is given, it will list volumes, else list directory
Platform unix:
it returns path to root directory if no path is specified.
"""
if _platform == "win32":
try:
drives = []
bitmask = ctypes.windll.kernel32.GetLogicalDrives()
for letter in letters:
if bitmask & 1:
drives.append(letter)
bitmask >>= 1
if (drive_name != '' and drive_name is not None and
drive_name in drives):
return "{0}{1}".format(drive_name, ':/')
else:
return drives # return drives if no argument is passed
except Exception:
return 'C:/'
else:
return '/'
class Filemanager(object):
"""FileManager Class."""
def __init__(self, trans_id):
self.trans_id = trans_id
self.patherror = encode_json(
{
'Error': gettext('No permission to operate on specified path.'),
'Code': -1
}
)
self.dir = get_storage_directory()
if isinstance(self.dir, list):
self.dir = ""
@staticmethod
def create_new_transaction(params):
"""
It will also create a unique transaction id and
store the information into session variable.
Args:
capabilities: Allow/Disallow user to perform
selection, rename, delete etc.
"""
# Define configs for dialog types
# select file, select folder, create mode
fm_type = params['dialog_type']
storage_dir = get_storage_directory()
# It is used in utitlity js to decide to
# show or hide select file type options
show_volumes = True if (isinstance(storage_dir, list) or
not storage_dir) else False
supp_types = allow_upload_files = params['supported_types'] \
if 'supported_types' in params else []
if fm_type == 'select_file':
capabilities = ['select_file', 'rename', 'upload', 'create']
supp_types = supp_types
files_only = True
folders_only = False
title = "Select File"
elif fm_type == 'select_folder':
capabilities = ['select_folder', 'rename', 'create']
files_only = False
folders_only = True
title = "Select Folder"
elif fm_type == 'create_file':
capabilities = ['select_file', 'rename', 'create']
supp_types = supp_types
files_only = True
folders_only = False
title = "Create File"
elif fm_type == 'storage_dialog':
capabilities = ['select_folder', 'select_file', 'download',
'rename', 'delete', 'upload', 'create']
supp_types = supp_types
files_only = True
folders_only = False
title = "Storage Manager"
# create configs using above configs
configs = {
"fileroot": "/",
"dialog_type": fm_type,
"title": title,
"upload": {
"multiple": True
},
"capabilities": capabilities,
"security": {
"uploadPolicy": "",
"uploadRestrictions": allow_upload_files
},
"files_only": files_only,
"folders_only": folders_only,
"supported_types": supp_types,
"platform_type": _platform,
"show_volumes": show_volumes
}
# Create a unique id for the transaction
trans_id = str(random.randint(1, 9999999))
if 'fileManagerData' not in session:
file_manager_data = dict()
else:
file_manager_data = session['fileManagerData']
file_upload_size = blueprint.get_file_size_preference().get()
configs['upload']['fileSizeLimit'] = file_upload_size
file_manager_data[trans_id] = configs
session['fileManagerData'] = file_manager_data
return trans_id
@staticmethod
def get_trasaction_selection(trans_id):
"""
This method returns the information of unique transaction
id from the session variable.
Args:
trans_id: unique transaction id
"""
file_manager_data = session['fileManagerData']
# Return from the function if transaction id not found
if str(trans_id) in file_manager_data:
return file_manager_data[str(trans_id)]
@staticmethod
def release_transaction(trans_id):
"""
This method is to remove the information of unique transaction
id from the session variable.
Args:
trans_id: unique transaction id
"""
file_manager_data = session['fileManagerData']
# Return from the function if transaction id not found
if str(trans_id) not in file_manager_data:
return make_json_response(data={'status': True})
# Remove the information of unique transaction id
# from the session variable.
file_manager_data.pop(str(trans_id), None)
session['fileManagerData'] = file_manager_data
return make_json_response(data={'status': True})
@staticmethod
def list_filesystem(dir, path, trans_data, file_type):
"""
It lists all file and folders within the given
directory.
"""
files = {}
if (_platform == "win32" and path == '/') and (not dir):
drives = __get_drives()
for drive in drives:
protected = 0
path = file_name = "{0}:/".format(drive)
try:
drive_size = getDriveSize(path)
drive_size_in_units = sizeof_fmt(drive_size)
except:
drive_size = 0
protected = 1 if drive_size == 0 else 0
files[drive] = {
"Filename": file_name,
"Path": path,
"file_type": 'drive',
"Protected": protected,
"Properties": {
"Date Created": "",
"Date Modified": "",
"Size": drive_size_in_units
}
}
return files
orig_path = "{0}{1}".format(dir, path)
user_dir = path
folders_only = trans_data['folders_only'] if 'folders_only' in \
trans_data else ''
files_only = trans_data['files_only'] if 'files_only' in \
trans_data else ''
supported_types = trans_data['supported_types'] \
if 'supported_types' in trans_data else []
orig_path = unquote(orig_path)
try:
for f in sorted(os.listdir(orig_path)):
protected = 0
system_path = os.path.join(os.path.join(orig_path, f))
# continue if file/folder is hidden
if (is_folder_hidden(system_path) or f.startswith('.')):
continue
user_path = os.path.join(os.path.join(user_dir, f))
created = time.ctime(os.path.getctime(system_path))
modified = time.ctime(os.path.getmtime(system_path))
file_extension = str(splitext(system_path))
# set protected to 1 if no write or read permission
if(not os.access(system_path, os.R_OK) or
not os.access(system_path, os.W_OK)):
protected = 1
# list files only or folders only
if os.path.isdir(system_path):
if files_only == 'true':
continue
file_extension = str('dir')
user_path = "{0}/".format(user_path)
else:
# filter files based on file_type
if file_type is not None and file_type != "*":
if folders_only or len(supported_types) > 0 and \
file_extension not in supported_types or \
file_type != file_extension:
continue
# create a list of files and folders
files[user_path] = {
"Filename": f,
"Path": user_path,
"file_type": file_extension,
"Protected": protected,
"Properties": {
"Date Created": created,
"Date Modified": modified,
"Size": sizeof_fmt(getSize(system_path))
}
}
except Exception as e:
if e.strerror == gettext('Permission denied'):
err_msg = "Error: {0}".format(e.strerror)
else:
err_msg = "Error: {0}".format(e.strerror)
files = {
'Code': 0,
'err_msg': err_msg
}
return files
def validate_request(self, capability):
"""
It validates the capability with the capabilities
stored in the session
"""
trans_data = Filemanager.get_trasaction_selection(self.trans_id)
return False if capability not in trans_data['capabilities'] else True
def getinfo(self, path=None, getsize=True, name=None, req=None):
"""
Returns a JSON object containing information
about the given file.
"""
path = unquote(path)
orig_path = "{0}{1}".format(self.dir, path)
user_dir = path
thefile = {
'Filename': split_path(orig_path)[-1],
'File Type': '',
'Path': user_dir,
'Error': '',
'Code': 0,
'Properties': {
'Date Created': '',
'Date Modified': '',
'Width': '',
'Height': '',
'Size': ''
}
}
if not path_exists(orig_path):
thefile['Error'] = gettext('File does not exist.')
return (encode_json(thefile), None, 'application/json')
if split_path(user_dir)[-1] == '/':
thefile['File Type'] = 'Directory'
else:
thefile['File Type'] = splitext(user_dir)
created = time.ctime(os.path.getctime(orig_path))
modified = time.ctime(os.path.getmtime(orig_path))
thefile['Properties']['Date Created'] = created
thefile['Properties']['Date Modified'] = modified
thefile['Properties']['Size'] = sizeof_fmt(getSize(orig_path))
return thefile
def getfolder(self, path=None, file_type="", name=None, req=None):
"""
Returns files and folders in give path
"""
trans_data = Filemanager.get_trasaction_selection(self.trans_id)
dir = self.dir
filelist = self.list_filesystem(dir, path, trans_data, file_type)
return filelist
def rename(self, old=None, new=None, req=None):
"""
Rename file or folder
"""
if not self.validate_request('rename'):
return {
'Error': gettext('Not allowed'),
'Code': 1
}
dir = self.dir
# check if it's dir
if old[-1] == '/':
old = old[:-1]
# extract filename
oldname = split_path(old)[-1]
path = str(old)
path = split_path(path)[0] # extract path
if not path[-1] == '/':
path += '/'
# newname = encode_urlpath(new)
newname = new
newpath = path + newname
# make system old path
oldpath_sys = "{0}{1}".format(dir, old)
newpath_sys = "{0}{1}".format(dir, newpath)
error_msg = gettext('Renamed Successfully.')
code = 1
try:
os.rename(oldpath_sys, newpath_sys)
code = 0
except Exception as e:
error_msg = "{0} - {1}".format(
gettext('There was an error renaming the file.'),
str(e))
result = {
'Old Path': old,
'Old Name': oldname,
'New Path': newpath,
'New Name': newname,
'Error': error_msg,
'Code': code
}
return result
def delete(self, path=None, req=None):
"""
Delete file or folder
"""
if not self.validate_request('delete'):
return {
'Error': gettext('Not allowed'),
'Code': 1
}
dir = self.dir
orig_path = "{0}{1}".format(dir, path)
err_msg = ''
code = 1
try:
if os.path.isdir(orig_path):
os.rmdir(orig_path)
code = 0
else:
os.remove(orig_path)
code = 0
except Exception as e:
err_msg = "Error: {0}".format(e.strerror)
result = {
'Path': path,
'Error': err_msg,
'Code': code
}
return result
def add(self, req=None):
"""
File upload functionality
"""
if not self.validate_request('upload'):
return {
'Error': gettext('Not allowed'),
'Code': 1
}
dir = self.dir
err_msg = ''
code = 1
try:
path = req.form.get('currentpath')
orig_path = "{0}{1}".format(dir, path)
thefile = req.files['newfile']
newName = '{0}{1}'.format(orig_path, thefile.filename)
with open(newName, 'wb') as f:
f.write(thefile.read())
code = 0
except Exception as e:
err_msg = "Error: {0}".format(e.strerror)
result = {
'Path': path,
'Name': newName,
'Error': err_msg,
'Code': code
}
return result
def is_file_exist(self, path, name, req=None):
"""
Checks whether given file exists or not
"""
dir = self.dir
err_msg = ''
code = 1
try:
orig_path = "{0}{1}".format(dir, path)
newName = '{0}{1}'.format(orig_path, name)
if os.path.exists(newName):
code = 0
else:
code = 1
except Exception as e:
err_msg = "Error: {0}".format(e.strerror)
result = {
'Path': path,
'Name': newName,
'Error': err_msg,
'Code': code
}
return result
def create_file(self, path, name, req=None):
"""
Create new file functionality
"""
if not self.validate_request('create'):
return {
'Error': gettext('Not allowed'),
'Code': 1
}
dir = self.dir
err_msg = ''
code = 1
try:
orig_path = "{0}{1}".format(dir, path)
newName = '{0}{1}'.format(orig_path, name)
if not os.path.exists(newName):
open(newName, 'w')
code = 0
else:
err_msg = gettext("Error: File already exists")
except Exception as e:
err_msg = "Error: {0}".format(e.strerror)
result = {
'Path': path,
'Name': newName,
'Error': err_msg,
'Code': code
}
return result
@staticmethod
def getNewName(dir, path, newName, count=1):
"""
Utility to provide new name for folder if file
with same name already exists
"""
last_char = newName[-1]
tnewPath = dir + '/' + path + newName + '_'+str(count)
if last_char == 'r' and not path_exists(tnewPath):
return tnewPath, newName
else:
last_char = int(tnewPath[-1]) + 1
newPath = dir + '/' + path + newName + '_'+str(last_char)
if path_exists(newPath):
count += 1
return Filemanager.getNewName(dir, path, newName, count)
else:
return newPath, newName
def addfolder(self, path, name):
"""
Functionality to create new folder
"""
if not self.validate_request('create'):
return {
'Error': gettext('Not allowed'),
'Code': 1
}
dir = self.dir
newName = name
if dir != "":
newPath = dir + '/' + path + newName + '/'
else:
newPath = path + newName + '/'
err_msg = ''
code = 1
if not path_exists(newPath):
try:
os.mkdir(newPath)
code = 0
except Exception as e:
err_msg = "Error: {0}".format(e.strerror)
else:
newPath, newName = self.getNewName(dir, path, newName)
try:
os.mkdir(newPath)
code = 0
except Exception as e:
err_msg = "Error: {0}".format(e.strerror)
result = {
'Parent': path,
'Name': newName,
'Error': err_msg,
'Code': code
}
return result
def download(self, path=None, name=None, req=None):
"""
Functionality to download file
"""
if not self.validate_request('download'):
return {
'Error': gettext('Not allowed'),
'Code': 1
}
dir = self.dir
orig_path = "{0}{1}".format(dir, path)
name = path.split('/')[-1]
content = open(orig_path, 'r')
resp = Response(content)
resp.headers['Content-Disposition'] = 'attachment; filename='+name
return resp
@blueprint.route("/filemanager/<int:trans_id>/", methods=["GET", "POST"])
@login_required
def file_manager(trans_id):
"""
It is the common function for every call which is made
and takes function name from post request and calls it.
It gets unique transaction id from post request and
rotate it into Filemanager class.
"""
myFilemanager = Filemanager(trans_id)
mode = ''
kwargs = {}
if req.method == 'POST':
if req.files:
mode = 'add'
kwargs = {'req': req}
else:
kwargs = json.loads(req.data)
kwargs['req'] = req
mode = kwargs['mode']
del kwargs['mode']
elif req.method == 'GET':
kwargs = {
'path': req.args['path'],
'name': req.args['name'] if 'name' in req.args else ''
}
mode = req.args['mode']
try:
func = getattr(myFilemanager, mode)
res = func(**kwargs)
return make_json_response(data={'result': res, 'status': True})
except Exception:
return getattr(myFilemanager, mode)(**kwargs)

View File

@ -0,0 +1,660 @@
/*
* CSS for Storage Manager Dialog
*/
.file_manager #uploader {
padding: 2px 4px;
border-width: 1px;
display: block;
text-align: right;
height: auto;
min-height:30px;
max-height: 80px;
overflow: hidden;
border-bottom: 1px;
top: 35px;
}
#uploader h1 {
font-size: 14px;
margin: 0;
margin-left: 5px;
padding: 0;
display: block;
float: left;
text-align: left;
line-height:1.9em;
text-shadow:1px 1px 0px #ffffff;
}
#uploader h1 b {
font-weight: normal;
}
.uploadresponse {
display: none;
}
.fileinfo {
min-width: 100px;
overflow: auto;
/* no margin or border allowed */
}
.fileinfo #contents li.selected, .fileinfo #contents tbody tr.selected {
background: #D9EDF7;
}
.fileinfo #contents li .fm_file_rename,
.fileinfo table#contents tr td:first-child input.fm_file_rename {
display: none;
width: 80px;
margin: 0 auto;
text-align: center;
height: 17px;
}
.fileinfo table#contents tr td p {
display: inline-block;
margin-bottom: 0;
}
.fileinfo table#contents tr td p {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.fileinfo > h1 {
font-size: 16px;
margin: 100px auto;
}
#toolbar {
display: block;
clear: left;
margin: 50px auto;
}
.fm_folder {
font-size: xx-large !important;
color: rgb(255, 204, 0);
}
.fm_drive {
font-size: xx-large !important;
color: darkgray;
}
.fm_file {
font-size: xx-large !important;
}
.file_manager button {
background-color: #C0C0C0;
}
.file_manager h1 {
font-size: medium;
}
/** Input file Replacement */
.file-input-container {
margin:0;
position:relative;
top:0px;
width:215px;
height:32px;
overflow: hidden;
}
/** Firefox hack */
@-moz-document url-prefix() {
.file-input-container {
top:11px;
width:255px;
}
}
/** Opera hack */
x:-o-prefocus, .file-input-container {top:16px;width:198px;}
.newfile {
position: absolute;
top:0;
left: 3px;
right:0;
width: 152px;
height:23px;
opacity:0; filter: alpha(opacity=0);
cursor: pointer;
border:1px solid blue;
}
.alt-fileinput {
display: inline;
wrap: no-wrap;
}
.filepath {
background-color: #F4F1ED;
border: 1px solid #dcdcdc;
margin: 0;
padding: 0.1em 0.3em;
line-height: 1.7em;
-webkit-border-top-left-radius: 6px;
-webkit-border-bottom-left-radius: 6px;
-moz-border-radius-topleft: 6px;
-moz-border-radius-bottomleft: 6px;
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
}
@-moz-document url-prefix() {
.filepath {
padding:0.2em 0.3em;
}
}
/** Input file Replacement - end */
.file_listing #contents.grid {
padding: 25px;
text-align: left;
}
.file_listing #contents.grid li {
display: block;
float: left;
width: 100px;
min-height: 80px;
text-align: center;
overflow: hidden;
margin-bottom: 10px;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
}
div.clip {
width: 30px;
height: 30px;
margin: 10px auto;
overflow: hidden;
}
.file_listing #contents.grid p {
display: block;
text-align: center;
margin-bottom: 10px;
}
.file_listing #contents.list {
width: 100%;
}
.file_listing #contents.list th,
.file_listing #contents.list td {
text-align: left;
padding: 6px;
white-space: nowrap;
}
.file_listing #contents.list thead {
background: rgb(244,241,237); /* Old browsers */
background: -moz-linear-gradient(top, rgba(244,241,237,1) 0%, rgba(214,212,209,1) 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(244,241,237,1)), color-stop(100%,rgba(214,212,209,1))); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, rgba(244,241,237,1) 0%,rgba(214,212,209,1) 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, rgba(244,241,237,1) 0%,rgba(214,212,209,1) 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(top, rgba(244,241,237,1) 0%,rgba(214,212,209,1) 100%); /* IE10+ */
background: linear-gradient(to bottom, rgba(244,241,237,1) 0%,rgba(214,212,209,1) 100%); /* W3C */
border-bottom: 1px solid #ccc;
display: inline-block;
width: 100%;
}
.file_listing #contents.list th {
font-weight: bold;
cursor: pointer;
}
.file_listing #contents.list th.tablesorter-headerAsc,
.file_listing #contents.list th.tablesorter-headerDesc {
background: rgb(214,212,209); /* Old browsers */
background: -moz-linear-gradient(top, rgba(214,212,209,1) 0%, rgba(244,241,237,1) 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(214,212,209,1)), color-stop(100%,rgba(244,241,237,1))); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, rgba(214,212,209,1) 0%,rgba(244,241,237,1) 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, rgba(214,212,209,1) 0%,rgba(244,241,237,1) 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(top, rgba(214,212,209,1) 0%,rgba(244,241,237,1) 100%); /* IE10+ */
background: linear-gradient(to bottom, rgba(214,212,209,1) 0%,rgba(244,241,237,1) 100%); /* W3C */
}
.file_listing #contents.list td {
border-bottom: 1px dotted #ccc;
}
.file_listing #contents.list td:first-child {
display: table-cell;
padding-left: 0;
width: 100%;
padding-left: 22px;
background-repeat: no-repeat;
background-position: 3px center;
}
.file_listing #contents.list td.tbl_folder:first-child:before {
margin-right: 5px;
color: rgb(255, 204, 0);
}
.file_listing #contents.list td.tbl_file:first-child:before {
margin-right: 5px;
}
.file_listing #contents.list td.tbl_drive:first-child:before {
color: darkgray;
margin-right: 5px;
}
.file_listing #contents.list tbody tr:hover {
background-color: #eee;
cursor: pointer;
}
.file_listing #contents.grid li:hover {
border: 1px solid #E5E5E5;
background-color: #F7F7F7;
cursor: pointer;
max-height: 78px;
}
.meta {
display: none;
}
#activity {
margin: 100px auto;
}
button.grid span,
button.list span {
width: 20px;
background-repeat: no-repeat;
background-position: center center;
}
.file_listing #contents li {
position: relative;
}
#dropzone-container h2 {
font-size: 20px;
margin-top: 0;
}
.pgadmin-storage-body {
min-width: 750px !important;
min-height: 380px !important;
}
.pgadmin-storage-body .ajs-content {
top: 0px !important;
left: 0 !important;
right: 0 !important;
height: 100% !important;
width: 100% !important;
}
.storage_dialog {
height: 100%;
width: 100%;
}
.storage_content {
height: 100%;
width: 100%;
}
.file_manager {
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
}
.btn-group.filemanager-btn-group .btn:not(:first-child):not(:last-child) {
border-left: 1px solid #A9A9A9;
}
.file_manager #uploader .filemanager-path-group {
padding: 0;
display: block;
border: 1px solid darkgrey;
height: 30px;
border-radius: 5px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
float: left;
margin-right: 10px;
}
.file_manager #uploader .btn-group .btn[disabled] {
color: #888;
background-color: #ccc;
}
.file_manager #uploader .filemanager-btn-group {
border: 1px solid darkgrey;
border-radius: 5px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
width: auto;
float: left;
overflow: hidden;
}
.file_manager .btn-group {
margin: 2px 3px;
}
.file_manager .fileinfo {
height: calc(100% - 109px);
overflow: hidden;
position: relative;
top: 35px;
font-size: 12px;
}
.file_manager .fileinfo #contents{
padding: 5px;
text-align: left;
display: inline-block;
height: 100%;
width: 100%;
overflow: auto;
}
.file_manager .fileinfo #contents thead tr{
position: relative;
display: block;
width: 100%;
}
.file_manager .fileinfo #contents thead tr th:nth-child(1),
.file_manager .fileinfo #contents tbody tr td:nth-child(1) {
position: relative;
width: 100%;
min-width: 100%;
max-width: 100%;
}
.file_manager .fileinfo #contents thead tr th:nth-child(2) {
width: 152px;
min-width: 152px;
max-width: 152px;
}
.file_manager .fileinfo #contents tbody tr td:nth-child(2) {
width: 150px;
min-width: 150px;
max-width: 150px;
}
.file_manager .fileinfo #contents thead tr th:nth-child(3) {
width: 197px;
min-width: 197px;
max-width: 197px;
}
.file_manager .fileinfo #contents tbody tr td:nth-child(3) {
width: 180px;
min-width: 180px;
max-width: 180px;
}
.file_manager .fileinfo #contents tbody {
display: block;
overflow: auto;
width: 100%;
height: calc(100% - 30px);
}
.file_manager .fileinfo #contents tbody tr{
display: table;
max-width: 100%;
width: 100%;
}
.file_manager .upload_file {
display: none;
z-index: 1;
margin-bottom: auto;
top: 0;
margin-top: 0;
height: calc(100% - 5px);
width: 100%;
border: none;
position: absolute;
bottom: 0;
background-color: black;
padding: 0px;
padding-top: 22px;
padding-left: 10px;
}
.file_manager .upload_file #dropzone-container {
height: 100%;
}
.file_manager .upload_file #multiple-uploads {
background: black;
color: white;
padding: 0px !important;
height: calc(100% - 20px);
overflow: auto;
width: 100%;
}
.fileinfo .prompt-info {
text-align: center;
color: #fff;
}
.file_manager #uploader .btn-group .show_selected_file {
float: left;
text-align: left;
vertical-align: middle;
padding: 4px 0 0 5px;
height: 100%;
}
.fileinfo .file_listing {
display: block;
height: calc(100% - 35px);
border: 1px solid #ccc;
border-bottom: none;
font-size: 12px;
}
.fileinfo .allowed_file_types {
display: block;
height: 25px;
position: absolute;
right: 0;
border-top: 1px solid #ccc;
padding-top: 4px;
bottom: 4px;
width: 100%;
background: #fff;
}
.allowed_file_types .create_input {
position: absolute;
left: 5px;
width: 230px;
}
.allowed_file_types .create_input span {
padding-right: 5px;
}
.allowed_file_types .create_input input[type="text"] {
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
height: 22px;
width: 140px;
font-size: 13px;
display: inline;
}
.allowed_file_types .change_file_types {
position: absolute;
top: 4px;
right: 0;
}
.allowed_file_types .change_file_types select {
width: 75px;
margin-left: 10px;
float: right;
height: 22px;
}
.allowed_file_types .change_file_types label {
float: right;
padding-top: 3px;
}
.upload_file .file_upload_main {
position: relative;
height: 127px;;
width: 120px;
display: inline-block;
margin: 0 15px 15px 0 !important;
border: 1px solid white;
position: relative;
border-radius: 5px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
background: #fff;
margin: 2px;
opacity: 1;
}
.upload_file .file_upload_main .show_error {
padding: 10px 0 0 10px;
color: #000;
}
.upload_file .file_upload_main .show_error p.size {
text-align: center;
}
.upload_file .file_upload_main .show_error p.name {
font-size: 13px;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
}
.file_upload_main .dz-preview {
margin: 0;
}
.file_upload_main .dz-progress {
top: 83px !important;
border: 1px solid #8a6d3b;
border-radius: 0 !important;
-moz-border-radius: 0 !important;
-webkit-border-radius: 0 !important;
}
.file_upload_main .dz-progress .dz-upload {
background: #d9edf7 !important;
text-align: center;
}
.file_upload_main .dz-progress .dz-upload.success {
background: green !important;
}
a.dz-remove {
display: none !important;
}
.upload_file .file_upload_main a.dz_file_remove {
position: absolute;
top: 0;
right: 0;
color: #FF0000;
cursor: pointer;
border-radius: 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
font-size: large;
}
.upload_file .file_upload_main a.dz_file_remove:hover {
border: 1px solid black;
}
.fileinfo .delete_item, .fileinfo .replace_file {
display: none;
padding: 7px 5px;
opacity: 0.8;
color: #fff;
border: 1px solid darkgrey;
background: #000;
box-shadow: 1px 0px 3px 1px red;
}
.fileinfo .delete_item span.pull-right .btn,
.fileinfo .replace_file span.pull-right .btn {
padding: 0px 5px;
color: #000;
background: #fff;
font-size: 12px;
}
.fileinfo .delete_item span,
.fileinfo .replace_file span {
margin-right: 10px;
}
.upload_file .dz_cross_btn {
color: #fff;
font-size: x-large;
right: -4px;
position: absolute;
top: -1px;
background: transparent;
border: none;
}
.file_manager .fileinfo #contents .fm_lock_icon {
color: red;
position: absolute;
top: 6px;
right: 0;
left: 19px;
font-size: 16px;
}
.file_manager .fileinfo #contents .fa-lock.tbl_lock_icon {
color: red;
position: absolute;
left: 29px;
top: 5px;
font-size: 10px;
}
.fileinfo .activity {
position: absolute;
left: 50%;
font-size: xx-large;
top: 30%;
z-index: 1;
}
.file_manager button.ON {
background: #F9F8F7;
border: 1px inset #ccc;
}

View File

@ -0,0 +1,55 @@
<html>
<head>
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery.dropzone/dropzone.js') }}"></script>
<script type="text/javascript" src="{{ url_for('file_manager.index') }}utility.js"></script>
</head>
<body>
<div class="file_manager">
<form id="uploader" method="post" class='col-xs-12'>
<div class="btn-group filemanager-path-group col-sm-7 col-xs-12" role="group">
<button name="home" type="button" value="Home" title="Home" class="fa fa-home btn home"><span></span></button>
<button name="level-up" type="button" title="Back" value="LevelUp" class="btn fa fa-level-up level-up" disabled><span></span></button>
<h1 title=''></h1>
</div>
<div class="btn-group filemanager-btn-group" role="group">
<div class="uploadresponse"></div>
<input class="mode" name="mode" type="hidden" value="add" />
<input class="currentpath" name="currentpath" type="hidden" />
<button type="button" title="Refresh" class="btn fa fa-refresh refresh"></button>
<button type="submit" title="Upload File" value="Upload" class="btn fa fa-upload upload"><span></span></button>
<button type="button" title="Download File" class="btn fa fa-download download" disabled><span></span></button>
<button name="delete" type="button" title="Delete File/Folder" class="btn fa fa-trash delete" disabled><span></span></button>
<button name="rename" type="button" title="Rename File/Folder" class="btn fa fa-pencil-square-o rename"><span></span></button>
<button name="newfolder" type="button" title="Create new folder" value="New Folder" class="btn fa fa-folder-open create"><span></span></button>
<button class="ON fa fa-th btn grid" type="button" title="View as grid"><span></span></button>
<button type="button" class="btn fa fa-list list" title="View as Table"><span></span></button>
</div>
</form>
<div class="fileinfo">
<span class="activity">
<img src="{{ url_for('browser.static', filename='css/aciTree/image/load-root.gif') }}">
</span>
<div class='delete_item'>
<span>Are you sure you want to delete this item ?</span>
<span class="pull-right">
<button type='button' class='btn btn_yes'>YES</button>
<button type='button' class='btn btn_no'>NO</button>
</span>
</div>
<div class='replace_file'>
<span>Are you sure you want to replace this file ?</span>
<span class="pull-right">
<button type='button' class='btn btn_yes'>YES</button>
<button type='button' class='btn btn_no'>NO</button>
</span>
</div>
<div class="file_listing">
</div>
<div class="upload_file dropzone">
</div>
<div class="allowed_file_types">
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,591 @@
define([
'jquery', 'underscore', 'alertify'],
// This defines File Manager dialog
function($, _, alertify) {
pgAdmin = pgAdmin || window.pgAdmin || {};
/*
* Hmm... this module is already been initialized, we can refer to the old
* object from here.
*/
if (pgAdmin.FileManager)
return pgAdmin.FileManager;
pgAdmin.FileManager = {
init: function() {
if (this.initialized)
return;
this.initialized = true;
var module_url = "{{ url_for('file_manager.index') }}",
fileConnector = module_url + "filemanager/";
// send a request to get transaction id
var getTransId = function(configs) {
return $.ajax({
data: configs,
type: "POST",
async: false,
url: module_url + "get_trans_id",
dataType: "json",
contentType: "application/json; charset=utf-8",
});
};
// Function to remove trans id from session
var removeTransId = function() {
return $.ajax({
type: "GET",
async: false,
url: module_url + "del_trans_id/" + trans_id,
dataType: "json",
contentType: "application/json; charset=utf-8",
});
};
// Declare the Storage dialog
alertify.dialog('storageManagerDlg', function() {
var controls = [], // Keep tracking of all the backform controls
// Dialog containter
$container = $("<div class='storage_dialog'></div>");
/*
* Function: renderStoragePanel
*
* Renders the FileManager in the content div based on the given
* configuration parameters.
*/
var renderStoragePanel = function(params) {
/*
* Clear the existing html in the storage content
*/
var content = $container.find('.storage_content');
content.empty();
$.get("{{ url_for('file_manager.index') }}", function(data) {
content.append(data);
});
transId = getTransId(params);
if (transId.readyState == 4)
t_res = JSON.parse(transId.responseText);
trans_id = t_res.data.fileTransId;
};
// Dialog property
return {
main: function(params) {
// Set title and button name
if (_.isUndefined(params['dialog_title']))
params['dialog_title'] = 'Storage manager';
this.set('title', params['dialog_title']);
if (_.isUndefined(params['btn_primary']))
params['btn_primary'] = 'Select';
this.set('label', params['btn_primary']);
var trans_id;
this.title = params.dialog_title;
params = JSON.stringify(params);
$container.find('.storage_content').remove();
$container.append("<div class='storage_content'></div>");
renderStoragePanel(params);
this.show();
},
settings: {
label: undefined
},
settingUpdated: function (key, oldValue, newValue) {
switch (key) {
case 'message':
this.setMessage(newValue);
break;
case 'label':
if (this.__internal.buttons[0].element) {
this.__internal.buttons[0].element.innerHTML = newValue;
}
break;
}
},
setup:function() {
return {
buttons:[
{
text: "{{ _('Select') }}", key: 13, className: "btn btn-primary fa fa-file file_manager_ok pg-alertify-button disabled"
},
{
text: "{{ _('Cancel') }}", className: "btn btn-danger fa fa-times pg-alertify-button"
}
],
focus: { element: 0 },
options: {
closableByDimmer: false,
}
};
},
callback: function(closeEvent) {
if (closeEvent.button.key == 13) {
//closeEvent.cancel = true;
}
if (closeEvent.button.text == "{{ _('Select') }}") {
if($('.fileinfo').data('view') == 'grid'){
sel_file = $('.fileinfo').find('#contents li.selected p span').attr('title');
} else {
sel_file = $('.fileinfo tbody tr.selected td p span').attr('title');
}
var newFile = $('.currentpath').val() + sel_file;
newFile = newFile.substr(1);
pgAdmin.Browser.Events.trigger('pgadmin-storage:finish_btn:storage_dialog', newFile);
}
removeTransId(trans_id);
},
build: function() {
this.elements.content.appendChild($container.get(0));
},
hooks: {
onshow: function() {
$(this.elements.body).addClass('pgadmin-storage-body');
}
}
};
});
// Declare the Selection dialog
alertify.dialog('fileSelectionDlg', function() {
var controls = [], // Keep tracking of all the backform controls
// Dialog containter
$container = $("<div class='storage_dialog file_selection_dlg'></div>");
// send a request to get transaction id
/*
* Function: renderStoragePanel
*
* Renders the FileManager in the content div based on the given
* configuration parameters.
*/
var renderStoragePanel = function(configs) {
/*
* Clear the existing html in the storage content
*/
var content = $container.find('.storage_content');
content.empty();
$.get("{{ url_for('file_manager.index') }}", function(data) {
content.append(data);
});
transId = getTransId(configs);
if (transId.readyState == 4)
t_res = JSON.parse(transId.responseText);
trans_id = t_res.data.fileTransId;
};
// Dialog property
return {
main: function(params) {
// Set title and button name
if (_.isUndefined(params['dialog_title']))
params['dialog_title'] = 'Select file';
this.set('title', params['dialog_title']);
if (_.isUndefined(params['btn_primary']))
params['btn_primary'] = 'Select';
this.set('label', params['btn_primary']);
var trans_id;
this.title = params.dialog_title;
params = JSON.stringify(params);
$container.find('.storage_content').remove();
$container.append("<div class='storage_content'></div>");
renderStoragePanel(params);
this.show();
},
settings: {
label: undefined
},
settingUpdated: function (key, oldValue, newValue) {
switch (key) {
case 'message':
this.setMessage(newValue);
break;
case 'label':
if (this.__internal.buttons[0].element) {
this.__internal.buttons[0].element.innerHTML = newValue;
}
break;
}
},
setup:function() {
return {
buttons:[
{
text: "{{ _('Select') }}", key: 13, className: "btn btn-primary fa fa-file file_manager_ok pg-alertify-button disabled"
},
{
text: "{{ _('Cancel') }}", key: 27, className: "btn btn-danger fa fa-times pg-alertify-button"
}
],
focus: { element: 0 },
options: {
closableByDimmer: false,
maximizable: false,
closable: false,
movable: true
}
};
},
callback: function(closeEvent) {
if (closeEvent.button.text == "{{ _('Select') }}") {
if($('.fileinfo').data('view') == 'grid'){
sel_file = $('.fileinfo').find('#contents li.selected p span').attr('title');
} else {
sel_file = $('.fileinfo tbody tr.selected td p span').attr('title');
}
var newFile = $('.currentpath').val() + sel_file;
newFile = newFile.substr(1);
pgAdmin.Browser.Events.trigger('pgadmin-storage:finish_btn:select_file', newFile);
}
removeTransId(trans_id);
},
build: function() {
this.elements.content.appendChild($container.get(0));
},
hooks: {
onshow: function() {
$(this.elements.body).addClass('pgadmin-storage-body');
}
}
};
});
// Declare the Folder Selection dialog
alertify.dialog('folderSelectionDlg', function() {
var controls = [], // Keep tracking of all the backform controls
// Dialog containter
$container = $("<div class='storage_dialog folder_selection_dlg'></div>");
// send a request to get transaction id
/*
* Function: renderStoragePanel
*
* Renders the FileManager in the content div based on the given
* configuration parameters.
*/
var renderStoragePanel = function(params) {
/*
* Clear the existing html in the storage content
*/
var content = $container.find('.storage_content');
content.empty();
$.get("{{ url_for('file_manager.index') }}", function(data) {
content.append(data);
});
transId = getTransId(params);
if (transId.readyState == 4)
t_res = JSON.parse(transId.responseText);
trans_id = t_res.data.fileTransId;
};
// Dialog property
return {
main: function(params) {
// Set title and button name
if (_.isUndefined(params['dialog_title']))
params['dialog_title'] = 'Select folder';
this.set('title', params['dialog_title']);
if (_.isUndefined(params['btn_primary']))
params['btn_primary'] = 'Select';
this.set('label', params['btn_primary']);
var trans_id;
params = JSON.stringify(params);
$container.find('.storage_content').remove();
$container.append("<div class='storage_content'></div>");
renderStoragePanel(params);
this.show();
},
settings: {
label: undefined
},
settingUpdated: function (key, oldValue, newValue) {
switch (key) {
case 'message':
this.setMessage(newValue);
break;
case 'label':
if (this.__internal.buttons[0].element) {
this.__internal.buttons[0].element.innerHTML = newValue;
}
break;
}
},
setup:function() {
return {
buttons:[
{
text: "{{ _('Select') }}", key: 13, className: "btn btn-primary fa fa-file file_manager_ok pg-alertify-button disabled"
},
{
text: "{{ _('Cancel') }}", key: 27, className: "btn btn-danger fa fa-times pg-alertify-button"
}
],
focus: { element: 0 },
options: {
closableByDimmer: false,
maximizable: false,
closable: false,
movable: true
}
};
},
callback: function(closeEvent) {
if (closeEvent.button.text == "{{ _('Select') }}") {
if($('.fileinfo').data('view') == 'grid'){
sel_file = $('.fileinfo').find('#contents li.selected p span').attr('title');
} else {
sel_file = $('.fileinfo tbody tr.selected td p span').attr('title');
}
var newFile = $('.currentpath').val() + sel_file;
newFile = newFile.substr(1);
pgAdmin.Browser.Events.trigger('pgadmin-storage:finish_btn:select_folder', newFile);
}
removeTransId(trans_id);
},
build: function() {
this.elements.content.appendChild($container.get(0));
},
hooks: {
onshow: function() {
$(this.elements.body).addClass('pgadmin-storage-body');
}
}
};
});
// Declare the Create mode dialog
alertify.dialog('createModeDlg', function() {
var controls = [], // Keep tracking of all the backform controls
// Dialog containter
$container = $("<div class='storage_dialog create_mode_dlg'></div>");
/*
* Function: renderStoragePanel
*
* Renders the FileManager in the content div based on the given
* configuration parameters.
*/
var renderStoragePanel = function(params) {
/*
* Clear the existing html in the storage content
*/
var content = $container.find('.storage_content');
content.empty();
$.get("{{ url_for('file_manager.index') }}", function(data) {
content.append(data);
});
transId = getTransId(params);
if (transId.readyState == 4)
t_res = JSON.parse(transId.responseText);
trans_id = t_res.data.fileTransId;
};
// Dialog property
return {
main: function(params) {
var trans_id;
// Set title and button name
if (_.isUndefined(params['dialog_title']))
params['dialog_title'] = 'Create file';
this.set('title', params['dialog_title']);
if (_.isUndefined(params['btn_primary']))
params['btn_primary'] = 'Create';
this.set('label', params['btn_primary']);
params = JSON.stringify(params);
$container.find('.storage_content').remove();
$container.append("<div class='storage_content'></div>");
renderStoragePanel(params);
this.show();
},
settings: {
label: undefined
},
settingUpdated: function (key, oldValue, newValue) {
switch (key) {
case 'message':
this.setMessage(newValue);
break;
case 'label':
if (this.__internal.buttons[0].element) {
this.__internal.buttons[0].element.innerHTML = newValue;
}
break;
}
},
setup:function() {
return {
buttons:[
{
text: "{{ _('Create') }}", key: 13, className: "btn btn-primary fa fa-file file_manager_create file_manager_ok pg-alertify-button disabled"
},
{
text: "{{ _('Cancel') }}", key: 27, className: "btn btn-danger fa fa-times file_manager_create_cancel pg-alertify-button"
}
],
focus: { element: 0 },
options: {
closableByDimmer: false,
maximizable: false,
closable: false,
movable: true
}
};
},
replace_file: function() {
$('.replace_file').show();
$('.replace_file .btn_yes').click(function(self) {
$('.replace_file').hide();
var selected_item = $('.allowed_file_types .create_input input[type="text"]').val(),
newFile = $('.currentpath').val() + selected_item,
newFile = newFile.substr(1);
pgAdmin.Browser.Events.trigger('pgadmin-storage:finish_btn:create_file', newFile);
$('.file_manager_create_cancel').trigger('click');
});
$('.replace_file .btn_no').click(function() {
$('.replace_file').hide();
});
},
is_file_exist: function() {
var selected_item = $('.allowed_file_types .create_input input[type="text"]').val(),
is_exist = false;
var file_data = {
'path': $('.currentpath').val(),
'name': selected_item,
'mode': 'is_file_exist'
};
$.ajax({
type: 'POST',
data: JSON.stringify(file_data),
url: fileConnector + trans_id+'/',
dataType: 'json',
contentType: "application/x-download; charset=utf-8",
async: false,
success: function(resp){
data = resp.data.result;
if(data['Code'] === 0){
is_exist = true;
} else {
is_exist = false;
}
}
});
return is_exist;
},
create_file: function() {
var selected_item = $('.allowed_file_types .create_input input[type="text"]').val(),
is_exist = false,
is_created = false;
var post_data = {
'path': $('.currentpath').val(),
'name': selected_item,
'mode': 'create_file'
};
$.ajax({
type: 'POST',
data: JSON.stringify(post_data),
url: fileConnector + trans_id+'/',
dataType: 'json',
contentType: "application/x-download; charset=utf-8",
async: false,
success: function(resp){
data = resp.data.result;
if(data['Code'] === 0){
alertify.success("New File created successfully.");
is_created = true;
} else {
alertify.error(data['Error']);
return false;
}
}
});
return is_created;
},
callback: function(closeEvent) {
if (closeEvent.button.text == "{{ _('Create') }}"){
var selected_item = $('.allowed_file_types .create_input input[type="text"]').val();
var newFile = $('.currentpath').val() + selected_item;
newFile = newFile.substr(1);
if(!_.isUndefined(selected_item) && selected_item !== '' && this.is_file_exist()) {
this.replace_file();
closeEvent.cancel = true;
}
else {
var is_created = this.create_file();
if (is_created) {
pgAdmin.Browser.Events.trigger('pgadmin-storage:finish_btn:create_file', newFile);
removeTransId(trans_id);
}
else {
closeEvent.cancel = true;
}
}
}
if (closeEvent.button.text == "{{ _('Cancel') }}"){
removeTransId(trans_id);
}
},
build: function() {
this.elements.content.appendChild($container.get(0));
},
hooks: {
onshow: function() {
$(this.elements.body).addClass('pgadmin-storage-body');
}
}
};
});
},
show_storage_dlg: function(params) {
alertify.storageManagerDlg(params).resizeTo('60%', '80%');
},
show_file_selection: function(params) {
alertify.fileSelectionDlg(params).resizeTo('60%', '80%');
},
show_folder_selection: function(params) {
alertify.folderSelectionDlg(params).resizeTo('60%', '80%');
},
show_create_dlg: function(params) {
alertify.createModeDlg(params).resizeTo('60%', '80%');
},
// call dialogs subject to dialog_type param
show_dialog: function(params) {
if(params.dialog_type == 'select_file') {
this.show_file_selection(params);
}
else if (params.dialog_type == 'select_folder') {
this.show_folder_selection(params);
}
else if (params.dialog_type == 'create_file') {
this.show_create_dlg(params);
}
else {
this.show_storage_dlg(params);
}
}
};
return pgAdmin.FileManager;
});

View File

@ -0,0 +1,25 @@
{
"options": {
"culture": "en",
"lang": "py",
"defaultViewMode": "grid",
"autoload": true,
"showFullPath": false,
"dialog_type": "{{data.dialog_type}}",
"fileRoot": "{{data.fileroot}}",
"capabilities": [{% for i in data.capabilities %}{% if loop.index != 1 %}, {% endif %}"{{i}}"{% endfor %}],
"allowed_file_types": [{% for i in data.supported_types %}{% if loop.index != 1 %}, {% endif %}"{{i}}"{% endfor %}],
"platform_type": "{{ data.platform_type }}",
"show_volumes":"{{data.show_volumes}}"
},
"security": {
"uploadPolicy": "{{ data.security.uploadPolicy }}",
"uploadRestrictions": [{% for i in data.security.uploadRestrictions %}{% if loop.index != 1 %}, {% endif %}"{{i}}"{% endfor %}]
},
"upload": {
"multiple": "{{ data.upload.multiple }}",
"number": 20,
"fileSizeLimit": "{{ data.upload.fileSizeLimit }}",
"imagesOnly": false
}
}

View File

@ -0,0 +1,41 @@
{
"LANGUAGE_FILE_NOT_FOUND": "Language file not found.",
"upload_success": "File uploaded successfully.",
"upload_error": "Error uploading file",
"browse": "Browse...",
"bytes": " bytes",
"cancel": "Cancel",
"close": "Close",
"confirmation_delete": "Are you sure you wish to delete this file?",
"current_folder": "",
"del": "Delete",
"download": "Download",
"dz_dictDefaultMessage": "Drop files here to upload",
"dz_dictFallbackMessage": "Your browser does not support drag'n'drop file uploads.",
"dz_dictMaxFilesExceeded": "Only %s simultaneous uploads are allowed.",
"dz_dictInvalidFileType": "You can't upload files of this type.",
"edit": "Edit file",
"file_size_limit": "The file size limit (per file) is ",
"file_too_big": "The file is too big.",
"gb": "gb",
"grid_view": "Switch to grid view.",
"items": "items",
"kb": "kb",
"list_view": "Switch to list view.",
"mb": "mb",
"modified": "Modified",
"move": "Move to ...",
"name": "Name",
"new_folder": "New Folder",
"no": "No",
"no_foldername": "No folder name was provided.",
"rename": "Rename",
"save": "Save",
"select": "Select",
"size": "Size",
"successful_added_folder": "New folder added successfully.",
"successful_delete": "Delete successful.",
"successful_rename": "Rename successful.",
"upload": "Upload",
"yes": "Yes"
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,388 @@
/*
* The MIT License
* Copyright (c) 2012 Matias Meno <m@tias.me>
*/
@-webkit-keyframes passing-through {
0% {
opacity: 0;
-webkit-transform: translateY(40px);
-moz-transform: translateY(40px);
-ms-transform: translateY(40px);
-o-transform: translateY(40px);
transform: translateY(40px); }
30%, 70% {
opacity: 1;
-webkit-transform: translateY(0px);
-moz-transform: translateY(0px);
-ms-transform: translateY(0px);
-o-transform: translateY(0px);
transform: translateY(0px); }
100% {
opacity: 0;
-webkit-transform: translateY(-40px);
-moz-transform: translateY(-40px);
-ms-transform: translateY(-40px);
-o-transform: translateY(-40px);
transform: translateY(-40px); } }
@-moz-keyframes passing-through {
0% {
opacity: 0;
-webkit-transform: translateY(40px);
-moz-transform: translateY(40px);
-ms-transform: translateY(40px);
-o-transform: translateY(40px);
transform: translateY(40px); }
30%, 70% {
opacity: 1;
-webkit-transform: translateY(0px);
-moz-transform: translateY(0px);
-ms-transform: translateY(0px);
-o-transform: translateY(0px);
transform: translateY(0px); }
100% {
opacity: 0;
-webkit-transform: translateY(-40px);
-moz-transform: translateY(-40px);
-ms-transform: translateY(-40px);
-o-transform: translateY(-40px);
transform: translateY(-40px); } }
@keyframes passing-through {
0% {
opacity: 0;
-webkit-transform: translateY(40px);
-moz-transform: translateY(40px);
-ms-transform: translateY(40px);
-o-transform: translateY(40px);
transform: translateY(40px); }
30%, 70% {
opacity: 1;
-webkit-transform: translateY(0px);
-moz-transform: translateY(0px);
-ms-transform: translateY(0px);
-o-transform: translateY(0px);
transform: translateY(0px); }
100% {
opacity: 0;
-webkit-transform: translateY(-40px);
-moz-transform: translateY(-40px);
-ms-transform: translateY(-40px);
-o-transform: translateY(-40px);
transform: translateY(-40px); } }
@-webkit-keyframes slide-in {
0% {
opacity: 0;
-webkit-transform: translateY(40px);
-moz-transform: translateY(40px);
-ms-transform: translateY(40px);
-o-transform: translateY(40px);
transform: translateY(40px); }
30% {
opacity: 1;
-webkit-transform: translateY(0px);
-moz-transform: translateY(0px);
-ms-transform: translateY(0px);
-o-transform: translateY(0px);
transform: translateY(0px); } }
@-moz-keyframes slide-in {
0% {
opacity: 0;
-webkit-transform: translateY(40px);
-moz-transform: translateY(40px);
-ms-transform: translateY(40px);
-o-transform: translateY(40px);
transform: translateY(40px); }
30% {
opacity: 1;
-webkit-transform: translateY(0px);
-moz-transform: translateY(0px);
-ms-transform: translateY(0px);
-o-transform: translateY(0px);
transform: translateY(0px); } }
@keyframes slide-in {
0% {
opacity: 0;
-webkit-transform: translateY(40px);
-moz-transform: translateY(40px);
-ms-transform: translateY(40px);
-o-transform: translateY(40px);
transform: translateY(40px); }
30% {
opacity: 1;
-webkit-transform: translateY(0px);
-moz-transform: translateY(0px);
-ms-transform: translateY(0px);
-o-transform: translateY(0px);
transform: translateY(0px); } }
@-webkit-keyframes pulse {
0% {
-webkit-transform: scale(1);
-moz-transform: scale(1);
-ms-transform: scale(1);
-o-transform: scale(1);
transform: scale(1); }
10% {
-webkit-transform: scale(1.1);
-moz-transform: scale(1.1);
-ms-transform: scale(1.1);
-o-transform: scale(1.1);
transform: scale(1.1); }
20% {
-webkit-transform: scale(1);
-moz-transform: scale(1);
-ms-transform: scale(1);
-o-transform: scale(1);
transform: scale(1); } }
@-moz-keyframes pulse {
0% {
-webkit-transform: scale(1);
-moz-transform: scale(1);
-ms-transform: scale(1);
-o-transform: scale(1);
transform: scale(1); }
10% {
-webkit-transform: scale(1.1);
-moz-transform: scale(1.1);
-ms-transform: scale(1.1);
-o-transform: scale(1.1);
transform: scale(1.1); }
20% {
-webkit-transform: scale(1);
-moz-transform: scale(1);
-ms-transform: scale(1);
-o-transform: scale(1);
transform: scale(1); } }
@keyframes pulse {
0% {
-webkit-transform: scale(1);
-moz-transform: scale(1);
-ms-transform: scale(1);
-o-transform: scale(1);
transform: scale(1); }
10% {
-webkit-transform: scale(1.1);
-moz-transform: scale(1.1);
-ms-transform: scale(1.1);
-o-transform: scale(1.1);
transform: scale(1.1); }
20% {
-webkit-transform: scale(1);
-moz-transform: scale(1);
-ms-transform: scale(1);
-o-transform: scale(1);
transform: scale(1); } }
.dropzone, .dropzone * {
box-sizing: border-box; }
.dropzone {
min-height: 150px;
border: 2px solid rgba(0, 0, 0, 0.3);
background: white;
padding: 20px 20px; }
.dropzone.dz-clickable {
cursor: pointer; }
.dropzone.dz-clickable * {
cursor: default; }
.dropzone.dz-clickable .dz-message, .dropzone.dz-clickable .dz-message * {
cursor: pointer; }
.dropzone.dz-started .dz-message {
display: none; }
.dropzone.dz-drag-hover {
border-style: solid; }
.dropzone.dz-drag-hover .dz-message {
opacity: 0.5; }
.dropzone .dz-message {
text-align: center;
margin: 2em 0; }
.dropzone .dz-preview {
position: relative;
display: inline-block;
vertical-align: top;
margin: 16px;
min-height: 100px; }
.dropzone .dz-preview:hover {
z-index: 1000; }
.dropzone .dz-preview:hover .dz-details {
opacity: 1; }
.dropzone .dz-preview.dz-file-preview .dz-image {
border-radius: 20px;
background: #999;
background: linear-gradient(to bottom, #eee, #ddd); }
.dropzone .dz-preview.dz-file-preview .dz-details {
opacity: 1; }
.dropzone .dz-preview.dz-image-preview {
background: white; }
.dropzone .dz-preview.dz-image-preview .dz-details {
-webkit-transition: opacity 0.2s linear;
-moz-transition: opacity 0.2s linear;
-ms-transition: opacity 0.2s linear;
-o-transition: opacity 0.2s linear;
transition: opacity 0.2s linear; }
.dropzone .dz-preview .dz-remove {
font-size: 14px;
text-align: center;
display: block;
cursor: pointer;
border: none; }
.dropzone .dz-preview .dz-remove:hover {
text-decoration: underline; }
.dropzone .dz-preview:hover .dz-details {
opacity: 1; }
.dropzone .dz-preview .dz-details {
z-index: 20;
position: absolute;
top: 0;
left: 0;
opacity: 0;
font-size: 13px;
min-width: 100%;
max-width: 100%;
padding: 2em 1em;
text-align: center;
color: rgba(0, 0, 0, 0.9);
line-height: 150%; }
.dropzone .dz-preview .dz-details .dz-size {
margin-bottom: 1em;
font-size: 16px; }
.dropzone .dz-preview .dz-details .dz-filename {
white-space: nowrap; }
.dropzone .dz-preview .dz-details .dz-filename:hover span {
border: 1px solid rgba(200, 200, 200, 0.8);
background-color: rgba(255, 255, 255, 0.8); }
.dropzone .dz-preview .dz-details .dz-filename:not(:hover) {
overflow: hidden;
text-overflow: ellipsis; }
.dropzone .dz-preview .dz-details .dz-filename:not(:hover) span {
border: 1px solid transparent; }
.dropzone .dz-preview .dz-details .dz-filename span, .dropzone .dz-preview .dz-details .dz-size span {
background-color: rgba(255, 255, 255, 0.4);
padding: 0 0.4em;
border-radius: 3px; }
.dropzone .dz-preview:hover .dz-image img {
-webkit-transform: scale(1.05, 1.05);
-moz-transform: scale(1.05, 1.05);
-ms-transform: scale(1.05, 1.05);
-o-transform: scale(1.05, 1.05);
transform: scale(1.05, 1.05);
-webkit-filter: blur(8px);
filter: blur(8px); }
.dropzone .dz-preview .dz-image {
border-radius: 20px;
overflow: hidden;
width: 120px;
height: 120px;
position: relative;
display: block;
z-index: 10; }
.dropzone .dz-preview .dz-image img {
display: block; }
.dropzone .dz-preview.dz-success .dz-success-mark {
-webkit-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);
-moz-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);
-ms-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);
-o-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);
animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); }
.dropzone .dz-preview.dz-error .dz-error-mark {
opacity: 1;
-webkit-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);
-moz-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);
-ms-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);
-o-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);
animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); }
.dropzone .dz-preview .dz-success-mark, .dropzone .dz-preview .dz-error-mark {
pointer-events: none;
opacity: 0;
z-index: 500;
position: absolute;
display: block;
top: 50%;
left: 50%;
margin-left: -27px;
margin-top: -27px; }
.dropzone .dz-preview .dz-success-mark svg, .dropzone .dz-preview .dz-error-mark svg {
display: block;
width: 54px;
height: 54px; }
.dropzone .dz-preview.dz-processing .dz-progress {
opacity: 1;
-webkit-transition: all 0.2s linear;
-moz-transition: all 0.2s linear;
-ms-transition: all 0.2s linear;
-o-transition: all 0.2s linear;
transition: all 0.2s linear; }
.dropzone .dz-preview.dz-complete .dz-progress {
opacity: 0;
-webkit-transition: opacity 0.4s ease-in;
-moz-transition: opacity 0.4s ease-in;
-ms-transition: opacity 0.4s ease-in;
-o-transition: opacity 0.4s ease-in;
transition: opacity 0.4s ease-in; }
.dropzone .dz-preview:not(.dz-processing) .dz-progress {
-webkit-animation: pulse 6s ease infinite;
-moz-animation: pulse 6s ease infinite;
-ms-animation: pulse 6s ease infinite;
-o-animation: pulse 6s ease infinite;
animation: pulse 6s ease infinite; }
.dropzone .dz-preview .dz-progress {
opacity: 1;
z-index: 1000;
pointer-events: none;
position: absolute;
height: 16px;
left: 50%;
top: 50%;
margin-top: -8px;
width: 80px;
margin-left: -40px;
background: rgba(255, 255, 255, 0.9);
-webkit-transform: scale(1);
border-radius: 8px;
overflow: hidden; }
.dropzone .dz-preview .dz-progress .dz-upload {
background: #333;
background: linear-gradient(to bottom, #666, #444);
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 0;
-webkit-transition: width 300ms ease-in-out;
-moz-transition: width 300ms ease-in-out;
-ms-transition: width 300ms ease-in-out;
-o-transition: width 300ms ease-in-out;
transition: width 300ms ease-in-out; }
.dropzone .dz-preview.dz-error .dz-error-message {
display: block; }
.dropzone .dz-preview.dz-error:hover .dz-error-message {
opacity: 1;
pointer-events: auto; }
.dropzone .dz-preview .dz-error-message {
pointer-events: none;
z-index: 1000;
position: absolute;
display: block;
display: none;
opacity: 0;
-webkit-transition: opacity 0.3s ease;
-moz-transition: opacity 0.3s ease;
-ms-transition: opacity 0.3s ease;
-o-transition: opacity 0.3s ease;
transition: opacity 0.3s ease;
border-radius: 8px;
font-size: 13px;
top: 130px;
left: -10px;
width: 140px;
background: #be2626;
background: linear-gradient(to bottom, #be2626, #a92222);
padding: 0.5em 1.2em;
color: white; }
.dropzone .dz-preview .dz-error-message:after {
content: '';
position: absolute;
top: -6px;
left: 64px;
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 6px solid #be2626; }

View File

@ -1104,4 +1104,55 @@ div.backform_control_notes label.control-label {
form[name="change_password_form"] .help-block {
color: #A94442 !important;
}
}
.file_selection_ctrl .create_input span {
padding-right: 10px;
font-weight: bold;
}
.file_selection_ctrl .create_input input[type="text"] {
height: 23px;
padding: 2px;
width: 194px;
border-radius: 2px;
}
.file_selection_ctrl .browse_file_input {
display: inline-block;
width: 220px;
margin-right: 0;
}
.file_selection_ctrl button.select_item {
display: inline;
background: #777;
background: -webkit-linear-gradient(#777, #999999);
background: -o-linear-gradient(#777, #999);
background: -moz-linear-gradient(#777, #999);
background: linear-gradient(#777, #999);
color: #fff;
padding: 9px 0px 9px 0px;
margin-left: 0px;
margin-right: -7px;
margin-top: -4px;
min-width: 30px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.file_selection_ctrl button.select_item:focus,
.file_selection_ctrl button.select_item:active {
outline: none;
box-shadow: none;
}
.file_selection_ctrl input[type="text"] {
width: calc(100% - 17px);
border: none;
margin-left: -6px;
margin-top: -3px;
height: 32px;
padding-left: 5px;
}

View File

@ -2041,5 +2041,71 @@
].join("\n"))
});
/*
* Input File Control: This control is used with Storage Manager Dialog,
* It allows user to perform following operations:
* - Select File
* - Select Folder
* - Create File
* - Opening Storage Manager Dialog itself.
*/
var FileControl = Backform.FileControl = Backform.InputControl.extend({
defaults: {
type: "text",
label: "",
min: undefined,
max: undefined,
maxlength: 255,
extraClasses: [],
dialog_title: '',
btn_primary: '',
helpMessage: null,
dialog_type: 'select_file'
},
initialize: function(){
Backform.InputControl.prototype.initialize.apply(this, arguments);
// Listen click events of Storage Manager dialog buttons
pgAdmin.Browser.Events.on('pgadmin-storage:finish_btn:'+this.field.get('dialog_type'), this.storage_dlg_hander, this);
},
template: _.template([
'<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
'<div class="<%=Backform.controlsClassName%>">',
'<div class="file_selection_ctrl form-control">',
'<input type="<%=type%>" class="browse_file_input form-control <%=extraClasses.join(\' \')%>" name="<%=name%>" min="<%=min%>" max="<%=max%>"maxlength="<%=maxlength%>" value="<%-value%>" placeholder="<%-placeholder%>" <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%> />',
'<button class="btn fa fa-ellipsis-h select_item pull-right" <%=disabled ? "disabled" : ""%> ></button>',
'<% if (helpMessage && helpMessage.length) { %>',
'<span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
'<% } %>',
'</div>',
'</div>'
].join("\n")),
events: {
"click .select_item": "onSelect",
},
onSelect: function(e) {
var dialog_type = this.field.get('dialog_type');
supp_types = this.field.get('supp_types'),
btn_primary = this.field.get('btn_primary'),
dialog_title = this.field.get('dialog_title');
var params = {
supported_types: supp_types,
dialog_type: dialog_type,
dialog_title: dialog_title,
btn_primary: btn_primary
};
pgAdmin.FileManager.init();
pgAdmin.FileManager.show_dialog(params);
},
storage_dlg_hander: function(value) {
var field = _.defaults(this.field.toJSON(), this.defaults),
attrArr = this.field.get("name").split('.'),
name = attrArr.shift();
// Set selected value into the model
this.model.set(name, decodeURI(value));
}
});
return Backform;
}));

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,7 @@ from flask import Blueprint
from collections import defaultdict
from operator import attrgetter
from .preferences import Preferences
from .paths import get_storage_directory
class PgAdminModule(Blueprint):

View File

@ -0,0 +1,68 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
#########################################################################
"""This file contains functions fetching different utility paths."""
import os
from flask.ext.security import current_user, login_required
import config
@login_required
def get_storage_directory():
if config.SERVER_MODE is not True:
return None
storage_dir = getattr(
config, 'STORAGE_DIR',
os.path.join(
os.path.realpath(
os.path.expanduser('~/.pgadmin/')
), 'storage'
)
)
username = current_user.email.split('@')[0]
if len(username) == 0 or username[0].isdigit():
username = 'pga_user_' + username
storage_dir = os.path.join(storage_dir, username)
print(storage_dir)
if not os.path.exists(storage_dir):
os.makedirs(storage_dir, int('700', 8))
return storage_dir
def init_app(app):
if config.SERVER_MODE is not True:
return None
storage_dir = getattr(
config, 'STORAGE_DIR',
os.path.join(
os.path.realpath(
os.path.expanduser('~/.pgadmin/')
), 'storage'
)
)
if not os.path.isdir(storage_dir):
if os.path.exists(storage_dir):
raise Exception(
'The value specified for as the storage directory is not a directory!'
)
os.makedirs(storage_dir, int('700', 8))
if not os.access(storage_dir, os.W_OK | os.R_OK):
raise Exception(
'The user does not have permission to read, write on the specified storage directory!'
)