mirror of
https://gitlab.com/crafty-controller/crafty-4.git
synced 2024-08-30 18:23:09 +00:00
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:
parent
734a576cb9
commit
86c1b374bf
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user