diff --git a/libraries.txt b/libraries.txt index 9fcf755cc..0f65f43e0 100644 --- a/libraries.txt +++ b/libraries.txt @@ -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 diff --git a/web/config.py b/web/config.py index bc67ead89..b11b7c6aa 100644 --- a/web/config.py +++ b/web/config.py @@ -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 ########################################################################## diff --git a/web/pgadmin/__init__.py b/web/pgadmin/__init__.py index b37d33db7..505ee4540 100644 --- a/web/pgadmin/__init__.py +++ b/web/pgadmin/__init__.py @@ -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) diff --git a/web/pgadmin/misc/file_manager/__init__.py b/web/pgadmin/misc/file_manager/__init__.py new file mode 100644 index 000000000..e6c4e004f --- /dev/null +++ b/web/pgadmin/misc/file_manager/__init__.py @@ -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("//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/", 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//", 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) diff --git a/web/pgadmin/misc/file_manager/static/css/file_manager.css b/web/pgadmin/misc/file_manager/static/css/file_manager.css new file mode 100755 index 000000000..cebed17c4 --- /dev/null +++ b/web/pgadmin/misc/file_manager/static/css/file_manager.css @@ -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; +} diff --git a/web/pgadmin/misc/file_manager/templates/file_manager/index.html b/web/pgadmin/misc/file_manager/templates/file_manager/index.html new file mode 100755 index 000000000..51044da4a --- /dev/null +++ b/web/pgadmin/misc/file_manager/templates/file_manager/index.html @@ -0,0 +1,55 @@ + + + + + + +
+
+
+ + +

+
+
+
+ + + + + + + + + + +
+
+
+ + + +
+ Are you sure you want to delete this item ? + + + + +
+
+ Are you sure you want to replace this file ? + + + + +
+
+
+
+
+
+
+
+
+ + diff --git a/web/pgadmin/misc/file_manager/templates/file_manager/js/file_manager.js b/web/pgadmin/misc/file_manager/templates/file_manager/js/file_manager.js new file mode 100644 index 000000000..535a890cc --- /dev/null +++ b/web/pgadmin/misc/file_manager/templates/file_manager/js/file_manager.js @@ -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 = $("
"); + + /* + * 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("
"); + 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 = $("
"); + + // 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("
"); + 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 = $("
"); + + // 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("
"); + 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 = $("
"); + + /* + * 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("
"); + 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; + }); diff --git a/web/pgadmin/misc/file_manager/templates/file_manager/js/file_manager_config.json b/web/pgadmin/misc/file_manager/templates/file_manager/js/file_manager_config.json new file mode 100644 index 000000000..efb7fcb6d --- /dev/null +++ b/web/pgadmin/misc/file_manager/templates/file_manager/js/file_manager_config.json @@ -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 + } +} diff --git a/web/pgadmin/misc/file_manager/templates/file_manager/js/languages/en.js b/web/pgadmin/misc/file_manager/templates/file_manager/js/languages/en.js new file mode 100644 index 000000000..dba85e8e8 --- /dev/null +++ b/web/pgadmin/misc/file_manager/templates/file_manager/js/languages/en.js @@ -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" +} diff --git a/web/pgadmin/misc/file_manager/templates/file_manager/js/utility.js b/web/pgadmin/misc/file_manager/templates/file_manager/js/utility.js new file mode 100755 index 000000000..7fcb2b9ef --- /dev/null +++ b/web/pgadmin/misc/file_manager/templates/file_manager/js/utility.js @@ -0,0 +1,1529 @@ +/** + * Filemanager JS core + * + * filemanager.js + * + * @license MIT License + * @author Jason Huck - Core Five Labs + * @author Simon Georget + * @copyright Authors + */ + +(function($) { + +// User alertify object +var alertify = require("alertify"); + + +/*--------------------------------------------------------- + Define functions used for various operations +---------------------------------------------------------*/ + +// function to retrieve GET params +$.urlParam = function(name) { + var results = new RegExp('[\\?&]' + name + '=([^&#]*)').exec(window.location.href); + if (results) + return results[1]; + else + return 0; +}; + +var getFileExtension = function(name) { + var found = name.lastIndexOf('.') + 1; + return (found > 0 ? name.substr(found) : ""); +}; + +var getFileName = function(name) { + var fm_filename = name; + if (fm_filename.length > 15 ) { + fm_filename = name.substr(0, 10) +'...'; + } + return fm_filename; +}; + +// send a request to get transaction id +var getTransId = function() { + return $.ajax({ + async: false, + url: "{{ url_for('file_manager.index') }}get_trans_id", + dataType: "jsonp" + }); +}; + +// Load language file +var loadLangFile = function(enjs) { + if($.urlParam('langCode') !== 0 && file_exists (enjs)) culture = $.urlParam('langCode'); + return $.ajax({ + async: false, + url: enjs, + dataType: 'jsonp', + contentType: "application/json; charset=utf-8" + }); +}; + +// We retrieve config settings from filemanager.config.js +var loadConfigFile = function (type) { + type = (typeof type === "undefined") ? "user" : type; + if (type == 'user') { + url = file_manager_config_json; + userconfig = file_manager_config_json; + } + return $.ajax({ + async: false, + url: url, + dataType: "jsonp", + contentType: "application/json; charset=utf-8" + }); +}; + +/* + * Forces columns to fill the layout vertically. + * Called on initial page load and on resize. + */ +var setDimensions = function() { + var main_container_height = ( $(window).height() ) / 2 + 35, + newH = main_container_height - $('#uploader').height() - 30; +}; + +// Display Min Path +var displayPath = function(path) { + if(config.options.showFullPath === false) { + // if a "displayPathDecorator" function is defined, use it to decorate path + return 'function' === (typeof displayPathDecorator) + ? displayPathDecorator(path) + : path.replace(fileRoot, "/"); + } else { + return path; + } +}; + +// Set the view buttons state +var setViewButtonsFor = function(viewMode) { + if (viewMode == 'grid') { + $('.grid').addClass('ON'); + $('.list').removeClass('ON'); + } + else { + $('.list').addClass('ON'); + $('.grid').removeClass('ON'); + } +}; + +/* + * preg_replace + */ +var preg_replace = function(array_pattern, array_pattern_replace, str) { + var new_str = String (str); + for (i=0; i -1; +} + +// Test if file is authorized +var isAuthorizedFile = function(filename) { + if(config.security.uploadPolicy == 'DISALLOW_ALL') { + if($.inArray(getExtension(filename), config.security.uploadRestrictions) != -1) return true; + } + if(config.security.uploadPolicy == 'ALLOW_ALL') { + if($.inArray(getExtension(filename), config.security.uploadRestrictions) == -1) return true; + } + return false; +}; + +// return filename extension +var getExtension = function(filename) { + if(filename.split('.').length == 1) + return ""; + return filename.split('.').pop(); +}; + +// return filename without extension { +var getFilename = function(filename) { + if(filename.lastIndexOf('.') != -1) + return filename.substring(0, filename.lastIndexOf('.')); + else + return filename; +}; + +// helpful in show/hide toolbar button for Windows +var hideButtons = function() { + var current_path = $('.currentpath').val(); + if(config.options.platform_type == 'win32' && current_path == "/") + return true; + return false; +}; + +/* + * Sets the folder status, upload, and new folder functions + * to the path specified. Called on initial page load and + * whenever a new directory is selected. + */ +var setUploader = function(path){ + $('.storage_dialog #uploader').find('a').remove(); + $('.storage_dialog #uploader').find('b').remove(); + + path = decodeURI(path); + var display_string = displayPath(path); + var mypath = ''; + + // split path + split_path = display_string.split('/'); + split_path = split_path.filter(function(e){return e;}); + + // set empty path if it is windows + if (config.options.platform_type === "win32" && config.options.show_volumes) { + mypath = ""; + } + else if (split_path.length === 0) + mypath = $('/'); + else + mypath = $('/'); + $(mypath).appendTo($('.storage_dialog #uploader h1')); + + for(var i in split_path) { + if (i < split_path.length-1) { + mypath = $(''+split_path[i]+'/'); + $(mypath).appendTo($('.storage_dialog #uploader h1')); + } + else { + mypath = $(''+split_path[i]+''); + $(mypath).appendTo($('.storage_dialog #uploader h1')); + } + } + + $('.currentpath').val(path); + if($('.storage_dialog #uploader h1 span').length === 0) { + $(''+lg.current_folder+'').appendTo($('.storage_dialog #uploader h1')); + } + + $('.storage_dialog #uploader h1').attr('title', display_string); + $('.storage_dialog #uploader h1').attr('data-path', display_string); + + // create new folder + $('.create').unbind().click(function(){ + var foldername = lg.default_foldername; + var $file_element, + $file_element_list; + + $('.file_manager button.create').attr('disabled', 'disabled'); + if($('.fileinfo').data('view') == 'grid'){ + + // template for creating new folder + var folder_div = "
  • "+ + "
    "+ + "

    New_Folder

    "+ + "
  • "; + + var path = $('.currentpath').val(), + $file_element = $(folder_div); + $('.fileinfo #contents.grid').append($file_element); + $file_element.find('p span').toggle(); + $file_element.find('p input').toggle().val(lg.new_folder).select(); + + // rename folder/file on pressing enter key + $('.file_manager').bind().on('keyup', function(e) { + if (e.keyCode == 13) { + e.stopPropagation(); + $file_element.find('p input').trigger('blur'); + } + }); + + // rename folder/file on blur + $file_element.find('p input').on('blur', function(e) { + $('.file_manager button.create').removeAttr('disabled'); + var text_value = $file_element.find('p input').val(), + path = $('.currentpath').val(); + $file_element.find('p input').toggle(); + $file_element.find('p span').toggle().html(text_value); + if(text_value === undefined) text_value = lg.new_folder; + getFolderName(text_value); + getFolderInfo(path); + }); + + } + else if($('.fileinfo').data('view') == 'list'){ + + // template to create new folder in table view + var folder_div = $(""+ + ""+ + "

    "+lg.new_folder+"

    "+ + ""+ + ""+ + ""+ + ""); + + $file_element_list = $(folder_div); + $('.fileinfo #contents.list').prepend($file_element_list); + $file_element_list.find('p span').toggle(); + $file_element_list.find('p input').toggle().val(lg.new_folder).select(); + + // rename folder/file on pressing enter key + $('.file_manager').bind().on('keyup', function(e) { + if (e.keyCode == 13) { + e.stopPropagation(); + $file_element_list.find('p input').trigger('blur'); + } + }); + + // rename folder/file on blur + $file_element_list.find('p input').on('blur', function(e) { + $('.file_manager button.create').removeAttr('disabled'); + var text_value = $file_element_list.find('p input').val(), + path = $('.currentpath').val(); + $file_element_list.find('p input').toggle(); + $file_element_list.find('p span').toggle().html(text_value); + if(text_value === undefined) text_value = lg.new_folder; + getFolderName(text_value); + getFolderInfo(path); + }); + } + + // create a new folder + var getFolderName = function(value){ + var fname = value; + + if(fname != ''){ + foldername = cleanString(fname); + var d = new Date(); // to prevent IE cache issues + $.getJSON(fileConnector + '?mode=addfolder&path=' + $('.currentpath').val() + '&name=' + foldername + '&time=' + d.getMilliseconds(), function(resp){ + result = resp.data.result; + if(result['Code'] === 0){ + alertify.success(lg.successful_added_folder); + getFolderInfo(result['Parent']); + } else { + alertify.error(result['Error']); + } + }); + } else { + alertify.error(lg.no_foldername); + } + }; + + }); +}; + +/* + * Binds specific actions to the toolbar based on capability. + * and show/hide buttons + */ +var bindToolbar = function(data){ + if (!has_capability(data, 'upload') || hideButtons()) { + $('.file_manager').find('button.upload').hide(); + } + else { + $('.file_manager').find('button.upload').show(); + } + + if (!has_capability(data, 'create') || hideButtons()) { + $('.file_manager').find('button.create').hide(); + } + else { + $('.file_manager').find('button.create').show(); + } + + if (!has_capability(data, 'delete') || hideButtons()) { + $('.file_manager').find('button.delete').hide(); + } else { + $('.file_manager').find('button.delete').click(function(){ + $('.fileinfo .delete_item').show(); + }); + + // take action based on pressed button yes or no + $('.fileinfo .delete_item button.btn_yes').unbind().on('click', function() { + if($('.fileinfo').data('view') == 'grid'){ + var path = $('.fileinfo').find('#contents li.selected .clip span').attr('data-alt'); + if(path.lastIndexOf('/') == path.length - 1){ + data['Path'] = path; + deleteItem(data); + } + else { + deleteItem(data); + } + } + else { + var path = $('.fileinfo').find('table#contents tbody tr.selected td:first-child').attr('title'); + if(path.lastIndexOf('/') == path.length - 1){ + data['Path'] = path; + deleteItem(data); + } + else { + deleteItem(data); + } + } + }); + + } + + // Download file on download button click + if (!has_capability(data, 'download') || hideButtons()) { + $('.file_manager').find('button.download').hide(); + } else { + $('.file_manager').find('button.download').unbind().click(function(){ + if($('.fileinfo').data('view') == 'grid'){ + var path = $('.fileinfo li.selected').find('.clip span').attr('data-alt'); + window.open(fileConnector + '?mode=download&path=' + encodeURIComponent(path), '_blank'); + } + else { + var path = $('.fileinfo').find('table#contents tbody tr.selected td:first-child').attr('title'); + window.open(fileConnector + '?mode=download&path=' + encodeURIComponent(path), '_blank'); + } + }); + } + + if (!has_capability(data, 'rename') || hideButtons()) { + $('.file_manager').find('button.rename').hide(); + } + else { + $('.file_manager').find('button.rename').show(); + } +}; + +// enable/disable button when files/folder are loaded +var enable_disable_btn = function() { + if($('.fileinfo').data('view') == 'grid'){ + var $grid_file = $('.file_manager').find('#contents li.selected'); + $grid_file.removeClass('selected'); + $('.file_manager').find('button.delete').prop('disabled', true); + $('.file_manager').find('button.download').prop('disabled', true); + $('.file_manager').find('button.rename').prop('disabled', true); + if ($grid_file.length > 0) { + $('.create_input input[type="text"]').val(''); + $('.file_manager_ok').addClass('disabled'); + } + } else { + var $list_file = $('.fileinfo').find('table#contents tbody tr.selected'); + $list_file.removeClass('selected'); + $('.file_manager').find('button.delete').prop('disabled', true); + $('.file_manager').find('button.download').prop('disabled', true); + $('.file_manager').find('button.rename').prop('disabled', true); + if ($list_file.length > 0) { + $('.create_input input[type="text"]').val(''); + $('.file_manager_ok').addClass('disabled'); + } + } + + $('.delete_item').hide(); + // clear address bar + $('.file_manager #uploader h1').show(); + $('.file_manager #uploader .show_selected_file').remove(); +}; + +// switch to folder view +$('.file_manager .fileinfo').on('click', function(e) { + enable_disable_btn(); +}); + + +// refresh current directory +$('.file_manager .refresh').on('click', function(e) { + enable_disable_btn(); + var curr_path = $('.currentpath').val(); + path = curr_path.substring(0, curr_path.lastIndexOf("/")) + "/"; + getFolderInfo(path); +}); + +/*--------------------------------------------------------- + Item Actions +---------------------------------------------------------*/ + +/* + * Rename the current item and returns the new name. + * by double clicking or by clicking the "Rename" button in + * table(list) views. + */ +var renameItem = function(data){ + var orig_name = getFilename(data['Filename']), + finalName = ''; + + var getNewName = function(rname){ + if(rname !== ''){ + var givenName = nameFormat(rname), + suffix = getExtension(data['Filename']); + if(suffix.length > 0) { + givenName = givenName + '.' + suffix; + } + + var oldPath = data['Path'], + post_data = { + "mode": "rename", + "old": data['Path'], + "new": givenName, + }; + + $.ajax({ + type: 'POST', + data: JSON.stringify(post_data), + url: fileConnector, + dataType: 'json', + contentType: "application/json; charset=utf-8", + async: false, + success: function(resp){ + result = resp.data.result; + if(result['Code'] === 0){ + var newPath = result['New Path'], + newName = result['New Name'], + title = $("#preview h1").attr("title"); + + if (typeof title !="undefined" && title == oldPath) + $('#preview h1').text(newName); + + if($('.fileinfo').data('view') == 'grid'){ + $('.fileinfo span[data-alt="' + oldPath + '"]').parent().next('p span').text(newName); + $('.fileinfo span[data-alt="' + oldPath + '"]').attr('data-alt', newPath); + } else { + $('.fileinfo td[title="' + oldPath + '"]').text(newName); + $('.fileinfo td[title="' + oldPath + '"]').attr('title', newPath); + } + $("#preview h1").html(newName); + + // actualized data for binding + data['Path']=newPath; + data['Filename']=newName; + + // UnBind toolbar functions. + $('.fileinfo').find('button.rename, button.delete, button.download').unbind(); + + alertify.success(lg.successful_rename); + } else { + alertify.error(result['Error']); + } + + finalName = result['New Name']; + } + }); + } + }; + + getNewName(data['NewFilename']); + return finalName; +}; + +/* + * delete the folder or files by clicking the "Delete" button + * in table(list) view + */ +var deleteItem = function(data){ + var isDeleted = false, + msg = lg.confirmation_delete; + + var doDelete = function(data){ + var parent = data['Path'].split('/').reverse().slice(1).reverse().join('/') + '/'; + var post_data = { + "mode": "delete", + "path": data['Path'] + }; + + $.ajax({ + type: 'POST', + data: JSON.stringify(post_data), + url: fileConnector, + dataType: 'json', + contentType: "application/json; charset=utf-8", + async: false, + success: function(resp){ + result = resp.data.result; + if(result['Code'] === 0){ + isDeleted = true; + if(isDeleted) { + alertify.success(lg.successful_delete); + var rootpath = result['Path'].substring(0, result['Path'].length-1); // removing the last slash + rootpath = rootpath.substr(0, rootpath.lastIndexOf('/') + 1); + getFolderInfo(rootpath); + } + } else { + isDeleted = false; + alertify.error(result['Error']); + } + } + }); + return isDeleted; + }; + + doDelete(data); + return isDeleted; +}; + + +// hide message prompt if clicked no +$('.delete_item button.btn_no').on('click', function() { + $('.delete_item').hide(); +}); + +/*--------------------------------------------------------- + Functions to Retrieve File and Folder Details +---------------------------------------------------------*/ + +/* Decides whether to retrieve file or folder info based on + * the path provided. + */ +var getDetailView = function(path){ + if(path.lastIndexOf('/') == path.length - 1){ + var allowed_types = config.options.allowed_file_types; + getFolderInfo(path, allowed_types[0]); + } +}; + +/* + * Retrieves information about the specified file as a JSON + * object and uses that data to populate a template for + * list views. Binds the toolbar for that file/folder to + * enable specific actions. Called whenever an item is + * clicked in list views. + */ +var getFileInfo = function(file){ + // Update location for status, upload, & new folder functions. + var currentpath = file.substr(0, file.lastIndexOf('/') + 1); + setUploader(currentpath); + + // Retrieve the data & populate the template. + var d = new Date(); // to prevent IE cache issues + var post_data = { + 'path': file, + 'mode': 'getinfo' + }; + + $.ajax({ + type: 'POST', + data: JSON.stringify(post_data), + url: fileConnector, + dataType: 'json', + contentType: "application/json; charset=utf-8", + async: false, + success: function(resp){ + data = resp.data.result; + if(data['Code'] === 0){ + var properties = ''; + if(data['Properties']['Size'] || parseInt(data['Properties']['Size'])==0) properties += '
    ' + lg.size + '
    ' + formatBytes(data['Properties']['Size']) + '
    '; + data['Capabilities'] = capabilities; + bindToolbar(data); + } else { + alertify.error(data['Error']); + } + } + }); +}; + +/* + * Retrieves data for all items within the given folder and + * creates a list view. + */ +var getFolderInfo = function(path, file_type=''){ + // Update location for status, upload, & new folder functions. + setUploader(path); + + // set default selected file type + if (file_type === '') + var file_type = $('.change_file_types select').val(); + + // navigate to directory or path when clicked on breadcrumbs + $('.file_manager a.breadcrumbs').unbind().on('click', function() { + var path = $(this).attr('data-path'), + current_dir = $(this).html(), + move_to = path.substring(0, path.lastIndexOf(current_dir))+current_dir; + getFolderInfo(move_to); + enab_dis_level_up(); + }); + + // hide select file if we are listing drives in windows. + if (hideButtons()) { + $(".allowed_file_types .change_file_types").hide(); + } + else { + $(".allowed_file_types .change_file_types").show(); + } + + // Display an activity indicator. + $('.fileinfo').find('span.activity').html(""); + + // Retrieve the data and generate the markup. + var d = new Date(); // to prevent IE cache issues + if ($.urlParam('type')) url += '&type=' + $.urlParam('type'); + + var post_data = { + 'path': path, + 'mode': 'getfolder', + 'file_type': file_type || "*" + }; + + $.ajax({ + type: 'POST', + data: JSON.stringify(post_data), + url: fileConnector, + dataType: 'json', + contentType: "application/json; charset=utf-8", + async: false, + success: function(resp){ + var result = ''; + data = resp.data.result; + + // hide activity indicator + $('.fileinfo').find('span.activity').hide(); + if(data.Code === 0) { + alertify.error(data.err_msg); + return; + } + + // generate HTML for files/folder and render into container + if(data){ + if($('.fileinfo').data('view') == 'grid') { + result += '
      '; + for(key in data) { + var props = data[key]['Properties'], + cap_classes = ""; + for (cap in capabilities) { + if (has_capability(data[key], capabilities[cap])) { + cap_classes += "cap_" + capabilities[cap]; + } + } + + data[key]['Capabilities'] = capabilities; + bindToolbar(data[key]); + + var class_type; + if(data[key]['file_type'] == 'dir') { + class_type = 'fa fa-folder-open fm_folder'; + } + else if(data[key]['file_type'] == 'drive') { + class_type = 'fa fa-hdd-o fm_drive'; + } + else { + class_type = 'fa fa-file-text fm_file'; + } + + var fm_filename = data[key]['Filename']; + if (fm_filename.length > 15 ) { + fm_filename = data[key]['Filename'].substr(0, 10) +'...'; + } + + var file_name_original = encodeURI(data[key]['Filename']); + var file_path_orig = encodeURI(data[key]['Path']); + result += '
    • '; + if (data[key]['Protected'] == 1) { + result += ''; + } + result += '
      '; + if(!has_capability(data[key], 'rename')) + result += '' + fm_filename + ''; + else + result += '

      ' + fm_filename + '

      '; + if(props['Width'] && props['Width'] != '') result += '' + props['Width'] + 'x' + props['Height'] + ''; + if(props['Size'] && props['Size'] != '') result += '' + props['Size'] + ''; + if(props['Date Created'] && props['Date Created'] != '') result += '' + props['Date Created'] + ''; + if(props['Date Modified'] && props['Date Modified'] != '') result += '' + props['Date Modified'] + ''; + result += '
    • '; + } + + result += '
    '; + } else { + result += ''; + result += ''; + result += ''; + + for(key in data){ + var path = encodeURI(data[key]['Path']), + props = data[key]['Properties'], + cap_classes = ""; + for (cap in capabilities) { + if (has_capability(data[key], capabilities[cap])) { + cap_classes += " cap_" + capabilities[cap]; + } + } + + data[key]['Capabilities'] = capabilities; + bindToolbar(data[key]); + + var class_type; + if(data[key]['file_type'] == 'dir') { + class_type = 'fa fa-folder-open tbl_folder'; + } + else if(data[key]['file_type'] == 'drive') { + class_type = 'fa fa-hdd-o tbl_drive'; + } + else { + class_type = 'fa fa-file-text tbl_file' + } + + var file_name_original = encodeURI(data[key]['Filename']); + result += ''; + + var fm_filename = data[key]['Filename']; + if (fm_filename.length > 15 ) { + fm_filename = data[key]['Filename'].substr(0, 10) +'...'; + } + + result += ''; + else + result += '

    ' + fm_filename + '

    '; + + if(props['Size'] && props['Size'] != ''){ + result += ''; + } else { + result += ''; + } + + if(props['Date Modified'] && props['Date Modified'] != ''){ + result += ''; + } else { + result += ''; + } + + result += ''; + } + + result += ''; + result += '
    ' + lg.name + '' + lg.size + '' + lg.modified + '
    '; + if(data[key]['Protected'] == 1) { + result += ''; + } + if(!has_capability(data[key], 'rename')) + result += '' + fm_filename + '' + props['Size'] + '' + props['Date Modified'] + '
    '; + } + } else { + result += '

    ' + lg.could_not_retrieve_folder + '

    '; + } + + // Add the new markup to the DOM. + $('.fileinfo .file_listing').html(result); + + // rename file/folder + $('.file_manager button.rename').unbind().on('click',function(e){ + if($('.fileinfo').data('view') == 'grid'){ + e.stopPropagation(); + var $this = $('.file_manager').find('#contents li.selected p'), + orig_value = decodeURI($this.find('span').attr('title')), + newvalue = orig_value.substring(0, orig_value.indexOf('.')); + + if (newvalue === '') + newvalue = decodeURI(orig_value); + + $this.find('input').toggle().val(newvalue).focus(); + $this.find('span').toggle(); + + // Rename folder/file on pressing enter key + $('.file_manager').unbind().on('keyup', function(e) { + if (e.keyCode == 13) { + e.stopPropagation(); + $('.fileinfo #contents li.selected p').find('input').trigger('blur'); + } + }); + + } + else if($('.fileinfo').data('view') == 'list'){ + e.stopPropagation(); + var $this = $('.fileinfo').find('table#contents tbody tr.selected td:first-child p'), + orig_value = decodeURI($this.find('span').html()), + newvalue = orig_value.substring(0, orig_value.indexOf('.')); + if (newvalue === '') + newvalue = orig_value; + + $this.find('input').toggle().val(newvalue).focus(); + $this.find('span').toggle(); + + // Rename folder/file on pressing enter key + $('.file_manager').unbind().on('keyup', function(e) { + if (e.keyCode == 13) { + e.stopPropagation(); + $('.fileinfo table#contents tr.selected td p').find('input').trigger('blur'); + } + }); + } + }); + + $('.fileinfo #contents li p').on('dblclick',function(e){ + e.stopPropagation(); + $this = $(this); + var orig_value = decodeURI($this.find('span').attr('title')), + newvalue = orig_value.substring(0, orig_value.indexOf('.')); + + if (newvalue === '') + newvalue = orig_value; + + $this.find('input').toggle().val(newvalue).focus(); + $this.find('span').toggle(); + + // Rename folder/file on pressing enter key + $('.file_manager').unbind().on('keyup', function(e) { + if (e.keyCode == 13) { + e.stopPropagation(); + $this.find('input').trigger('blur'); + } + }); + }); + + // Rename UI handling + $('.fileinfo #contents li p').on('blur dblclick','input', function(e){ + e.stopPropagation(); + var old_name = decodeURI($(this).siblings('span').attr('title')), + newvalue = old_name.substring(0, old_name.indexOf('.')); + last = getFileExtension(old_name); + if(old_name.indexOf('.') == 0) + last = '' + + if (newvalue == '') + newvalue = decodeURI(old_name); + + if(e.type=="keydown") + { + if(e.which==13) + { + var full_name = decodeURI($(this).val()) + (last !== '' ? '.' + last: ''); + $(this).toggle(); + $(this).siblings('span').toggle().html(full_name); + + var new_name = decodeURI($(this).val()), + path = decodeURI($(this).parent().parent().find('span').attr('data-alt')); + + var data = { + 'Filename': old_name, + 'Path': path, + 'NewFilename': new_name + }; + + if (newvalue !== new_name) { + renameItem(data); + var parent = $('.currentpath').val(); + getFolderInfo(parent); + } + e.stopPropagation(); + } + if(e.which==38 || e.which==40 || e.which==37 || e.which==39 || e.keyCode == 32) + { + e.stopPropagation(); + } + } + else if(e.type=="focusout") + { + if($(this).css('display')=="inline-block") + { + var full_name = decodeURI($(this).val()) + (last !== ''? '.' + last: ''); + $(this).toggle(); + $(this).siblings('span').toggle().html(full_name); + + var new_name = decodeURI($(this).val()), + path = decodeURI($(this).parent().parent().find('span').attr('data-alt')); + + var data = { + 'Filename': old_name, + 'Path': path, + 'NewFilename': new_name + }; + + if (newvalue !== new_name) { + renameItem(data); + var parent = $('.currentpath').val(); + getFolderInfo(parent); + } + } + } + else + { + e.stopPropagation(); + } + }); + + $('.fileinfo table#contents tr td p').on('dblclick',function(e){ + e.stopPropagation(); + // Prompt to rename file/folder + $this = $(this); + var orig_value = decodeURI($this.find('span').attr('title')), + newvalue = orig_value.substring(0, orig_value.indexOf('.')); + + if (newvalue === '') + newvalue = orig_value; + + $this.find('input').toggle().val(newvalue).focus(); + $this.find('span').toggle(); + + // Rename folder/file on pressing enter key + $('.file_manager').unbind().on('keyup', function(e) { + if (e.keyCode == 13) { + e.stopPropagation(); + $this.find('input').trigger('blur'); + } + }); + }); + + $('.fileinfo table#contents tr td p').on('blur dblclick','input',function(e){ + var old_name = decodeURI($(this).siblings('span').attr('title')), + newvalue = old_name.substring(0, old_name.indexOf('.')); + last = getFileExtension(old_name); + if(old_name.indexOf('.') == 0) + last = '' + + if (newvalue == '') + newvalue = old_name; + + if(e.type=="focusout") + { + if($(this).css('display')=="inline-block") + { + var full_name = decodeURI($(this).val()) + (last !== ''? '.' + last: ''); + $(this).toggle(); + $(this).siblings('span').toggle().html(full_name); + + var new_name = decodeURI($(this).val()), + path = decodeURI($(this).parent().parent().attr('title')); + + var data = { + 'Filename': old_name, + 'Path': path, + 'NewFilename': new_name + }; + + if (newvalue !== new_name) { + renameItem(data); + var parent = path.split('/').reverse().slice(2).reverse().join('/') + '/'; + getFolderInfo(parent); + } + } + } + else + { + e.stopPropagation(); + } + }); + + /* + * Bind click events + * Select items - afolder dblclick + */ + if($('.fileinfo').data('view') == 'grid'){ + // Get into folder on dblclick + $('.fileinfo').find('#contents li').dblclick(function(e){ + e.stopPropagation(); + + // Enable/Disable level up button + enab_dis_level_up(); + var path = decodeURI($(this).find('span').attr('data-alt')); + if(path.lastIndexOf("/") == path.length - 1){ + $('.file_manager_ok').addClass('disabled'); + var $create_input = $('.create_input input[type="text"]'); + $('.file_manager button.delete, .file_manager button.rename').attr('disabled', 'disabled'); + $('.file_manager button.download').attr('disabled', 'disabled'); + getFolderInfo(path); + if ($create_input.length != 0 && $create_input.val() != '') { + $('.file_manager_ok').removeClass('disabled'); + } + } else { + getFileInfo(path); + } + }); + + data_cap = {} + data_cap['Capabilities'] = capabilities; + $('.fileinfo').find('#contents li').click(function(e){ + e.stopPropagation(); + var path = decodeURI($(this).find('.clip span').attr('data-alt')), + file_name = $(this).find('p span').attr('title'), + is_protected = $(this).find('.clip span.fm_lock_icon').attr('data-protected'); + if(path.lastIndexOf('/') == path.length - 1){ + if(has_capability(data_cap, 'select_folder') && is_protected == undefined) { + $(this).parent().find('li.selected').removeClass('selected'); + $(this).addClass('selected'); + $('.file_manager_ok').removeClass('disabled'); + $('.file_manager button.delete, .file_manager button.rename').removeAttr('disabled', 'disabled'); + $('.file_manager button.download').attr('disabled', 'disabled'); + // set selected folder name in breadcrums + $('.file_manager #uploader h1').hide(); + $('.file_manager #uploader .show_selected_file').remove(); + $(''+path+'').appendTo('.file_manager #uploader .filemanager-path-group'); + } + //getFolderInfo(path); + } else { + if(has_capability(data_cap, 'select_file') && is_protected == undefined) { + $(this).parent().find('li.selected').removeClass('selected'); + $(this).addClass('selected'); + $('.file_manager_ok').removeClass('disabled'); + $('.file_manager button.delete, .file_manager button.download, .file_manager button.rename').removeAttr('disabled'); + // set selected folder name in breadcrums + $('.file_manager #uploader h1').hide(); + $('.file_manager #uploader .show_selected_file').remove(); + $(''+path+'').appendTo('.file_manager #uploader .filemanager-path-group'); + } + if(config.options.dialog_type == 'create_file' && is_protected == undefined) { + $('.create_input input[type="text"]').val(file_name); + $('.file_manager_ok, .file_manager_create').removeClass('disabled'); + } + getFileInfo(path); + } + + }); + } else { + $('.fileinfo table#contents tbody tr').on('click', function(e){ + e.stopPropagation(); + var path = decodeURI($('td:first-child', this).attr('title')), + file_name = decodeURI($('td:first-child p span', this).attr('title')), + is_protected = $('td:first-child', this).find('i.tbl_lock_icon').attr('data-protected'); + if(path.lastIndexOf('/') == path.length - 1){ + if(has_capability(data_cap, 'select_folder') && is_protected == undefined) { + $(this).parent().find('tr.selected').removeClass('selected'); + $('td:first-child', this).parent().addClass('selected'); + $('.file_manager_ok').removeClass('disabled'); + $('.file_manager button.download').attr('disabled', 'disabled'); + $('.file_manager button.delete, .file_manager button.rename').removeAttr('disabled'); + // set selected folder name in breadcrums + $('.file_manager #uploader h1').hide(); + $('.file_manager #uploader .show_selected_file').remove(); + $(''+path+'').appendTo('.file_manager #uploader .filemanager-path-group'); + } + //getFolderInfo(path); + } else { + if(has_capability(data_cap, 'select_file') && is_protected == undefined) { + $(this).parent().find('tr.selected').removeClass('selected'); + $('td:first-child', this).parent().addClass('selected'); + $('.file_manager_ok').removeClass('disabled'); + $('.file_manager button.delete, .file_manager button.download, .file_manager button.rename').removeAttr('disabled'); + // set selected folder name in breadcrums + $('.file_manager #uploader h1').hide(); + $('.file_manager #uploader .show_selected_file').remove(); + $(''+path+'').appendTo('.file_manager #uploader .filemanager-path-group'); + } + if(config.options.dialog_type == 'create_file' && is_protected == undefined) { + $('.create_input input[type="text"]').val(file_name); + $('.file_manager_ok, .file_manager_create').removeClass('disabled'); + } + getFileInfo(path); + } + + + }); + + $('.fileinfo table#contents tbody tr').on('dblclick', function(e){ + e.stopPropagation(); + + // Enable/Disable level up button + enab_dis_level_up(); + var path = $('td:first-child', this).attr('title'); + if(path.lastIndexOf('/') == path.length - 1){ + $('.file_manager_ok').removeClass('disabled'); + $('.file_manager button.download').attr('disabled', 'disabled'); + $('.file_manager button.delete, .file_manager button.rename').attr('disabled', 'disabled'); + getFolderInfo(path); + } else { + getFileInfo(path); + } + }); + + } + } + }); +}; + +// Enable/Disable level up button +var enab_dis_level_up = function() { + + $('.file_manager #uploader h1').show(); + $('.show_selected_file').remove(); + setTimeout(function() { + var b = $('.currentpath').val(), + $level_up = $('.file_manager').find('button.level-up'); + $home_btn = $('.file_manager').find('button.home'); + if (b === fileRoot) { + $level_up.attr('disabled', 'disabled'); + $home_btn.attr('disabled', 'disabled'); + } + else { + $home_btn.removeAttr('disabled'); + $level_up.removeAttr('disabled'); + } + }, 100); +} + +// Get transaction id to generate request url and +// to generate config files on runtime +var transId = getTransId(), + t_id = ''; + +if (transId.readyState == 4) + t_res = JSON.parse(transId.responseText); +t_id = t_res.data.fileTransId; + +var root_url = '{{ url_for("file_manager.index") }}', + file_manager_config_json = root_url+t_id+'/file_manager_config.json', + file_manager_config_js = root_url+'file_manager_config.js', + fileConnector = root_url+'filemanager/'+t_id+'/', + confg = loadConfigFile(); + + +// load user configuration file +if (confg.readyState == 4) + config = JSON.parse(confg.responseText); + +var fileRoot = config.options.fileRoot, + capabilities = config.options.capabilities; + +/* + * Get localized messages from file + * through culture var or from URL + */ +var lg = [], + enjs = '{{ url_for("file_manager.index") }}'+"en.js", + lgf = loadLangFile(enjs); + +if (lgf.readyState == 4) + lg = JSON.parse(lgf.responseText); + +// Disable home button on load +$('.file_manager').find('button.home').attr('disabled', 'disabled'); +$('.file_manager').find('button.rename').attr('disabled', 'disabled'); + +if (config.options.dialog_type == 'select_file' || + config.options.dialog_type == 'create_file' || + config.options.dialog_type == 'storage_dialog') { + + // Create file selection dropdown + var allowed_types = config.options.allowed_file_types, + types_len = allowed_types.length; + if(types_len > 0) { + var i = 0, + select_box = "
    "; + select_box += "
    "; + } + + $(".allowed_file_types").html(select_box); + + $(".allowed_file_types select").on('change', function() { + var selected_val = $(this).val(), + curr_path = $('.currentpath').val(); + getFolderInfo(curr_path, selected_val); + }); +} + +if (config.options.dialog_type == 'create_file') { + var create_file_html = '
    '+ + 'Filename:'+ + ''+ + '
    '; + + $('.create_mode_dlg').find('.allowed_file_types').prepend(create_file_html); + + $('.create_input input[type="text"]').on('keypress, keydown', function() { + var input_text_len = $(this).val().length; + if(input_text_len > 0 ) { + $('.file_manager_ok').removeClass('disabled'); + } + else { + $('.file_manager_ok').addClass('disabled'); + } + }); +} +/*--------------------------------------------------------- + Initialization +---------------------------------------------------------*/ + +$(function(){ + if(config.extra_js) { + for(var i=0; i< config.extra_js.length; i++) { + $.ajax({ + url: config.extra_js[i], + dataType: "script", + async: extra_js_async + }); + } + } + + if($.urlParam('expandedFolder') != 0) { + expandedFolder = $.urlParam('expandedFolder'); + fullexpandedFolder = fileRoot + expandedFolder; + } else { + expandedFolder = ''; + fullexpandedFolder = null; + } + + // Adjust layout. + setDimensions(); + + // we finalize the FileManager UI initialization + // with localized text if necessary + if(config.autoload == true) { + $('.upload').append(lg.upload); + $('.create').append(lg.new_folder); + $('.grid').attr('title', lg.grid_view); + $('.list').attr('title', lg.list_view); + $('.fileinfo h1').append(lg.select_from_left); + $('#itemOptions a[href$="#select"]').append(lg.select); + $('#itemOptions a[href$=".download"]').append(lg.download); + $('#itemOptions a[href$=".rename"]').append(lg.rename); + $('#itemOptions a[href$=".delete"]').append(lg.del); + /** Input file Replacement */ + $('.browse').append('+'); + + $('.browse').attr('title', lg.browse); + + $(".newfile").change(function() { + $(".filepath").val($(this).val()); + }); + + /** Input file Replacement - end */ + } + + // Set initial view state. + $('.fileinfo').data('view', config.options.defaultViewMode); + setViewButtonsFor(config.options.defaultViewMode); + + // Upload click event + $('.file_manager .uploader').on('click', 'a', function(e) { + e.preventDefault(); + var b = $('.currentpath').val(); + var node_val = $(this).next().text(); + parent = b.substring(0, b.slice(0, -1).lastIndexOf(node_val)); + getFolderInfo(parent); + }); + + // re-render the home view + $('.file_manager .home').click(function(){ + var currentViewMode = $('.fileinfo').data('view'); + $('.fileinfo').data('view', currentViewMode); + getFolderInfo(fileRoot); + enab_dis_level_up(); + }); + + // Go one directory back + $(".file_manager .level-up").click(function() { + var b = $('.currentpath').val(); + + // Enable/Disable level up button + enab_dis_level_up(); + + if (b != fileRoot) { + parent = b.substring(0, b.slice(0, -1).lastIndexOf("/")) + "/"; + var d = $(".fileinfo").data("view"); + $(".fileinfo").data("view", d); + getFolderInfo(parent); + } + }); + + // set buttons to switch between grid and list views. + $('.file_manager .grid').click(function(){ + setViewButtonsFor('grid'); + $('.fileinfo').data('view', 'grid'); + enable_disable_btn(); + getFolderInfo($('.currentpath').val()); + }); + + // Show list mode + $('.file_manager .list').click(function(){ + setViewButtonsFor('list'); + $('.fileinfo').data('view', 'list'); + enable_disable_btn(); + getFolderInfo($('.currentpath').val()); + }); + + // Provide initial values for upload form, status, etc. + setUploader(fileRoot); + + $('#uploader').attr('action', fileConnector); + + data = { + 'Capabilities': capabilities + }; + if (has_capability(data, 'upload')) { + Dropzone.autoDiscover = false; + // we remove simple file upload element + $('.file-input-container').remove(); + $('.upload').remove(); + $( ".create" ).before( ' ' ); + + $('.upload').unbind().click(function() { + // we create prompt + var msg = '
    '; + msg += ''; + msg += '
    '; + msg += '
    '+lg.file_size_limit + config.upload.fileSizeLimit + ' ' + lg.mb + '.
    '; + + error_flag = false; + var path = $('.currentpath').val(), + fileSize = (config.upload.fileSizeLimit != 'auto') ? config.upload.fileSizeLimit : 256; // default dropzone value + + if(config.security.uploadPolicy == 'DISALLOW_ALL') { + var allowedFiles = '.' + config.security.uploadRestrictions.join(',.'); + } else { + // we allow any extension since we have no easy way to handle the the built-in `acceptedFiles` params + // Would be handled later by the connector + var allowedFiles = null; + } + + if ($.urlParam('type').toString().toLowerCase() == 'images' || config.upload.imagesOnly) { + var allowedFiles = '.' + config.images.imagesExt.join(',.'); + } + + $('.file_manager .upload_file').toggle(); + $('.file_manager .upload_file').html(msg); + + //var previewTemplate = '
    '; + var previewTemplate = '
    '+ + '
    '+ + '

    '+ + '

    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + ''+ + '
    '; + + $("div#multiple-uploads").dropzone({ + paramName: "newfile", + url: fileConnector + '?config=' + userconfig, + maxFilesize: fileSize, + maxFiles: config.upload.number, + addRemoveLinks: true, + previewTemplate: previewTemplate, + parallelUploads: config.upload.number, + dictMaxFilesExceeded: lg.dz_dictMaxFilesExceeded.replace("%s", config.upload.number), + dictDefaultMessage: lg.dz_dictDefaultMessage, + dictInvalidFileType: lg.dz_dictInvalidFileType, + dictFileTooBig: lg.file_too_big + ' ' + lg.file_size_limit + config.upload.fileSizeLimit + ' ' + lg.mb, + acceptedFiles: allowedFiles, + autoProcessQueue: true, + init: function() { + var dropzone = this; + $('.dz_cross_btn').unbind().on('click', function() { + $('.file_manager .upload_file').toggle(); + }); + + }, + sending: function(file, xhr, formData) { + formData.append("mode", "add"); + formData.append("currentpath", path); + $('.upload_file .dz_cross_btn').attr('disabled', 'disabled'); + setTimeout(function() {}, 10000); + }, + success: function(file, response) { + var response = jQuery.parseJSON(response), + data = response.data.result, + $this = $(file.previewTemplate); + + if (data['Code'] == 0) { + setTimeout(function(){ + $this.find(".dz-upload").addClass("success"); + }, 1000); + $this.find(".dz-upload").css('width', "100%").html("100%"); + alertify.success(lg.upload_success); + } else { + $this.find(".dz-upload").addClass("error"); + $this.find(".dz-upload").css('width', "0%").html("0%"); + alertify.error(data['Error']); + } + getFolderInfo(path); + }, + totaluploadprogress: function(progress) { + }, + complete: function(file) { + if (this.getUploadingFiles().length === 0 && this.getQueuedFiles().length === 0) { + } + if (file.status == "error") { + alertify.error(lg.ERROR_UPLOADING_FILE); + } + $('.upload_file .dz_cross_btn').removeAttr('disabled'); + getFolderInfo(path); + } + }); + + }); + } + + // Disable select function if no window.opener + if(! (window.opener || window.tinyMCEPopup) ) $('#itemOptions a[href$="#select"]').remove(); + // Keep only browseOnly features if needed + if(config.options.browseOnly == true) { + $('.newfile').remove(); + $('.upload').remove(); + $('.create').remove(); + $('#toolbar').remove('.rename'); + $('.contextMenu .rename').remove(); + $('.contextMenu .delete').remove(); + } + getDetailView(fileRoot + expandedFolder); +}); + +})(jQuery); diff --git a/web/pgadmin/static/css/jquery.dropzone/dropzone.css b/web/pgadmin/static/css/jquery.dropzone/dropzone.css new file mode 100644 index 000000000..0494d1ccf --- /dev/null +++ b/web/pgadmin/static/css/jquery.dropzone/dropzone.css @@ -0,0 +1,388 @@ +/* + * The MIT License + * Copyright (c) 2012 Matias Meno + */ +@-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; } diff --git a/web/pgadmin/static/css/overrides.css b/web/pgadmin/static/css/overrides.css index 00a982919..d37db64fc 100755 --- a/web/pgadmin/static/css/overrides.css +++ b/web/pgadmin/static/css/overrides.css @@ -1104,4 +1104,55 @@ div.backform_control_notes label.control-label { form[name="change_password_form"] .help-block { color: #A94442 !important; -} \ No newline at end of file +} + + +.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; +} diff --git a/web/pgadmin/static/js/backform.pgadmin.js b/web/pgadmin/static/js/backform.pgadmin.js index 60d45b66b..4a8141173 100644 --- a/web/pgadmin/static/js/backform.pgadmin.js +++ b/web/pgadmin/static/js/backform.pgadmin.js @@ -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([ + '', + '
    ', + '
    ', + ' <%=required ? "required" : ""%> />', + '', + '<% if (helpMessage && helpMessage.length) { %>', + '<%=helpMessage%>', + '<% } %>', + '
    ', + '
    ' + ].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; })); diff --git a/web/pgadmin/static/js/jquery.dropzone/dropzone.js b/web/pgadmin/static/js/jquery.dropzone/dropzone.js new file mode 100644 index 000000000..dd5f39e09 --- /dev/null +++ b/web/pgadmin/static/js/jquery.dropzone/dropzone.js @@ -0,0 +1,1767 @@ + +/* + * + * More info at [www.dropzonejs.com](http://www.dropzonejs.com) + * + * Copyright (c) 2012, Matias Meno + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +(function() { + var Dropzone, Emitter, camelize, contentLoaded, detectVerticalSquash, drawImageIOSFix, noop, without, + __slice = [].slice, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + noop = function() {}; + + Emitter = (function() { + function Emitter() {} + + Emitter.prototype.addEventListener = Emitter.prototype.on; + + Emitter.prototype.on = function(event, fn) { + this._callbacks = this._callbacks || {}; + if (!this._callbacks[event]) { + this._callbacks[event] = []; + } + this._callbacks[event].push(fn); + return this; + }; + + Emitter.prototype.emit = function() { + var args, callback, callbacks, event, _i, _len; + event = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + this._callbacks = this._callbacks || {}; + callbacks = this._callbacks[event]; + if (callbacks) { + for (_i = 0, _len = callbacks.length; _i < _len; _i++) { + callback = callbacks[_i]; + callback.apply(this, args); + } + } + return this; + }; + + Emitter.prototype.removeListener = Emitter.prototype.off; + + Emitter.prototype.removeAllListeners = Emitter.prototype.off; + + Emitter.prototype.removeEventListener = Emitter.prototype.off; + + Emitter.prototype.off = function(event, fn) { + var callback, callbacks, i, _i, _len; + if (!this._callbacks || arguments.length === 0) { + this._callbacks = {}; + return this; + } + callbacks = this._callbacks[event]; + if (!callbacks) { + return this; + } + if (arguments.length === 1) { + delete this._callbacks[event]; + return this; + } + for (i = _i = 0, _len = callbacks.length; _i < _len; i = ++_i) { + callback = callbacks[i]; + if (callback === fn) { + callbacks.splice(i, 1); + break; + } + } + return this; + }; + + return Emitter; + + })(); + + Dropzone = (function(_super) { + var extend, resolveOption; + + __extends(Dropzone, _super); + + Dropzone.prototype.Emitter = Emitter; + + + /* + This is a list of all available events you can register on a dropzone object. + + You can register an event handler like this: + + dropzone.on("dragEnter", function() { }); + */ + + Dropzone.prototype.events = ["drop", "dragstart", "dragend", "dragenter", "dragover", "dragleave", "addedfile", "addedfiles", "removedfile", "thumbnail", "error", "errormultiple", "processing", "processingmultiple", "uploadprogress", "totaluploadprogress", "sending", "sendingmultiple", "success", "successmultiple", "canceled", "canceledmultiple", "complete", "completemultiple", "reset", "maxfilesexceeded", "maxfilesreached", "queuecomplete"]; + + Dropzone.prototype.defaultOptions = { + url: null, + method: "post", + withCredentials: false, + parallelUploads: 2, + uploadMultiple: false, + maxFilesize: 256, + paramName: "file", + createImageThumbnails: true, + maxThumbnailFilesize: 10, + thumbnailWidth: 120, + thumbnailHeight: 120, + filesizeBase: 1000, + maxFiles: null, + params: {}, + clickable: true, + ignoreHiddenFiles: true, + acceptedFiles: null, + acceptedMimeTypes: null, + autoProcessQueue: true, + autoQueue: true, + addRemoveLinks: false, + previewsContainer: null, + hiddenInputContainer: "body", + capture: null, + renameFilename: null, + dictDefaultMessage: "Drop files here to upload", + dictFallbackMessage: "Your browser does not support drag'n'drop file uploads.", + dictFallbackText: "Please use the fallback form below to upload your files like in the olden days.", + dictFileTooBig: "File is too big ({{filesize}}MiB). Max filesize: {{maxFilesize}}MiB.", + dictInvalidFileType: "You can't upload files of this type.", + dictResponseError: "Server responded with {{statusCode}} code.", + dictCancelUpload: "Cancel upload", + dictCancelUploadConfirmation: "Are you sure you want to cancel this upload?", + dictRemoveFile: "Remove file", + dictRemoveFileConfirmation: null, + dictMaxFilesExceeded: "You can not upload any more files.", + accept: function(file, done) { + return done(); + }, + init: function() { + return noop; + }, + forceFallback: false, + fallback: function() { + var child, messageElement, span, _i, _len, _ref; + this.element.className = "" + this.element.className + " dz-browser-not-supported"; + _ref = this.element.getElementsByTagName("div"); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + child = _ref[_i]; + if (/(^| )dz-message($| )/.test(child.className)) { + messageElement = child; + child.className = "dz-message"; + continue; + } + } + if (!messageElement) { + messageElement = Dropzone.createElement("
    "); + this.element.appendChild(messageElement); + } + span = messageElement.getElementsByTagName("span")[0]; + if (span) { + if (span.textContent != null) { + span.textContent = this.options.dictFallbackMessage; + } else if (span.innerText != null) { + span.innerText = this.options.dictFallbackMessage; + } + } + return this.element.appendChild(this.getFallbackForm()); + }, + resize: function(file) { + var info, srcRatio, trgRatio; + info = { + srcX: 0, + srcY: 0, + srcWidth: file.width, + srcHeight: file.height + }; + srcRatio = file.width / file.height; + info.optWidth = this.options.thumbnailWidth; + info.optHeight = this.options.thumbnailHeight; + if ((info.optWidth == null) && (info.optHeight == null)) { + info.optWidth = info.srcWidth; + info.optHeight = info.srcHeight; + } else if (info.optWidth == null) { + info.optWidth = srcRatio * info.optHeight; + } else if (info.optHeight == null) { + info.optHeight = (1 / srcRatio) * info.optWidth; + } + trgRatio = info.optWidth / info.optHeight; + if (file.height < info.optHeight || file.width < info.optWidth) { + info.trgHeight = info.srcHeight; + info.trgWidth = info.srcWidth; + } else { + if (srcRatio > trgRatio) { + info.srcHeight = file.height; + info.srcWidth = info.srcHeight * trgRatio; + } else { + info.srcWidth = file.width; + info.srcHeight = info.srcWidth / trgRatio; + } + } + info.srcX = (file.width - info.srcWidth) / 2; + info.srcY = (file.height - info.srcHeight) / 2; + return info; + }, + + /* + Those functions register themselves to the events on init and handle all + the user interface specific stuff. Overwriting them won't break the upload + but can break the way it's displayed. + You can overwrite them if you don't like the default behavior. If you just + want to add an additional event handler, register it on the dropzone object + and don't overwrite those options. + */ + drop: function(e) { + return this.element.classList.remove("dz-drag-hover"); + }, + dragstart: noop, + dragend: function(e) { + return this.element.classList.remove("dz-drag-hover"); + }, + dragenter: function(e) { + return this.element.classList.add("dz-drag-hover"); + }, + dragover: function(e) { + return this.element.classList.add("dz-drag-hover"); + }, + dragleave: function(e) { + return this.element.classList.remove("dz-drag-hover"); + }, + paste: noop, + reset: function() { + return this.element.classList.remove("dz-started"); + }, + addedfile: function(file) { + var node, removeFileEvent, removeLink, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _results; + if (this.element === this.previewsContainer) { + this.element.classList.add("dz-started"); + } + if (this.previewsContainer) { + file.previewElement = Dropzone.createElement(this.options.previewTemplate.trim()); + file.previewTemplate = file.previewElement; + this.previewsContainer.appendChild(file.previewElement); + _ref = file.previewElement.querySelectorAll("[data-dz-name]"); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + node = _ref[_i]; + node.textContent = this._renameFilename(file.name); + } + _ref1 = file.previewElement.querySelectorAll("[data-dz-size]"); + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + node = _ref1[_j]; + node.innerHTML = this.filesize(file.size); + } + if (this.options.addRemoveLinks) { + file._removeLink = Dropzone.createElement("" + this.options.dictRemoveFile + ""); + file.previewElement.appendChild(file._removeLink); + } + removeFileEvent = (function(_this) { + return function(e) { + e.preventDefault(); + e.stopPropagation(); + if (file.status === Dropzone.UPLOADING) { + return Dropzone.confirm(_this.options.dictCancelUploadConfirmation, function() { + return _this.removeFile(file); + }); + } else { + if (_this.options.dictRemoveFileConfirmation) { + return Dropzone.confirm(_this.options.dictRemoveFileConfirmation, function() { + return _this.removeFile(file); + }); + } else { + return _this.removeFile(file); + } + } + }; + })(this); + _ref2 = file.previewElement.querySelectorAll("[data-dz-remove]"); + _results = []; + for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { + removeLink = _ref2[_k]; + _results.push(removeLink.addEventListener("click", removeFileEvent)); + } + return _results; + } + }, + removedfile: function(file) { + var _ref; + if (file.previewElement) { + if ((_ref = file.previewElement) != null) { + _ref.parentNode.removeChild(file.previewElement); + } + } + return this._updateMaxFilesReachedClass(); + }, + thumbnail: function(file, dataUrl) { + var thumbnailElement, _i, _len, _ref; + if (file.previewElement) { + file.previewElement.classList.remove("dz-file-preview"); + _ref = file.previewElement.querySelectorAll("[data-dz-thumbnail]"); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + thumbnailElement = _ref[_i]; + thumbnailElement.alt = file.name; + thumbnailElement.src = dataUrl; + } + return setTimeout(((function(_this) { + return function() { + return file.previewElement.classList.add("dz-image-preview"); + }; + })(this)), 1); + } + }, + error: function(file, message) { + var node, _i, _len, _ref, _results; + if (file.previewElement) { + file.previewElement.classList.add("dz-error"); + if (typeof message !== "String" && message.error) { + message = message.error; + } + _ref = file.previewElement.querySelectorAll("[data-dz-errormessage]"); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + node = _ref[_i]; + _results.push(node.textContent = message); + } + return _results; + } + }, + errormultiple: noop, + processing: function(file) { + if (file.previewElement) { + file.previewElement.classList.add("dz-processing"); + if (file._removeLink) { + return file._removeLink.textContent = this.options.dictCancelUpload; + } + } + }, + processingmultiple: noop, + uploadprogress: function(file, progress, bytesSent) { + var node, _i, _len, _ref, _results; + if (file.previewElement) { + _ref = file.previewElement.querySelectorAll("[data-dz-uploadprogress]"); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + node = _ref[_i]; + if (node.nodeName === 'PROGRESS') { + _results.push(node.value = progress); + } else { + _results.push(node.style.width = "" + progress + "%"); + } + } + return _results; + } + }, + totaluploadprogress: noop, + sending: noop, + sendingmultiple: noop, + success: function(file) { + if (file.previewElement) { + return file.previewElement.classList.add("dz-success"); + } + }, + successmultiple: noop, + canceled: function(file) { + return this.emit("error", file, "Upload canceled."); + }, + canceledmultiple: noop, + complete: function(file) { + if (file._removeLink) { + file._removeLink.textContent = this.options.dictRemoveFile; + } + if (file.previewElement) { + return file.previewElement.classList.add("dz-complete"); + } + }, + completemultiple: noop, + maxfilesexceeded: noop, + maxfilesreached: noop, + queuecomplete: noop, + addedfiles: noop, + previewTemplate: "
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n \n Check\n \n \n \n \n \n
    \n
    \n \n Error\n \n \n \n \n \n \n \n
    \n
    " + }; + + extend = function() { + var key, object, objects, target, val, _i, _len; + target = arguments[0], objects = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + for (_i = 0, _len = objects.length; _i < _len; _i++) { + object = objects[_i]; + for (key in object) { + val = object[key]; + target[key] = val; + } + } + return target; + }; + + function Dropzone(element, options) { + var elementOptions, fallback, _ref; + this.element = element; + this.version = Dropzone.version; + this.defaultOptions.previewTemplate = this.defaultOptions.previewTemplate.replace(/\n*/g, ""); + this.clickableElements = []; + this.listeners = []; + this.files = []; + if (typeof this.element === "string") { + this.element = document.querySelector(this.element); + } + if (!(this.element && (this.element.nodeType != null))) { + throw new Error("Invalid dropzone element."); + } + if (this.element.dropzone) { + throw new Error("Dropzone already attached."); + } + Dropzone.instances.push(this); + this.element.dropzone = this; + elementOptions = (_ref = Dropzone.optionsForElement(this.element)) != null ? _ref : {}; + this.options = extend({}, this.defaultOptions, elementOptions, options != null ? options : {}); + if (this.options.forceFallback || !Dropzone.isBrowserSupported()) { + return this.options.fallback.call(this); + } + if (this.options.url == null) { + this.options.url = this.element.getAttribute("action"); + } + if (!this.options.url) { + throw new Error("No URL provided."); + } + if (this.options.acceptedFiles && this.options.acceptedMimeTypes) { + throw new Error("You can't provide both 'acceptedFiles' and 'acceptedMimeTypes'. 'acceptedMimeTypes' is deprecated."); + } + if (this.options.acceptedMimeTypes) { + this.options.acceptedFiles = this.options.acceptedMimeTypes; + delete this.options.acceptedMimeTypes; + } + this.options.method = this.options.method.toUpperCase(); + if ((fallback = this.getExistingFallback()) && fallback.parentNode) { + fallback.parentNode.removeChild(fallback); + } + if (this.options.previewsContainer !== false) { + if (this.options.previewsContainer) { + this.previewsContainer = Dropzone.getElement(this.options.previewsContainer, "previewsContainer"); + } else { + this.previewsContainer = this.element; + } + } + if (this.options.clickable) { + if (this.options.clickable === true) { + this.clickableElements = [this.element]; + } else { + this.clickableElements = Dropzone.getElements(this.options.clickable, "clickable"); + } + } + this.init(); + } + + Dropzone.prototype.getAcceptedFiles = function() { + var file, _i, _len, _ref, _results; + _ref = this.files; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if (file.accepted) { + _results.push(file); + } + } + return _results; + }; + + Dropzone.prototype.getRejectedFiles = function() { + var file, _i, _len, _ref, _results; + _ref = this.files; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if (!file.accepted) { + _results.push(file); + } + } + return _results; + }; + + Dropzone.prototype.getFilesWithStatus = function(status) { + var file, _i, _len, _ref, _results; + _ref = this.files; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if (file.status === status) { + _results.push(file); + } + } + return _results; + }; + + Dropzone.prototype.getQueuedFiles = function() { + return this.getFilesWithStatus(Dropzone.QUEUED); + }; + + Dropzone.prototype.getUploadingFiles = function() { + return this.getFilesWithStatus(Dropzone.UPLOADING); + }; + + Dropzone.prototype.getAddedFiles = function() { + return this.getFilesWithStatus(Dropzone.ADDED); + }; + + Dropzone.prototype.getActiveFiles = function() { + var file, _i, _len, _ref, _results; + _ref = this.files; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if (file.status === Dropzone.UPLOADING || file.status === Dropzone.QUEUED) { + _results.push(file); + } + } + return _results; + }; + + Dropzone.prototype.init = function() { + var eventName, noPropagation, setupHiddenFileInput, _i, _len, _ref, _ref1; + if (this.element.tagName === "form") { + this.element.setAttribute("enctype", "multipart/form-data"); + } + if (this.element.classList.contains("dropzone") && !this.element.querySelector(".dz-message")) { + this.element.appendChild(Dropzone.createElement("
    " + this.options.dictDefaultMessage + "
    ")); + } + if (this.clickableElements.length) { + setupHiddenFileInput = (function(_this) { + return function() { + if (_this.hiddenFileInput) { + _this.hiddenFileInput.parentNode.removeChild(_this.hiddenFileInput); + } + _this.hiddenFileInput = document.createElement("input"); + _this.hiddenFileInput.setAttribute("type", "file"); + if ((_this.options.maxFiles == null) || _this.options.maxFiles > 1) { + _this.hiddenFileInput.setAttribute("multiple", "multiple"); + } + _this.hiddenFileInput.className = "dz-hidden-input"; + if (_this.options.acceptedFiles != null) { + _this.hiddenFileInput.setAttribute("accept", _this.options.acceptedFiles); + } + if (_this.options.capture != null) { + _this.hiddenFileInput.setAttribute("capture", _this.options.capture); + } + _this.hiddenFileInput.style.visibility = "hidden"; + _this.hiddenFileInput.style.position = "absolute"; + _this.hiddenFileInput.style.top = "0"; + _this.hiddenFileInput.style.left = "0"; + _this.hiddenFileInput.style.height = "0"; + _this.hiddenFileInput.style.width = "0"; + document.querySelector(_this.options.hiddenInputContainer).appendChild(_this.hiddenFileInput); + return _this.hiddenFileInput.addEventListener("change", function() { + var file, files, _i, _len; + files = _this.hiddenFileInput.files; + if (files.length) { + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + _this.addFile(file); + } + } + _this.emit("addedfiles", files); + return setupHiddenFileInput(); + }); + }; + })(this); + setupHiddenFileInput(); + } + this.URL = (_ref = window.URL) != null ? _ref : window.webkitURL; + _ref1 = this.events; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + eventName = _ref1[_i]; + this.on(eventName, this.options[eventName]); + } + this.on("uploadprogress", (function(_this) { + return function() { + return _this.updateTotalUploadProgress(); + }; + })(this)); + this.on("removedfile", (function(_this) { + return function() { + return _this.updateTotalUploadProgress(); + }; + })(this)); + this.on("canceled", (function(_this) { + return function(file) { + return _this.emit("complete", file); + }; + })(this)); + this.on("complete", (function(_this) { + return function(file) { + if (_this.getAddedFiles().length === 0 && _this.getUploadingFiles().length === 0 && _this.getQueuedFiles().length === 0) { + return setTimeout((function() { + return _this.emit("queuecomplete"); + }), 0); + } + }; + })(this)); + noPropagation = function(e) { + e.stopPropagation(); + if (e.preventDefault) { + return e.preventDefault(); + } else { + return e.returnValue = false; + } + }; + this.listeners = [ + { + element: this.element, + events: { + "dragstart": (function(_this) { + return function(e) { + return _this.emit("dragstart", e); + }; + })(this), + "dragenter": (function(_this) { + return function(e) { + noPropagation(e); + return _this.emit("dragenter", e); + }; + })(this), + "dragover": (function(_this) { + return function(e) { + var efct; + try { + efct = e.dataTransfer.effectAllowed; + } catch (_error) {} + e.dataTransfer.dropEffect = 'move' === efct || 'linkMove' === efct ? 'move' : 'copy'; + noPropagation(e); + return _this.emit("dragover", e); + }; + })(this), + "dragleave": (function(_this) { + return function(e) { + return _this.emit("dragleave", e); + }; + })(this), + "drop": (function(_this) { + return function(e) { + noPropagation(e); + return _this.drop(e); + }; + })(this), + "dragend": (function(_this) { + return function(e) { + return _this.emit("dragend", e); + }; + })(this) + } + } + ]; + this.clickableElements.forEach((function(_this) { + return function(clickableElement) { + return _this.listeners.push({ + element: clickableElement, + events: { + "click": function(evt) { + if ((clickableElement !== _this.element) || (evt.target === _this.element || Dropzone.elementInside(evt.target, _this.element.querySelector(".dz-message")))) { + _this.hiddenFileInput.click(); + } + return true; + } + } + }); + }; + })(this)); + this.enable(); + return this.options.init.call(this); + }; + + Dropzone.prototype.destroy = function() { + var _ref; + this.disable(); + this.removeAllFiles(true); + if ((_ref = this.hiddenFileInput) != null ? _ref.parentNode : void 0) { + this.hiddenFileInput.parentNode.removeChild(this.hiddenFileInput); + this.hiddenFileInput = null; + } + delete this.element.dropzone; + return Dropzone.instances.splice(Dropzone.instances.indexOf(this), 1); + }; + + Dropzone.prototype.updateTotalUploadProgress = function() { + var activeFiles, file, totalBytes, totalBytesSent, totalUploadProgress, _i, _len, _ref; + totalBytesSent = 0; + totalBytes = 0; + activeFiles = this.getActiveFiles(); + if (activeFiles.length) { + _ref = this.getActiveFiles(); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + totalBytesSent += file.upload.bytesSent; + totalBytes += file.upload.total; + } + totalUploadProgress = 100 * totalBytesSent / totalBytes; + } else { + totalUploadProgress = 100; + } + return this.emit("totaluploadprogress", totalUploadProgress, totalBytes, totalBytesSent); + }; + + Dropzone.prototype._getParamName = function(n) { + if (typeof this.options.paramName === "function") { + return this.options.paramName(n); + } else { + return "" + this.options.paramName + (this.options.uploadMultiple ? "[" + n + "]" : ""); + } + }; + + Dropzone.prototype._renameFilename = function(name) { + if (typeof this.options.renameFilename !== "function") { + return name; + } + return this.options.renameFilename(name); + }; + + Dropzone.prototype.getFallbackForm = function() { + var existingFallback, fields, fieldsString, form; + if (existingFallback = this.getExistingFallback()) { + return existingFallback; + } + fieldsString = "
    "; + if (this.options.dictFallbackText) { + fieldsString += "

    " + this.options.dictFallbackText + "

    "; + } + fieldsString += "
    "; + fields = Dropzone.createElement(fieldsString); + if (this.element.tagName !== "FORM") { + form = Dropzone.createElement("
    "); + form.appendChild(fields); + } else { + this.element.setAttribute("enctype", "multipart/form-data"); + this.element.setAttribute("method", this.options.method); + } + return form != null ? form : fields; + }; + + Dropzone.prototype.getExistingFallback = function() { + var fallback, getFallback, tagName, _i, _len, _ref; + getFallback = function(elements) { + var el, _i, _len; + for (_i = 0, _len = elements.length; _i < _len; _i++) { + el = elements[_i]; + if (/(^| )fallback($| )/.test(el.className)) { + return el; + } + } + }; + _ref = ["div", "form"]; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + tagName = _ref[_i]; + if (fallback = getFallback(this.element.getElementsByTagName(tagName))) { + return fallback; + } + } + }; + + Dropzone.prototype.setupEventListeners = function() { + var elementListeners, event, listener, _i, _len, _ref, _results; + _ref = this.listeners; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + elementListeners = _ref[_i]; + _results.push((function() { + var _ref1, _results1; + _ref1 = elementListeners.events; + _results1 = []; + for (event in _ref1) { + listener = _ref1[event]; + _results1.push(elementListeners.element.addEventListener(event, listener, false)); + } + return _results1; + })()); + } + return _results; + }; + + Dropzone.prototype.removeEventListeners = function() { + var elementListeners, event, listener, _i, _len, _ref, _results; + _ref = this.listeners; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + elementListeners = _ref[_i]; + _results.push((function() { + var _ref1, _results1; + _ref1 = elementListeners.events; + _results1 = []; + for (event in _ref1) { + listener = _ref1[event]; + _results1.push(elementListeners.element.removeEventListener(event, listener, false)); + } + return _results1; + })()); + } + return _results; + }; + + Dropzone.prototype.disable = function() { + var file, _i, _len, _ref, _results; + this.clickableElements.forEach(function(element) { + return element.classList.remove("dz-clickable"); + }); + this.removeEventListeners(); + _ref = this.files; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + _results.push(this.cancelUpload(file)); + } + return _results; + }; + + Dropzone.prototype.enable = function() { + this.clickableElements.forEach(function(element) { + return element.classList.add("dz-clickable"); + }); + return this.setupEventListeners(); + }; + + Dropzone.prototype.filesize = function(size) { + var cutoff, i, selectedSize, selectedUnit, unit, units, _i, _len; + selectedSize = 0; + selectedUnit = "b"; + if (size > 0) { + units = ['TB', 'GB', 'MB', 'KB', 'b']; + for (i = _i = 0, _len = units.length; _i < _len; i = ++_i) { + unit = units[i]; + cutoff = Math.pow(this.options.filesizeBase, 4 - i) / 10; + if (size >= cutoff) { + selectedSize = size / Math.pow(this.options.filesizeBase, 4 - i); + selectedUnit = unit; + break; + } + } + selectedSize = Math.round(10 * selectedSize) / 10; + } + return "" + selectedSize + " " + selectedUnit; + }; + + Dropzone.prototype._updateMaxFilesReachedClass = function() { + if ((this.options.maxFiles != null) && this.getAcceptedFiles().length >= this.options.maxFiles) { + if (this.getAcceptedFiles().length === this.options.maxFiles) { + this.emit('maxfilesreached', this.files); + } + return this.element.classList.add("dz-max-files-reached"); + } else { + return this.element.classList.remove("dz-max-files-reached"); + } + }; + + Dropzone.prototype.drop = function(e) { + var files, items; + if (!e.dataTransfer) { + return; + } + this.emit("drop", e); + files = e.dataTransfer.files; + this.emit("addedfiles", files); + if (files.length) { + items = e.dataTransfer.items; + if (items && items.length && (items[0].webkitGetAsEntry != null)) { + this._addFilesFromItems(items); + } else { + this.handleFiles(files); + } + } + }; + + Dropzone.prototype.paste = function(e) { + var items, _ref; + if ((e != null ? (_ref = e.clipboardData) != null ? _ref.items : void 0 : void 0) == null) { + return; + } + this.emit("paste", e); + items = e.clipboardData.items; + if (items.length) { + return this._addFilesFromItems(items); + } + }; + + Dropzone.prototype.handleFiles = function(files) { + var file, _i, _len, _results; + _results = []; + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + _results.push(this.addFile(file)); + } + return _results; + }; + + Dropzone.prototype._addFilesFromItems = function(items) { + var entry, item, _i, _len, _results; + _results = []; + for (_i = 0, _len = items.length; _i < _len; _i++) { + item = items[_i]; + if ((item.webkitGetAsEntry != null) && (entry = item.webkitGetAsEntry())) { + if (entry.isFile) { + _results.push(this.addFile(item.getAsFile())); + } else if (entry.isDirectory) { + _results.push(this._addFilesFromDirectory(entry, entry.name)); + } else { + _results.push(void 0); + } + } else if (item.getAsFile != null) { + if ((item.kind == null) || item.kind === "file") { + _results.push(this.addFile(item.getAsFile())); + } else { + _results.push(void 0); + } + } else { + _results.push(void 0); + } + } + return _results; + }; + + Dropzone.prototype._addFilesFromDirectory = function(directory, path) { + var dirReader, errorHandler, readEntries; + dirReader = directory.createReader(); + errorHandler = function(error) { + return typeof console !== "undefined" && console !== null ? typeof console.log === "function" ? console.log(error) : void 0 : void 0; + }; + readEntries = (function(_this) { + return function() { + return dirReader.readEntries(function(entries) { + var entry, _i, _len; + if (entries.length > 0) { + for (_i = 0, _len = entries.length; _i < _len; _i++) { + entry = entries[_i]; + if (entry.isFile) { + entry.file(function(file) { + if (_this.options.ignoreHiddenFiles && file.name.substring(0, 1) === '.') { + return; + } + file.fullPath = "" + path + "/" + file.name; + return _this.addFile(file); + }); + } else if (entry.isDirectory) { + _this._addFilesFromDirectory(entry, "" + path + "/" + entry.name); + } + } + readEntries(); + } + return null; + }, errorHandler); + }; + })(this); + return readEntries(); + }; + + Dropzone.prototype.accept = function(file, done) { + if (file.size > this.options.maxFilesize * 1024 * 1024) { + return done(this.options.dictFileTooBig.replace("{{filesize}}", Math.round(file.size / 1024 / 10.24) / 100).replace("{{maxFilesize}}", this.options.maxFilesize)); + } else if (!Dropzone.isValidFile(file, this.options.acceptedFiles)) { + return done(this.options.dictInvalidFileType); + } else if ((this.options.maxFiles != null) && this.getAcceptedFiles().length >= this.options.maxFiles) { + done(this.options.dictMaxFilesExceeded.replace("{{maxFiles}}", this.options.maxFiles)); + return this.emit("maxfilesexceeded", file); + } else { + return this.options.accept.call(this, file, done); + } + }; + + Dropzone.prototype.addFile = function(file) { + file.upload = { + progress: 0, + total: file.size, + bytesSent: 0 + }; + this.files.push(file); + file.status = Dropzone.ADDED; + this.emit("addedfile", file); + this._enqueueThumbnail(file); + return this.accept(file, (function(_this) { + return function(error) { + if (error) { + file.accepted = false; + _this._errorProcessing([file], error); + } else { + file.accepted = true; + if (_this.options.autoQueue) { + _this.enqueueFile(file); + } + } + return _this._updateMaxFilesReachedClass(); + }; + })(this)); + }; + + Dropzone.prototype.enqueueFiles = function(files) { + var file, _i, _len; + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + this.enqueueFile(file); + } + return null; + }; + + Dropzone.prototype.enqueueFile = function(file) { + if (file.status === Dropzone.ADDED && file.accepted === true) { + file.status = Dropzone.QUEUED; + if (this.options.autoProcessQueue) { + return setTimeout(((function(_this) { + return function() { + return _this.processQueue(); + }; + })(this)), 0); + } + } else { + throw new Error("This file can't be queued because it has already been processed or was rejected."); + } + }; + + Dropzone.prototype._thumbnailQueue = []; + + Dropzone.prototype._processingThumbnail = false; + + Dropzone.prototype._enqueueThumbnail = function(file) { + if (this.options.createImageThumbnails && file.type.match(/image.*/) && file.size <= this.options.maxThumbnailFilesize * 1024 * 1024) { + this._thumbnailQueue.push(file); + return setTimeout(((function(_this) { + return function() { + return _this._processThumbnailQueue(); + }; + })(this)), 0); + } + }; + + Dropzone.prototype._processThumbnailQueue = function() { + if (this._processingThumbnail || this._thumbnailQueue.length === 0) { + return; + } + this._processingThumbnail = true; + return this.createThumbnail(this._thumbnailQueue.shift(), (function(_this) { + return function() { + _this._processingThumbnail = false; + return _this._processThumbnailQueue(); + }; + })(this)); + }; + + Dropzone.prototype.removeFile = function(file) { + if (file.status === Dropzone.UPLOADING) { + this.cancelUpload(file); + } + this.files = without(this.files, file); + this.emit("removedfile", file); + if (this.files.length === 0) { + return this.emit("reset"); + } + }; + + Dropzone.prototype.removeAllFiles = function(cancelIfNecessary) { + var file, _i, _len, _ref; + if (cancelIfNecessary == null) { + cancelIfNecessary = false; + } + _ref = this.files.slice(); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if (file.status !== Dropzone.UPLOADING || cancelIfNecessary) { + this.removeFile(file); + } + } + return null; + }; + + Dropzone.prototype.createThumbnail = function(file, callback) { + var fileReader; + fileReader = new FileReader; + fileReader.onload = (function(_this) { + return function() { + if (file.type === "image/svg+xml") { + _this.emit("thumbnail", file, fileReader.result); + if (callback != null) { + callback(); + } + return; + } + return _this.createThumbnailFromUrl(file, fileReader.result, callback); + }; + })(this); + return fileReader.readAsDataURL(file); + }; + + Dropzone.prototype.createThumbnailFromUrl = function(file, imageUrl, callback, crossOrigin) { + var img; + img = document.createElement("img"); + if (crossOrigin) { + img.crossOrigin = crossOrigin; + } + img.onload = (function(_this) { + return function() { + var canvas, ctx, resizeInfo, thumbnail, _ref, _ref1, _ref2, _ref3; + file.width = img.width; + file.height = img.height; + resizeInfo = _this.options.resize.call(_this, file); + if (resizeInfo.trgWidth == null) { + resizeInfo.trgWidth = resizeInfo.optWidth; + } + if (resizeInfo.trgHeight == null) { + resizeInfo.trgHeight = resizeInfo.optHeight; + } + canvas = document.createElement("canvas"); + ctx = canvas.getContext("2d"); + canvas.width = resizeInfo.trgWidth; + canvas.height = resizeInfo.trgHeight; + drawImageIOSFix(ctx, img, (_ref = resizeInfo.srcX) != null ? _ref : 0, (_ref1 = resizeInfo.srcY) != null ? _ref1 : 0, resizeInfo.srcWidth, resizeInfo.srcHeight, (_ref2 = resizeInfo.trgX) != null ? _ref2 : 0, (_ref3 = resizeInfo.trgY) != null ? _ref3 : 0, resizeInfo.trgWidth, resizeInfo.trgHeight); + thumbnail = canvas.toDataURL("image/png"); + _this.emit("thumbnail", file, thumbnail); + if (callback != null) { + return callback(); + } + }; + })(this); + if (callback != null) { + img.onerror = callback; + } + return img.src = imageUrl; + }; + + Dropzone.prototype.processQueue = function() { + var i, parallelUploads, processingLength, queuedFiles; + parallelUploads = this.options.parallelUploads; + processingLength = this.getUploadingFiles().length; + i = processingLength; + if (processingLength >= parallelUploads) { + return; + } + queuedFiles = this.getQueuedFiles(); + if (!(queuedFiles.length > 0)) { + return; + } + if (this.options.uploadMultiple) { + return this.processFiles(queuedFiles.slice(0, parallelUploads - processingLength)); + } else { + while (i < parallelUploads) { + if (!queuedFiles.length) { + return; + } + this.processFile(queuedFiles.shift()); + i++; + } + } + }; + + Dropzone.prototype.processFile = function(file) { + return this.processFiles([file]); + }; + + Dropzone.prototype.processFiles = function(files) { + var file, _i, _len; + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + file.processing = true; + file.status = Dropzone.UPLOADING; + this.emit("processing", file); + } + if (this.options.uploadMultiple) { + this.emit("processingmultiple", files); + } + return this.uploadFiles(files); + }; + + Dropzone.prototype._getFilesWithXhr = function(xhr) { + var file, files; + return files = (function() { + var _i, _len, _ref, _results; + _ref = this.files; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if (file.xhr === xhr) { + _results.push(file); + } + } + return _results; + }).call(this); + }; + + Dropzone.prototype.cancelUpload = function(file) { + var groupedFile, groupedFiles, _i, _j, _len, _len1, _ref; + if (file.status === Dropzone.UPLOADING) { + groupedFiles = this._getFilesWithXhr(file.xhr); + for (_i = 0, _len = groupedFiles.length; _i < _len; _i++) { + groupedFile = groupedFiles[_i]; + groupedFile.status = Dropzone.CANCELED; + } + file.xhr.abort(); + for (_j = 0, _len1 = groupedFiles.length; _j < _len1; _j++) { + groupedFile = groupedFiles[_j]; + this.emit("canceled", groupedFile); + } + if (this.options.uploadMultiple) { + this.emit("canceledmultiple", groupedFiles); + } + } else if ((_ref = file.status) === Dropzone.ADDED || _ref === Dropzone.QUEUED) { + file.status = Dropzone.CANCELED; + this.emit("canceled", file); + if (this.options.uploadMultiple) { + this.emit("canceledmultiple", [file]); + } + } + if (this.options.autoProcessQueue) { + return this.processQueue(); + } + }; + + resolveOption = function() { + var args, option; + option = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + if (typeof option === 'function') { + return option.apply(this, args); + } + return option; + }; + + Dropzone.prototype.uploadFile = function(file) { + return this.uploadFiles([file]); + }; + + Dropzone.prototype.uploadFiles = function(files) { + var file, formData, handleError, headerName, headerValue, headers, i, input, inputName, inputType, key, method, option, progressObj, response, updateProgress, url, value, xhr, _i, _j, _k, _l, _len, _len1, _len2, _len3, _m, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; + xhr = new XMLHttpRequest(); + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + file.xhr = xhr; + } + method = resolveOption(this.options.method, files); + url = resolveOption(this.options.url, files); + xhr.open(method, url, true); + xhr.withCredentials = !!this.options.withCredentials; + response = null; + handleError = (function(_this) { + return function() { + var _j, _len1, _results; + _results = []; + for (_j = 0, _len1 = files.length; _j < _len1; _j++) { + file = files[_j]; + _results.push(_this._errorProcessing(files, response || _this.options.dictResponseError.replace("{{statusCode}}", xhr.status), xhr)); + } + return _results; + }; + })(this); + updateProgress = (function(_this) { + return function(e) { + var allFilesFinished, progress, _j, _k, _l, _len1, _len2, _len3, _results; + if (e != null) { + progress = 100 * e.loaded / e.total; + for (_j = 0, _len1 = files.length; _j < _len1; _j++) { + file = files[_j]; + file.upload = { + progress: progress, + total: e.total, + bytesSent: e.loaded + }; + } + } else { + allFilesFinished = true; + progress = 100; + for (_k = 0, _len2 = files.length; _k < _len2; _k++) { + file = files[_k]; + if (!(file.upload.progress === 100 && file.upload.bytesSent === file.upload.total)) { + allFilesFinished = false; + } + file.upload.progress = progress; + file.upload.bytesSent = file.upload.total; + } + if (allFilesFinished) { + return; + } + } + _results = []; + for (_l = 0, _len3 = files.length; _l < _len3; _l++) { + file = files[_l]; + _results.push(_this.emit("uploadprogress", file, progress, file.upload.bytesSent)); + } + return _results; + }; + })(this); + xhr.onload = (function(_this) { + return function(e) { + var _ref; + if (files[0].status === Dropzone.CANCELED) { + return; + } + if (xhr.readyState !== 4) { + return; + } + response = xhr.responseText; + if (xhr.getResponseHeader("content-type") && ~xhr.getResponseHeader("content-type").indexOf("application/json")) { + try { + response = JSON.parse(response); + } catch (_error) { + e = _error; + response = "Invalid JSON response from server."; + } + } + updateProgress(); + if (!((200 <= (_ref = xhr.status) && _ref < 300))) { + return handleError(); + } else { + return _this._finished(files, response, e); + } + }; + })(this); + xhr.onerror = (function(_this) { + return function() { + if (files[0].status === Dropzone.CANCELED) { + return; + } + return handleError(); + }; + })(this); + progressObj = (_ref = xhr.upload) != null ? _ref : xhr; + progressObj.onprogress = updateProgress; + headers = { + "Accept": "application/json", + "Cache-Control": "no-cache", + "X-Requested-With": "XMLHttpRequest" + }; + if (this.options.headers) { + extend(headers, this.options.headers); + } + for (headerName in headers) { + headerValue = headers[headerName]; + if (headerValue) { + xhr.setRequestHeader(headerName, headerValue); + } + } + formData = new FormData(); + if (this.options.params) { + _ref1 = this.options.params; + for (key in _ref1) { + value = _ref1[key]; + formData.append(key, value); + } + } + for (_j = 0, _len1 = files.length; _j < _len1; _j++) { + file = files[_j]; + this.emit("sending", file, xhr, formData); + } + if (this.options.uploadMultiple) { + this.emit("sendingmultiple", files, xhr, formData); + } + if (this.element.tagName === "FORM") { + _ref2 = this.element.querySelectorAll("input, textarea, select, button"); + for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { + input = _ref2[_k]; + inputName = input.getAttribute("name"); + inputType = input.getAttribute("type"); + if (input.tagName === "SELECT" && input.hasAttribute("multiple")) { + _ref3 = input.options; + for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) { + option = _ref3[_l]; + if (option.selected) { + formData.append(inputName, option.value); + } + } + } else if (!inputType || ((_ref4 = inputType.toLowerCase()) !== "checkbox" && _ref4 !== "radio") || input.checked) { + formData.append(inputName, input.value); + } + } + } + for (i = _m = 0, _ref5 = files.length - 1; 0 <= _ref5 ? _m <= _ref5 : _m >= _ref5; i = 0 <= _ref5 ? ++_m : --_m) { + formData.append(this._getParamName(i), files[i], this._renameFilename(files[i].name)); + } + return this.submitRequest(xhr, formData, files); + }; + + Dropzone.prototype.submitRequest = function(xhr, formData, files) { + return xhr.send(formData); + }; + + Dropzone.prototype._finished = function(files, responseText, e) { + var file, _i, _len; + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + file.status = Dropzone.SUCCESS; + this.emit("success", file, responseText, e); + this.emit("complete", file); + } + if (this.options.uploadMultiple) { + this.emit("successmultiple", files, responseText, e); + this.emit("completemultiple", files); + } + if (this.options.autoProcessQueue) { + return this.processQueue(); + } + }; + + Dropzone.prototype._errorProcessing = function(files, message, xhr) { + var file, _i, _len; + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + file.status = Dropzone.ERROR; + this.emit("error", file, message, xhr); + this.emit("complete", file); + } + if (this.options.uploadMultiple) { + this.emit("errormultiple", files, message, xhr); + this.emit("completemultiple", files); + } + if (this.options.autoProcessQueue) { + return this.processQueue(); + } + }; + + return Dropzone; + + })(Emitter); + + Dropzone.version = "4.3.0"; + + Dropzone.options = {}; + + Dropzone.optionsForElement = function(element) { + if (element.getAttribute("id")) { + return Dropzone.options[camelize(element.getAttribute("id"))]; + } else { + return void 0; + } + }; + + Dropzone.instances = []; + + Dropzone.forElement = function(element) { + if (typeof element === "string") { + element = document.querySelector(element); + } + if ((element != null ? element.dropzone : void 0) == null) { + throw new Error("No Dropzone found for given element. This is probably because you're trying to access it before Dropzone had the time to initialize. Use the `init` option to setup any additional observers on your Dropzone."); + } + return element.dropzone; + }; + + Dropzone.autoDiscover = true; + + Dropzone.discover = function() { + var checkElements, dropzone, dropzones, _i, _len, _results; + if (document.querySelectorAll) { + dropzones = document.querySelectorAll(".dropzone"); + } else { + dropzones = []; + checkElements = function(elements) { + var el, _i, _len, _results; + _results = []; + for (_i = 0, _len = elements.length; _i < _len; _i++) { + el = elements[_i]; + if (/(^| )dropzone($| )/.test(el.className)) { + _results.push(dropzones.push(el)); + } else { + _results.push(void 0); + } + } + return _results; + }; + checkElements(document.getElementsByTagName("div")); + checkElements(document.getElementsByTagName("form")); + } + _results = []; + for (_i = 0, _len = dropzones.length; _i < _len; _i++) { + dropzone = dropzones[_i]; + if (Dropzone.optionsForElement(dropzone) !== false) { + _results.push(new Dropzone(dropzone)); + } else { + _results.push(void 0); + } + } + return _results; + }; + + Dropzone.blacklistedBrowsers = [/opera.*Macintosh.*version\/12/i]; + + Dropzone.isBrowserSupported = function() { + var capableBrowser, regex, _i, _len, _ref; + capableBrowser = true; + if (window.File && window.FileReader && window.FileList && window.Blob && window.FormData && document.querySelector) { + if (!("classList" in document.createElement("a"))) { + capableBrowser = false; + } else { + _ref = Dropzone.blacklistedBrowsers; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + regex = _ref[_i]; + if (regex.test(navigator.userAgent)) { + capableBrowser = false; + continue; + } + } + } + } else { + capableBrowser = false; + } + return capableBrowser; + }; + + without = function(list, rejectedItem) { + var item, _i, _len, _results; + _results = []; + for (_i = 0, _len = list.length; _i < _len; _i++) { + item = list[_i]; + if (item !== rejectedItem) { + _results.push(item); + } + } + return _results; + }; + + camelize = function(str) { + return str.replace(/[\-_](\w)/g, function(match) { + return match.charAt(1).toUpperCase(); + }); + }; + + Dropzone.createElement = function(string) { + var div; + div = document.createElement("div"); + div.innerHTML = string; + return div.childNodes[0]; + }; + + Dropzone.elementInside = function(element, container) { + if (element === container) { + return true; + } + while (element = element.parentNode) { + if (element === container) { + return true; + } + } + return false; + }; + + Dropzone.getElement = function(el, name) { + var element; + if (typeof el === "string") { + element = document.querySelector(el); + } else if (el.nodeType != null) { + element = el; + } + if (element == null) { + throw new Error("Invalid `" + name + "` option provided. Please provide a CSS selector or a plain HTML element."); + } + return element; + }; + + Dropzone.getElements = function(els, name) { + var e, el, elements, _i, _j, _len, _len1, _ref; + if (els instanceof Array) { + elements = []; + try { + for (_i = 0, _len = els.length; _i < _len; _i++) { + el = els[_i]; + elements.push(this.getElement(el, name)); + } + } catch (_error) { + e = _error; + elements = null; + } + } else if (typeof els === "string") { + elements = []; + _ref = document.querySelectorAll(els); + for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { + el = _ref[_j]; + elements.push(el); + } + } else if (els.nodeType != null) { + elements = [els]; + } + if (!((elements != null) && elements.length)) { + throw new Error("Invalid `" + name + "` option provided. Please provide a CSS selector, a plain HTML element or a list of those."); + } + return elements; + }; + + Dropzone.confirm = function(question, accepted, rejected) { + if (window.confirm(question)) { + return accepted(); + } else if (rejected != null) { + return rejected(); + } + }; + + Dropzone.isValidFile = function(file, acceptedFiles) { + var baseMimeType, mimeType, validType, _i, _len; + if (!acceptedFiles) { + return true; + } + acceptedFiles = acceptedFiles.split(","); + mimeType = file.type; + baseMimeType = mimeType.replace(/\/.*$/, ""); + for (_i = 0, _len = acceptedFiles.length; _i < _len; _i++) { + validType = acceptedFiles[_i]; + validType = validType.trim(); + if (validType.charAt(0) === ".") { + if (file.name.toLowerCase().indexOf(validType.toLowerCase(), file.name.length - validType.length) !== -1) { + return true; + } + } else if (/\/\*$/.test(validType)) { + if (baseMimeType === validType.replace(/\/.*$/, "")) { + return true; + } + } else { + if (mimeType === validType) { + return true; + } + } + } + return false; + }; + + if (typeof jQuery !== "undefined" && jQuery !== null) { + jQuery.fn.dropzone = function(options) { + return this.each(function() { + return new Dropzone(this, options); + }); + }; + } + + if (typeof module !== "undefined" && module !== null) { + module.exports = Dropzone; + } else { + window.Dropzone = Dropzone; + } + + Dropzone.ADDED = "added"; + + Dropzone.QUEUED = "queued"; + + Dropzone.ACCEPTED = Dropzone.QUEUED; + + Dropzone.UPLOADING = "uploading"; + + Dropzone.PROCESSING = Dropzone.UPLOADING; + + Dropzone.CANCELED = "canceled"; + + Dropzone.ERROR = "error"; + + Dropzone.SUCCESS = "success"; + + + /* + + Bugfix for iOS 6 and 7 + Source: http://stackoverflow.com/questions/11929099/html5-canvas-drawimage-ratio-bug-ios + based on the work of https://github.com/stomita/ios-imagefile-megapixel + */ + + detectVerticalSquash = function(img) { + var alpha, canvas, ctx, data, ey, ih, iw, py, ratio, sy; + iw = img.naturalWidth; + ih = img.naturalHeight; + canvas = document.createElement("canvas"); + canvas.width = 1; + canvas.height = ih; + ctx = canvas.getContext("2d"); + ctx.drawImage(img, 0, 0); + data = ctx.getImageData(0, 0, 1, ih).data; + sy = 0; + ey = ih; + py = ih; + while (py > sy) { + alpha = data[(py - 1) * 4 + 3]; + if (alpha === 0) { + ey = py; + } else { + sy = py; + } + py = (ey + sy) >> 1; + } + ratio = py / ih; + if (ratio === 0) { + return 1; + } else { + return ratio; + } + }; + + drawImageIOSFix = function(ctx, img, sx, sy, sw, sh, dx, dy, dw, dh) { + var vertSquashRatio; + vertSquashRatio = detectVerticalSquash(img); + return ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh / vertSquashRatio); + }; + + + /* + * contentloaded.js + * + * Author: Diego Perini (diego.perini at gmail.com) + * Summary: cross-browser wrapper for DOMContentLoaded + * Updated: 20101020 + * License: MIT + * Version: 1.2 + * + * URL: + * http://javascript.nwbox.com/ContentLoaded/ + * http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE + */ + + contentLoaded = function(win, fn) { + var add, doc, done, init, poll, pre, rem, root, top; + done = false; + top = true; + doc = win.document; + root = doc.documentElement; + add = (doc.addEventListener ? "addEventListener" : "attachEvent"); + rem = (doc.addEventListener ? "removeEventListener" : "detachEvent"); + pre = (doc.addEventListener ? "" : "on"); + init = function(e) { + if (e.type === "readystatechange" && doc.readyState !== "complete") { + return; + } + (e.type === "load" ? win : doc)[rem](pre + e.type, init, false); + if (!done && (done = true)) { + return fn.call(win, e.type || e); + } + }; + poll = function() { + var e; + try { + root.doScroll("left"); + } catch (_error) { + e = _error; + setTimeout(poll, 50); + return; + } + return init("poll"); + }; + if (doc.readyState !== "complete") { + if (doc.createEventObject && root.doScroll) { + try { + top = !win.frameElement; + } catch (_error) {} + if (top) { + poll(); + } + } + doc[add](pre + "DOMContentLoaded", init, false); + doc[add](pre + "readystatechange", init, false); + return win[add](pre + "load", init, false); + } + }; + + Dropzone._autoDiscoverFunction = function() { + if (Dropzone.autoDiscover) { + return Dropzone.discover(); + } + }; + + contentLoaded(window, Dropzone._autoDiscoverFunction); + +}).call(this); diff --git a/web/pgadmin/utils/__init__.py b/web/pgadmin/utils/__init__.py index 010750939..c30d5bb60 100644 --- a/web/pgadmin/utils/__init__.py +++ b/web/pgadmin/utils/__init__.py @@ -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): diff --git a/web/pgadmin/utils/paths.py b/web/pgadmin/utils/paths.py new file mode 100644 index 000000000..19c88a2f1 --- /dev/null +++ b/web/pgadmin/utils/paths.py @@ -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!' + )