Fixed upload streams

Lukas added a lot of pretty things to the upload waiting screen.
Current bug: progress bar is not 100% working.
This commit is contained in:
Andrew 2021-08-22 16:31:49 -04:00
parent 734a576cb9
commit 86c1b374bf
5 changed files with 200 additions and 133 deletions

View File

@ -578,7 +578,16 @@ class Helpers:
return output
@staticmethod
def in_path(x, y):
def in_path(parent_path, child_path):
# Smooth out relative path names, note: if you are concerned about symbolic links, you should use os.path.realpath too
parent_path = os.path.abspath(parent_path)
child_path = os.path.abspath(child_path)
# Compare the common path of the parent and child path with the common path of just the parent path. Using the commonpath method on just the parent path will regularise the path name in the same way as the comparison that deals with both paths, removing any trailing path separator
return os.path.commonpath([parent_path]) == os.path.commonpath([parent_path, child_path])
@staticmethod
def in_path_old(x, y):
return os.path.abspath(y).__contains__(os.path.abspath(x))
@staticmethod

View File

@ -28,7 +28,6 @@ try:
from app.classes.web.static_handler import CustomStaticHandler
from app.classes.shared.translation import translation
from app.classes.web.upload_handler import UploadHandler
from app.classes.web.upload_handler import ProxyHandler
except ModuleNotFoundError as e:
logger.critical("Import Error: Unable to load {} module".format(e, e.name))
@ -131,8 +130,7 @@ class Webserver:
(r'/api/stats/servers', ServersStats, handler_args),
(r'/api/stats/node', NodeStats, handler_args),
(r'/ws', SocketHandler, handler_args),
(r'/upload', UploadHandler, handler_args),
(r'/proxy', ProxyHandler, handler_args)
(r'/upload', UploadHandler),
]
app = tornado.web.Application(

View File

@ -1,88 +1,79 @@
from tornado.concurrent import Future
from tornado.escape import utf8
from tornado import gen
from tornado.httpclient import AsyncHTTPClient
from tornado.ioloop import IOLoop
from tornado.options import parse_command_line, define, options
from tornado.web import Application, RequestHandler, stream_request_body
import tornado.options
import tornado.web
import tornado.httpserver
from tornado.options import options
from app.classes.shared.models import db_helper, Enum_Permissions
from app.classes.shared.helpers import helper
from app.classes.web.websocket_helper import websocket_helper
from app.classes.shared.console import console
import logging
import toro
import os
import json
import time
logger = logging.getLogger(__name__)
# Class&Function Defination
MAX_STREAMED_SIZE = 1024 * 1024 * 1024
define('server_delay', default=2.0)
define('client_delay', default=1.0)
define('num_chunks', default=40)
@stream_request_body
class UploadHandler(RequestHandler):
@tornado.web.stream_request_body
class UploadHandler(tornado.web.RequestHandler):
def prepare(self):
print("In PREPARE")
logger.info('UploadHandler.prepare')
self.do_upload = True
user_data = json.loads(self.get_secure_cookie('user_data'))
user_id = user_data['user_id']
@gen.coroutine
def data_received(self, chunk):
print("In RECIEVED")
logger.info('UploadHandler.data_received(%d bytes: %r)',
len(chunk), chunk[:9])
yield gen.Task(IOLoop.current().call_later, options.server_delay)
server_id = self.request.headers.get('X-ServerId', None)
def put(self):
print("In PUT")
logger.info('UploadHandler.put')
self.write('ok')
if user_id is None:
logger.warning('User ID not found in upload handler call')
console.warning('User ID not found in upload handler call')
self.do_upload = False
if server_id is None:
logger.warning('Server ID not found in upload handler call')
console.warning('Server ID not found in upload handler call')
self.do_upload = False
@stream_request_body
class ProxyHandler(RequestHandler):
def prepare(self):
logger.info('ProxyHandler.prepare')
self.chunks = toro.Queue(1)
self.fetch_future = AsyncHTTPClient().fetch(
'http://localhost:%d/upload' % options.port,
method='PUT',
body_producer=self.body_producer,
request_timeout=3600.0)
user_permissions = db_helper.get_user_permissions_list(user_id, server_id)
if Enum_Permissions.Files not in user_permissions:
logger.warning(f'User {user_id} tried to upload a file to {server_id} without permissions!')
console.warning(f'User {user_id} tried to upload a file to {server_id} without permissions!')
self.do_upload = False
@gen.coroutine
def body_producer(self, write):
while True:
chunk = yield self.chunks.get()
if chunk is None:
return
yield write(chunk)
path = self.request.headers.get('X-Path', None)
filename = self.request.headers.get('X-FileName', None)
full_path = os.path.join(path, filename)
@gen.coroutine
def data_received(self, chunk):
logger.info('ProxyHandler.data_received(%d bytes: %r)',
len(chunk), chunk[:9])
yield self.chunks.put(chunk)
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], full_path):
print(user_id, server_id, db_helper.get_server_data_by_id(server_id)['path'], full_path)
logger.warning(f'User {user_id} tried to upload a file to {server_id} but the path is not inside of the server!')
console.warning(f'User {user_id} tried to upload a file to {server_id} but the path is not inside of the server!')
self.do_upload = False
if self.do_upload:
try:
self.f = open(full_path, "wb")
except Exception as e:
logger.error("Upload failed with error: {}".format(e))
self.do_upload = False
# If max_body_size is not set, you cannot upload files > 100MB
self.request.connection.set_max_body_size(MAX_STREAMED_SIZE)
@gen.coroutine
def put(self):
logger.info('ProxyHandler.put')
# Write None to the chunk queue to signal body_producer to exit,
# then wait for the request to finish.
yield self.chunks.put(None)
response = yield self.fetch_future
self.set_status(response.code)
self.write(response.body)
def post(self):
logger.info("Upload completed")
if self.do_upload:
time.sleep(5)
websocket_helper.broadcast('close_upload_box', 'success')
self.finish('success') # Nope, I'm sending "success"
self.f.close()
else:
time.sleep(5)
websocket_helper.broadcast('close_upload_box', 'error')
self.finish('error')
@gen.coroutine
def client():
@gen.coroutine
def body_producer(write):
for i in range(options.num_chunks):
yield gen.Task(IOLoop.current().call_later, options.client_delay)
chunk = ('chunk %02d ' % i) * 10000
logger.info('client writing %d bytes: %r', len(chunk), chunk[:9])
yield write(utf8(chunk))
response = yield AsyncHTTPClient().fetch(
'http://localhost:%d/proxy' % options.port,
method='PUT',
body_producer=body_producer,
request_timeout=3600.0)
logger.info('client finished with response %d: %r',
response.code, response.body)
def data_received(self, data):
if self.do_upload:
self.f.write(data)

View File

@ -321,6 +321,10 @@
regex: /^properties$/,
replaceWith: 'ace/mode/properties'
},
{
regex: /^log$/,
replaceWith: 'ace/mode/txt'
},
];
var filePath = '';
@ -520,52 +524,115 @@
});
window.location.href = "/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=files"
}
function uploadFilesE(event){
function sendFile(file, path, server_id, onProgress){
var xmlHttpRequest = new XMLHttpRequest();
var token = getCookie("_xsrf")
var fileName = file.name
var target = '/upload?server_id=' + server_id
var mimeType = file.type
xmlHttpRequest.open('POST', target, true);
xmlHttpRequest.setRequestHeader('X-Content-Type', mimeType);
xmlHttpRequest.setRequestHeader('X-XSRFToken', token);
xmlHttpRequest.setRequestHeader('X-Content-Disposition', 'attachment; filename="' + fileName + '"');
xmlHttpRequest.setRequestHeader('X-Path', path);
xmlHttpRequest.setRequestHeader('X-FileName', fileName);
xmlHttpRequest.setRequestHeader('X-ServerId', "{{ data['server_stats']['server_id']['server_id'] }}");
xmlHttpRequest.upload.addEventListener('progress', (event) =>
onProgress(Math.floor(event.loaded / event.total * 100)), false);
xmlHttpRequest.addEventListener('load', (event) => {
if (event.target.responseText == 'success') {
console.log('Upload for file', file.name, 'was successful!')
}
else {
alert('Upload failed with response: ' + event.target.responseText);
doUpload = false;
}
}, false);
xmlHttpRequest.addEventListener('error', (e) => {
console.error('Error while uploading file', file.name + '.', 'Event:', e)
}, false);
xmlHttpRequest.send(file);
}
var uploadWaitDialog;
var doUpload = true;
function uploadFilesE(event){
path = event.target.parentElement.getAttribute('data-path');
console.log("PATH: " + path);
$(function () {
server_id = {{ data['server_stats']['server_id']['server_id'] }};
var uploadHtml = "<div>" +
'<form id="upload_file" enctype="multipart/form-data">'+"<label class='upload-area' style='width:100%;text-align:center;' for='files'>" +
"<input id='files' name='files' type='file' style='display:none;' multiple='true'>" +
"<i class='fa fa-cloud-upload fa-3x'></i>" +
"<br />" +
"Click Here To Upload" +
"</label></form>" +
"<br />" +
"<ul style='margin-left:5px !important;' id='fileList'></ul>" +
"</div><div class='clearfix'></div>";
bootbox.dialog({
message: uploadHtml,
title: "File Upload",
buttons: {
success: {
label: "Upload",
className: "btn-default",
callback: function () {
$('#upload_file').submit(); //.trigger('submit');
files = document.getElementById("upload_files").files
for (i = 0; i < files.length; i++){
var xmlHttpRequest = new XMLHttpRequest();
var file = files[i]
var fileName = files[i].name
var target = '/proxy'
var mimeType = files[i].type
xmlHttpRequest.open('POSTT', target, true);
console.log(xmlHttpRequest);
xmlHttpRequest.setRequestHeader('Content-Type', mimeType);
xmlHttpRequest.setRequestHeader('Content-Disposition', 'attachment; filename="' + fileName + '"');
xmlHttpRequest.send(file);
$(function () {
server_id = {{ data['server_stats']['server_id']['server_id'] }};
var uploadHtml = "<div>" +
'<form id="upload_file" enctype="multipart/form-data">'+"<label class='upload-area' style='width:100%;text-align:center;' for='files'>" +
"<input id='files' name='files' type='file' style='display:none;' multiple='true'>" +
"<i class='fa fa-cloud-upload fa-3x'></i>" +
"<br />" +
"Click Here To Select Files" +
"</label></form>" +
"<br />" +
"<ul style='margin-left:5px !important;' id='fileList'></ul>" +
"</div><div class='clearfix'></div>";
bootbox.dialog({
message: uploadHtml,
title: "Upload Files to : "+path,
buttons: {
success: {
label: "Upload",
className: "btn-default",
callback: async function () {
files = document.getElementById("files");
uploadWaitDialog = bootbox.dialog({
message: `
<p class="text-center mb-0">
<i class="fa fa-spin fa-cog"></i>
Please wait while we upload your files... This may take a while.<br>
<strong>DO NOT CLOSE THIS PAGE.</strong>
</p>
<div class="progress" id="upload-progress-bar-parent">
</div>
`,
closeButton: false
});
let nFiles = files.files.length;
for(i=0; i < files.files.length; i++) {
if (!doUpload) {
doUpload = true;
hideUploadBox();
break;
}
console.log(files.files[i]);
const progressHtml = `
<div>
${path + '/' + files.files[i]}:
<div
id="upload-progress-bar-${i + 1}"
class="progress-bar progress-bar-striped progress-bar-animated"
role="progressbar"
style="width: 0%"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100"
></div>
</div>
`;
$('#upload-progress-bar-parent').append(progressHtml);
sendFile(files.files[i], path, server_id, (progress) => {
$(`#upload-progress-bar-${i + 1}`).attr('aria-valuenow', progress)
$(`#upload-progress-bar-${i + 1}`).css('width', progress + '%')
});
}
}
}
}
hideUploadBox();
//$('#upload_file').submit(); //.trigger('submit');
}
}
}
});
var fileList = document.getElementById("files");
fileList.addEventListener("change", function (e) {
var list = "";
@ -577,18 +644,6 @@
}, false);
});
}
function uploadFiles(e){
path = event.target.parentElement.getAttribute('data-path');
server_id = {{ data['server_stats']['server_id']['server_id'] }};
var uploadHtml = '<form enctype="multipart/form-data" action="/ajax/upload_files?id=' + server_id +'&path='+ path +'"method="post">{% raw xsrf_form_html() %}<div class="form-group">'+"<label class='upload-area' style='width:100%;text-align:center;' for='fupload'>" +'<input id="files" type="file" name="files" multiple>' +"<i class='fa fa-cloud-upload fa-3x'></i>" +"<br />" +"Upload Files Here" +"</label>" +"<br />" +"<span style='margin-left:5px !important;' id='fileList'></span>"+'</div><br><br><input id="upload_file" type="submit"value="Upload File" class="btn btn-success hidden"/></form>';
bootbox.dialog({
message: uploadHtml,
title: "Upload Files To "+path,
});
}
function getTreeView() {
$.ajax({
type: "GET",
@ -688,12 +743,25 @@
document.addEventListener('click', function(e){
let inside = (e.target.closest('#files-tree-nav'));
let contextMenu = document.getElementById('files-tree-nav');
if(!inside){
let contextMenu = document.getElementById('files-tree-nav');
contextMenu.setAttribute('style', 'display:none');
}else{
contextMenu.setAttribute('style', 'display:none');
}
});
if (webSocket) {
webSocket.on('close_upload_box', function (close_upload_box) {
hideUploadBox();
});
}
function hideUploadBox(){
if (!uploadWaitDialog) return;
uploadWaitDialog.modal('hide');
getTreeView();
}
function createFileE(event) {
bootbox.prompt("{% raw translate('serverFiles', 'createFileQuestion') %}", function(result) {

1
chat.txt Normal file
View File

@ -0,0 +1 @@
Sure