mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Added support to download utility files at the client-side. Fixes #3318
This commit is contained in:
committed by
Akshay Joshi
parent
7573fac29f
commit
c2ad97d0ab
@@ -22,7 +22,7 @@ from subprocess import Popen, PIPE
|
||||
import logging
|
||||
|
||||
from pgadmin.utils import u_encode, file_quote, fs_encoding, \
|
||||
get_complete_file_path
|
||||
get_complete_file_path, get_storage_directory, IS_WIN
|
||||
|
||||
import pytz
|
||||
from dateutil import parser
|
||||
@@ -59,6 +59,48 @@ class IProcessDesc(object, metaclass=ABCMeta):
|
||||
def details(self, cmd, args):
|
||||
pass
|
||||
|
||||
@property
|
||||
def current_storage_dir(self):
|
||||
|
||||
if config.SERVER_MODE:
|
||||
|
||||
file = self.bfile
|
||||
try:
|
||||
# check if file name is encoded with UTF-8
|
||||
file = self.bfile.decode('utf-8')
|
||||
except Exception as e:
|
||||
str(e)
|
||||
# do nothing if bfile is not encoded.
|
||||
|
||||
path = get_complete_file_path(file)
|
||||
path = file if path is None else path
|
||||
|
||||
if IS_WIN:
|
||||
path = os.path.realpath(path)
|
||||
|
||||
storage_directory = os.path.basename(get_storage_directory())
|
||||
|
||||
if storage_directory in path:
|
||||
start = path.index(storage_directory)
|
||||
end = start + (len(storage_directory))
|
||||
last_dir = os.path.dirname(path[end:])
|
||||
else:
|
||||
last_dir = file
|
||||
|
||||
if IS_WIN:
|
||||
if '\\' in last_dir:
|
||||
if len(last_dir) == 1:
|
||||
last_dir = last_dir.replace('\\', '\\\\')
|
||||
else:
|
||||
last_dir = last_dir.replace('\\', '/')
|
||||
else:
|
||||
last_dir = last_dir.replace('\\', '/')
|
||||
|
||||
return None if hasattr(self, 'is_import') and self.is_import \
|
||||
else last_dir
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class BatchProcess(object):
|
||||
def __init__(self, **kwargs):
|
||||
@@ -543,8 +585,12 @@ class BatchProcess(object):
|
||||
desc = loads(p.desc)
|
||||
details = desc
|
||||
type_desc = ''
|
||||
current_storage_dir = None
|
||||
|
||||
if isinstance(desc, IProcessDesc):
|
||||
|
||||
from pgadmin.tools.backup import BackupMessage
|
||||
from pgadmin.tools.import_export import IEMessage
|
||||
args = []
|
||||
args_csv = StringIO(
|
||||
p.arguments.encode('utf-8')
|
||||
@@ -555,9 +601,11 @@ class BatchProcess(object):
|
||||
args = args + arg
|
||||
details = desc.details(p.command, args)
|
||||
type_desc = desc.type_desc
|
||||
if isinstance(desc, (BackupMessage, IEMessage)):
|
||||
current_storage_dir = desc.current_storage_dir
|
||||
desc = desc.message
|
||||
|
||||
return desc, details, type_desc
|
||||
return desc, details, type_desc, current_storage_dir
|
||||
|
||||
@staticmethod
|
||||
def list():
|
||||
@@ -584,7 +632,8 @@ class BatchProcess(object):
|
||||
|
||||
execution_time = BatchProcess.total_seconds(etime - stime)
|
||||
|
||||
desc, details, type_desc = BatchProcess._check_process_desc(p)
|
||||
desc, details, type_desc, current_storage_dir = BatchProcess.\
|
||||
_check_process_desc(p)
|
||||
|
||||
res.append({
|
||||
'id': p.pid,
|
||||
@@ -596,7 +645,8 @@ class BatchProcess(object):
|
||||
'exit_code': p.exit_code,
|
||||
'acknowledge': p.acknowledge,
|
||||
'execution_time': execution_time,
|
||||
'process_state': p.process_state
|
||||
'process_state': p.process_state,
|
||||
'current_storage_dir': current_storage_dir,
|
||||
})
|
||||
|
||||
if changed:
|
||||
|
@@ -57,3 +57,7 @@ ol.pg-bg-process-logs {
|
||||
.pg-bg-bgprocess:hover .bg-close {
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.icon-storage-manager:before {
|
||||
font-icon: url('../img/storage_manager.svg');
|
||||
}
|
||||
|
13
web/pgadmin/misc/bgprocess/static/img/storage_manager.svg
Normal file
13
web/pgadmin/misc/bgprocess/static/img/storage_manager.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M4.3,5.3h8.8V4c0-0.7-0.6-1.3-1.3-1.3H7.5L5.8,1H1.5C0.8,1,0.2,1.6,0.2,2.3v7.4L2,6.5C2.6,5.8,3.4,5.3,4.3,5.3z"/>
|
||||
<path d="M7.5,8.9c0-1.2,0.7-2.3,1.6-2.8H4.3C3.7,6.1,3.1,6.4,2.8,7l-1.9,3.3c-0.2,0.4,0.1,1,0.6,1h7C7.8,10.7,7.5,9.8,7.5,8.9z"/>
|
||||
<path d="M15,6.1h-2.6c1,0.6,1.6,1.6,1.6,2.8c0,0.3-0.1,0.6-0.1,0.9L15.5,7C15.8,6.7,15.5,6.1,15,6.1z"/>
|
||||
<circle cx="10.8" cy="8.9" r="2.8"/>
|
||||
</g>
|
||||
<path d="M14,11.8l-1.2-0.3c-1.3,0.9-2.9,0.8-3.8,0l-1.2,0.3C7,12,6.5,12.7,6.5,13.5v0.7c0,0.4,0.3,0.9,0.9,0.9h2.3l1.1-2.4l1.3,2.4
|
||||
h2.4c0.4,0,0.9-0.3,0.9-0.9v-0.8C15.2,12.6,14.8,11.9,14,11.8z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 944 B |
@@ -20,6 +20,8 @@ define('misc.bgprocess', [
|
||||
return pgBrowser.BackgroundProcessObsorver;
|
||||
}
|
||||
|
||||
var isServerMode = (function() { return pgAdmin.server_mode == 'True'; })();
|
||||
|
||||
var wcDocker = window.wcDocker;
|
||||
|
||||
var BGProcess = function(info, notify) {
|
||||
@@ -61,6 +63,7 @@ define('misc.bgprocess', [
|
||||
curr_status: null,
|
||||
state: 0, // 0: NOT Started, 1: Started, 2: Finished, 3: Terminated
|
||||
completed: false,
|
||||
current_storage_dir: null,
|
||||
|
||||
id: info['id'],
|
||||
type_desc: null,
|
||||
@@ -161,6 +164,9 @@ define('misc.bgprocess', [
|
||||
if ('process_state' in data)
|
||||
self.state = data.process_state;
|
||||
|
||||
if ('current_storage_dir' in data)
|
||||
self.current_storage_dir = data.current_storage_dir;
|
||||
|
||||
if ('out' in data) {
|
||||
self.out = data.out && data.out.pos;
|
||||
|
||||
@@ -325,8 +331,8 @@ define('misc.bgprocess', [
|
||||
</div>
|
||||
<div class="pg-bg-etime my-auto mr-2"></div>
|
||||
<div class="ml-auto">
|
||||
<button class="btn btn-secondary pg-bg-more-details"><span class="fa fa-info-circle" role="img"></span> ` + gettext('More details...') + `</button>
|
||||
<button class="btn btn-danger bg-process-stop" disabled><span class="fa fa-times-circle" role="img"></span> ` + gettext('Stop Process') + `</button>
|
||||
<button class="btn btn-secondary pg-bg-more-details" title="More Details"><span class="fa fa-info-circle" role="img"></span> ` + gettext('More details...') + `</button>
|
||||
<button class="btn btn-danger bg-process-stop" disabled><span class="fa fa-times-circle" role="img" title="Stop the operation"></span> ` + gettext('Stop Process') + `</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pg-bg-status py-1">
|
||||
@@ -387,6 +393,7 @@ define('misc.bgprocess', [
|
||||
var $status_bar = $(self.container.find('.pg-bg-status'));
|
||||
$status_bar.html(self.curr_status);
|
||||
var $btn_stop_process = $(self.container.find('.bg-process-stop'));
|
||||
|
||||
// Enable Stop Process button only when process is running
|
||||
if (parseInt(self.state) === 1) {
|
||||
$btn_stop_process.attr('disabled', false);
|
||||
@@ -415,7 +422,27 @@ define('misc.bgprocess', [
|
||||
$logs = container.find('.bg-process-watcher'),
|
||||
$header = container.find('.bg-process-details'),
|
||||
$footer = container.find('.bg-process-footer'),
|
||||
$btn_stop_process = container.find('.bg-process-stop');
|
||||
$btn_stop_process = container.find('.bg-process-stop'),
|
||||
$btn_storage_manager = container.find('.bg-process-storage-manager');
|
||||
|
||||
if(self.current_storage_dir && isServerMode) { //for backup & exports with server mode, operate over storage manager
|
||||
|
||||
if($btn_storage_manager.length == 0) {
|
||||
var str_storage_manager_btn = '<button id="bg-process-storage-manager" class="btn btn-secondary bg-process-storage-manager" title="Click to open file location" aria-label="Storage Manager" tabindex="0" disabled><span class="pg-font-icon icon-storage-manager" role="img"></span></button> ';
|
||||
container.find('.bg-process-details .bg-btn-section').prepend(str_storage_manager_btn);
|
||||
$btn_storage_manager = container.find('.bg-process-storage-manager');
|
||||
}
|
||||
|
||||
// Disable storage manager button only when process is running
|
||||
if (parseInt(self.state) === 1) {
|
||||
$btn_storage_manager.attr('disabled', true);
|
||||
}
|
||||
else {
|
||||
$btn_storage_manager.attr('disabled', false);
|
||||
}
|
||||
// On Click event for storage manager button.
|
||||
$btn_storage_manager.off('click').on('click', self.storage_manager.bind(this));
|
||||
}
|
||||
|
||||
// Enable Stop Process button only when process is running
|
||||
if (parseInt(self.state) === 1) {
|
||||
@@ -526,6 +553,15 @@ define('misc.bgprocess', [
|
||||
});
|
||||
},
|
||||
|
||||
storage_manager: function() {
|
||||
|
||||
var self = this;
|
||||
if(self.current_storage_dir) {
|
||||
pgBrowser.Events.trigger(
|
||||
'pgadmin:tools:storage_manager', self.current_storage_dir
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
_.extend(
|
||||
@@ -634,7 +670,7 @@ define('misc.bgprocess', [
|
||||
'<span>' + gettext('Start time') + ': <span class="bgprocess-start-time"></span>' +
|
||||
'</span>'+
|
||||
'</div>' +
|
||||
'<div class="ml-auto">' +
|
||||
'<div class="ml-auto bg-btn-section">' +
|
||||
'<button type="button" class="btn btn-danger bg-process-stop" disabled><span class="fa fa-times-circle" role="img"></span> ' + gettext('Stop Process') + '</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
|
@@ -23,7 +23,7 @@ from werkzeug.exceptions import InternalServerError
|
||||
|
||||
import simplejson as json
|
||||
from flask import render_template, Response, session, request as req, \
|
||||
url_for, current_app
|
||||
url_for, current_app, send_from_directory
|
||||
from flask_babelex import gettext
|
||||
from flask_security import login_required
|
||||
from pgadmin.utils import PgAdminModule
|
||||
@@ -1214,11 +1214,16 @@ class Filemanager(object):
|
||||
'attachment; filename=' + name
|
||||
return resp
|
||||
|
||||
name = path.split('/')[-1]
|
||||
content = open(orig_path, 'rb')
|
||||
resp = Response(content)
|
||||
resp.headers['Content-Disposition'] = 'attachment; filename=' + name
|
||||
return resp
|
||||
name = os.path.basename(path)
|
||||
if orig_path and len(orig_path) > 0:
|
||||
dir_path = os.path.dirname(orig_path)
|
||||
else:
|
||||
dir_path = os.path.dirname(path)
|
||||
|
||||
response = send_from_directory(dir_path, name, as_attachment=True)
|
||||
response.headers["filename"] = name
|
||||
|
||||
return response
|
||||
|
||||
def permission(self, path=None, req=None):
|
||||
the_dir = self.dir if self.dir is not None else ''
|
||||
|
@@ -9,6 +9,7 @@
|
||||
|
||||
import './select_dialogue';
|
||||
import './create_dialogue';
|
||||
import './storage_dialogue';
|
||||
|
||||
define('misc.file_manager', [
|
||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
|
||||
@@ -41,7 +42,10 @@ define('misc.file_manager', [
|
||||
let dialogHeight = pgAdmin.Browser.stdH.calc(pgAdmin.Browser.stdH.lg);
|
||||
if (params.dialog_type == 'create_file') {
|
||||
Alertify.createModeDlg(params).resizeTo(dialogWidth, dialogHeight);
|
||||
} else {
|
||||
} else if(params.dialog_type == 'storage_dialog') {
|
||||
Alertify.fileStorageDlg(params).resizeTo(dialogWidth, dialogHeight);
|
||||
}
|
||||
else {
|
||||
Alertify.fileSelectionDlg(params).resizeTo(dialogWidth, dialogHeight);
|
||||
}
|
||||
},
|
||||
|
@@ -77,7 +77,8 @@ module.exports = Alertify.dialog('fileSelectionDlg', function() {
|
||||
$($(self.elements.footer).find('.file_manager_ok')).trigger('click');
|
||||
});
|
||||
}, 200);
|
||||
self.__internal.buttons[1].element.disabled = true;
|
||||
if(self.__internal.buttons[1])
|
||||
self.__internal.buttons[1].element.disabled = true;
|
||||
},
|
||||
setup: function() {
|
||||
return {
|
||||
|
45
web/pgadmin/misc/file_manager/static/js/storage_dialogue.js
Normal file
45
web/pgadmin/misc/file_manager/static/js/storage_dialogue.js
Normal file
@@ -0,0 +1,45 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2020, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import gettext from 'sources/gettext';
|
||||
import Alertify from 'pgadmin.alertifyjs';
|
||||
|
||||
// Declare the Storage dialog
|
||||
module.exports = Alertify.dialog('fileStorageDlg', function() {
|
||||
// Dialog property
|
||||
return {
|
||||
settingUpdated: function(key, oldValue, newValue) {
|
||||
if(key == 'message') {
|
||||
this.setMessage(newValue);
|
||||
}
|
||||
},
|
||||
setup: function() {
|
||||
return {
|
||||
buttons: [{
|
||||
text: gettext('Cancel'),
|
||||
key: 27,
|
||||
className: 'btn btn-secondary fa fa-times pg-alertify-button',
|
||||
}],
|
||||
options: {
|
||||
closableByDimmer: false,
|
||||
maximizable: false,
|
||||
closable: false,
|
||||
movable: true,
|
||||
padding: !1,
|
||||
overflow: !1,
|
||||
model: 0,
|
||||
resizable: true,
|
||||
pinnable: false,
|
||||
modal: false,
|
||||
autoReset: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
}, true, 'fileSelectionDlg');
|
@@ -23,6 +23,8 @@ define([
|
||||
'sources/csrf', 'tablesorter', 'tablesorter-metric',
|
||||
], function($, _, Alertify, gettext, url_for, Dropzone, pgAdmin, csrf) {
|
||||
|
||||
pgAdmin.Browser = pgAdmin.Browser || {};
|
||||
|
||||
/*---------------------------------------------------------
|
||||
Define functions used for various operations
|
||||
---------------------------------------------------------*/
|
||||
@@ -179,14 +181,17 @@ define([
|
||||
$('.file_manager').find('button.download').hide();
|
||||
} else {
|
||||
$('.file_manager').find('button.download').off().on('click', function() {
|
||||
var path;
|
||||
var path,
|
||||
params = {};
|
||||
|
||||
params[pgAdmin.csrf_token_header] = pgAdmin.csrf_token;
|
||||
|
||||
if ($('.fileinfo').data('view') == 'grid') {
|
||||
path = $('.fileinfo li.selected').find('.clip span').attr('data-alt');
|
||||
window.open(pgAdmin.FileUtils.fileConnector + '?_=' + Date.now() + 'mode=download&path=' + path, '_blank');
|
||||
} else {
|
||||
path = $('.fileinfo').find('table#contents tbody tr.selected td:first-child').attr('title');
|
||||
window.open(pgAdmin.FileUtils.fileConnector + '?_=' + Date.now() + 'mode=download&path=' + path, '_blank');
|
||||
}
|
||||
download_file(path);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1030,12 +1035,15 @@ define([
|
||||
|
||||
$('.file_manager_ok').removeClass('disabled');
|
||||
$('.file_manager_ok').attr('disabled', false);
|
||||
$('.file_manager button.delete, .file_manager button.rename').removeAttr(
|
||||
$('.file_manager button.delete').removeAttr(
|
||||
'disabled', 'disabled'
|
||||
);
|
||||
$('.file_manager button.download').attr(
|
||||
'disabled', 'disabled'
|
||||
);
|
||||
$('.file_manager button.rename').attr(
|
||||
'disabled', 'disabled'
|
||||
);
|
||||
// set selected folder name in breadcrums
|
||||
$('.file_manager #uploader .input-path').hide();
|
||||
$('.file_manager #uploader .show_selected_file').remove();
|
||||
@@ -1078,7 +1086,8 @@ define([
|
||||
$('.file_manager_ok').removeClass('disabled');
|
||||
$('.file_manager_ok').attr('disabled', false);
|
||||
$('.file_manager button.download').attr('disabled', 'disabled');
|
||||
$('.file_manager button.delete, .file_manager button.rename').removeAttr('disabled');
|
||||
$('.file_manager button.rename').attr('disabled', 'disabled');
|
||||
$('.file_manager button.delete').removeAttr('disabled');
|
||||
|
||||
// set selected folder name in breadcrums
|
||||
$('.file_manager #uploader .input-path').hide();
|
||||
@@ -1168,6 +1177,60 @@ define([
|
||||
is_protected == undefined;
|
||||
};
|
||||
|
||||
// Download selected file
|
||||
var download_file = function (path) {
|
||||
|
||||
var data = { 'path': path, 'mode': 'download' },
|
||||
params = {};
|
||||
|
||||
params[pgAdmin.csrf_token_header] = pgAdmin.csrf_token;
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: pgAdmin.FileUtils.fileConnector,
|
||||
contentType: false,
|
||||
headers: params,
|
||||
xhrFields: {
|
||||
responseType: 'blob',
|
||||
},
|
||||
cache: false,
|
||||
data: JSON.stringify(data),
|
||||
success: function (blob, status, xhr) {
|
||||
// check for a filename
|
||||
var filename = xhr.getResponseHeader('filename');
|
||||
|
||||
if (typeof window.navigator.msSaveBlob !== 'undefined') {
|
||||
// IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
|
||||
window.navigator.msSaveBlob(blob, filename);
|
||||
} else {
|
||||
var URL = window.URL || window.webkitURL;
|
||||
var downloadUrl = URL.createObjectURL(blob);
|
||||
|
||||
if (filename) {
|
||||
// use HTML5 a[download] attribute to specify filename
|
||||
var a = document.createElement('a');
|
||||
// safari doesn't support this yet
|
||||
if (typeof a.download === 'undefined') {
|
||||
window.location.href = downloadUrl;
|
||||
} else {
|
||||
a.href = downloadUrl;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
}
|
||||
} else {
|
||||
window.location.href = downloadUrl;
|
||||
}
|
||||
|
||||
setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
|
||||
}
|
||||
},
|
||||
error: function (error) {
|
||||
Alertify.error(error);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/*---------------------------------------------------------
|
||||
Initialization - Entry point
|
||||
---------------------------------------------------------*/
|
||||
@@ -1446,12 +1509,13 @@ define([
|
||||
) {
|
||||
$('.file_manager_ok').removeClass('disabled');
|
||||
$('.file_manager_ok').attr('disabled', false);
|
||||
$('.file_manager button.delete, .file_manager button.rename').removeAttr(
|
||||
$('.file_manager button.delete').removeAttr(
|
||||
'disabled', 'disabled'
|
||||
);
|
||||
$('.file_manager button.download').attr(
|
||||
'disabled', 'disabled'
|
||||
);
|
||||
$('.file_manager button.rename').attr('disabled', 'disabled');
|
||||
// set selected folder name in breadcrums
|
||||
$('.file_manager #uploader .input-path').hide();
|
||||
$('.file_manager #uploader .show_selected_file').remove();
|
||||
|
Reference in New Issue
Block a user