Add upload import functionality.

Update gitignore
This commit is contained in:
Andrew 2022-09-27 22:06:22 -04:00
parent 8cada1c450
commit b9bd654e58
6 changed files with 840 additions and 426 deletions

1
.gitignore vendored
View File

@ -18,6 +18,7 @@ env.bak/
venv.bak/ venv.bak/
.idea/ .idea/
/imports/
/servers/ /servers/
/backups/ /backups/
/temp/ /temp/

View File

@ -476,6 +476,12 @@ class AjaxHandler(BaseHandler):
elif page == "unzip_server": elif page == "unzip_server":
path = self.get_argument("path", None) path = self.get_argument("path", None)
if not path:
path = os.path.join(
self.controller.project_root,
"imports",
self.get_argument("file", ""),
)
if Helpers.check_file_exists(path): if Helpers.check_file_exists(path):
self.helper.unzip_server(path, exec_user["user_id"]) self.helper.unzip_server(path, exec_user["user_id"])
else: else:

View File

@ -4,6 +4,7 @@ import time
import tornado.web import tornado.web
import tornado.options import tornado.options
import tornado.httpserver import tornado.httpserver
from app.classes.models.crafty_permissions import EnumPermissionsCrafty
from app.classes.models.server_permissions import EnumPermissionsServer from app.classes.models.server_permissions import EnumPermissionsServer
from app.classes.shared.console import Console from app.classes.shared.console import Console
@ -33,6 +34,190 @@ class UploadHandler(BaseHandler):
def prepare(self): def prepare(self):
# Class & Function Defination # Class & Function Defination
api_key, _token_data, exec_user = self.current_user api_key, _token_data, exec_user = self.current_user
self.upload_type = str(self.request.headers.get("X-Content-Upload-Type"))
print(self.upload_type)
if self.upload_type == "server_import":
superuser = exec_user["superuser"]
if api_key is not None:
superuser = superuser and api_key.superuser
user_id = exec_user["user_id"]
stream_size_value = self.helper.get_setting("stream_size_GB")
max_streamed_size = (1024 * 1024 * 1024) * stream_size_value
self.content_len = int(self.request.headers.get("Content-Length"))
if self.content_len > max_streamed_size:
logger.error(
f"User with ID {user_id} attempted to upload a file that"
f" exceeded the max body size."
)
self.helper.websocket_helper.broadcast_user(
user_id,
"send_start_error",
{
"error": self.helper.translation.translate(
"error",
"fileTooLarge",
self.controller.users.get_user_lang_by_id(user_id),
),
},
)
return
self.do_upload = True
if superuser:
exec_user_server_permissions = (
self.controller.server_perms.list_defined_permissions()
)
elif api_key is not None:
exec_user_server_permissions = (
self.controller.crafty_perms.get_api_key_permissions_list(api_key)
)
else:
exec_user_server_permissions = (
self.controller.crafty_perms.get_crafty_permissions_list(
exec_user["user_id"]
)
)
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 (
EnumPermissionsCrafty.SERVER_CREATION
not in exec_user_server_permissions
and not exec_user["superuser"]
):
logger.warning(
f"User {user_id} tried to upload a server" " without permissions!"
)
Console.warning(
f"User {user_id} tried to upload a server" " without permissions!"
)
self.do_upload = False
path = os.path.join(self.controller.project_root, "imports")
# Delete existing files
if len(os.listdir(path)) > 0:
for item in os.listdir():
try:
os.remove(os.path.join(path, item))
except:
logger.debug("Could not delete file on user server upload")
self.helper.ensure_dir_exists(path)
filename = self.request.headers.get("X-FileName", None)
full_path = os.path.join(path, filename)
if self.do_upload:
try:
self.f = open(full_path, "wb")
except Exception as e:
logger.error(f"Upload failed with error: {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)
elif self.upload_type == "background":
superuser = exec_user["superuser"]
if api_key is not None:
superuser = superuser and api_key.superuser
user_id = exec_user["user_id"]
stream_size_value = self.helper.get_setting("stream_size_GB")
max_streamed_size = (1024 * 1024 * 1024) * stream_size_value
self.content_len = int(self.request.headers.get("Content-Length"))
if self.content_len > max_streamed_size:
logger.error(
f"User with ID {user_id} attempted to upload a file that"
f" exceeded the max body size."
)
self.helper.websocket_helper.broadcast_user(
user_id,
"send_start_error",
{
"error": self.helper.translation.translate(
"error",
"fileTooLarge",
self.controller.users.get_user_lang_by_id(user_id),
),
},
)
return
self.do_upload = True
if superuser:
exec_user_server_permissions = (
self.controller.server_perms.list_defined_permissions()
)
elif api_key is not None:
exec_user_server_permissions = (
self.controller.server_perms.get_api_key_permissions_list(
api_key, server_id
)
)
else:
exec_user_server_permissions = (
self.controller.server_perms.get_user_id_permissions_list(
exec_user["user_id"], server_id
)
)
server_id = self.request.headers.get("X-ServerId", None)
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
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 EnumPermissionsServer.FILES not in exec_user_server_permissions:
logger.warning(
f"User {user_id} tried to upload a file to "
f"{server_id} without permissions!"
)
Console.warning(
f"User {user_id} tried to upload a file to "
f"{server_id} without permissions!"
)
self.do_upload = False
path = self.request.headers.get("X-Path", None)
filename = self.request.headers.get("X-FileName", None)
full_path = os.path.join(path, filename)
if not Helpers.in_path(
Helpers.get_os_understandable_path(
self.controller.servers.get_server_data_by_id(server_id)["path"]
),
full_path,
):
logger.warning(
f"User {user_id} tried to upload a file to {server_id} "
f"but the path is not inside of the server!"
)
Console.warning(
f"User {user_id} tried to upload a file to {server_id} "
f"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(f"Upload failed with error: {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)
else:
server_id = self.get_argument("server_id", None) server_id = self.get_argument("server_id", None)
superuser = exec_user["superuser"] superuser = exec_user["superuser"]
if api_key is not None: if api_key is not None:
@ -80,17 +265,16 @@ class UploadHandler(BaseHandler):
) )
server_id = self.request.headers.get("X-ServerId", None) server_id = self.request.headers.get("X-ServerId", None)
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
if user_id is None: if user_id is None:
logger.warning("User ID not found in upload handler call") logger.warning("User ID not found in upload handler call")
Console.warning("User ID not found in upload handler call") Console.warning("User ID not found in upload handler call")
self.do_upload = False 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
if EnumPermissionsServer.FILES not in exec_user_server_permissions: if EnumPermissionsServer.FILES not in exec_user_server_permissions:
logger.warning( logger.warning(
f"User {user_id} tried to upload a file to " f"User {user_id} tried to upload a file to "
@ -112,14 +296,6 @@ class UploadHandler(BaseHandler):
), ),
full_path, full_path,
): ):
print(
user_id,
server_id,
Helpers.get_os_understandable_path(
self.controller.servers.get_server_data_by_id(server_id)["path"]
),
full_path,
)
logger.warning( logger.warning(
f"User {user_id} tried to upload a file to {server_id} " f"User {user_id} tried to upload a file to {server_id} "
f"but the path is not inside of the server!" f"but the path is not inside of the server!"
@ -141,7 +317,10 @@ class UploadHandler(BaseHandler):
def post(self): def post(self):
logger.info("Upload completed") logger.info("Upload completed")
if self.upload_type == "server_files":
files_left = int(self.request.headers.get("X-Files-Left", None)) files_left = int(self.request.headers.get("X-Files-Left", None))
else:
files_left = 0
if self.do_upload: if self.do_upload:
time.sleep(5) time.sleep(5)

View File

@ -711,6 +711,7 @@
xmlHttpRequest.setRequestHeader('X-Content-Length', size); xmlHttpRequest.setRequestHeader('X-Content-Length', size);
xmlHttpRequest.setRequestHeader('X-Content-Disposition', 'attachment; filename="' + fileName + '"'); xmlHttpRequest.setRequestHeader('X-Content-Disposition', 'attachment; filename="' + fileName + '"');
xmlHttpRequest.setRequestHeader('X-Path', path); xmlHttpRequest.setRequestHeader('X-Path', path);
xmlHttpRequest.setRequestHeader('X-Content-Upload-Type', 'server_files')
xmlHttpRequest.setRequestHeader('X-Files-Left', left); xmlHttpRequest.setRequestHeader('X-Files-Left', left);
xmlHttpRequest.setRequestHeader('X-FileName', fileName); xmlHttpRequest.setRequestHeader('X-FileName', fileName);
xmlHttpRequest.setRequestHeader('X-ServerId', serverId); xmlHttpRequest.setRequestHeader('X-ServerId', serverId);

View File

@ -431,9 +431,170 @@
</p> </p>
</div> </div>
</div> </div>
<div class="col-sm-6 grid-margin"> <div class="col-sm-6 grid-margin stretch-card">
<img id="op_logo" style="filter: grayscale(10%); opacity: .1;" src="../../static/assets/images/logo_small.svg" <div class="card">
alt="Crafty logo" /> <div class="card-body">
<h4>{{ translate('serverWizard', 'uploadZip', data['lang']) }}</h4>
<br />
<p class="card-description">
<form name="zip" method="post" class="server-wizard" onSubmit="wait_msg(true)">
{% raw xsrf_form_html() %}
<input type="hidden" value="import_zip" name="create_type">
<div class="row">
<div class="col-sm-12">
<div class="col-sm-12">
<div class="form-group">
<label for="server_name">{{ translate('serverWizard', 'serverName', data['lang']) }}</label>
<input type="text" class="form-control" id="server_name" name="server_name" value=""
placeholder="{{ translate('serverWizard', 'myNewServer', data['lang']) }}" required>
</div>
</div>
<div class="col-sm-12">
<div class="form-group">
<label for="server">Server Upload </label><br>
<span id="upload_input">
<input type="file" multiple="false" class="form-control" id="file" name="file" required
style="width: 70%;">
<button type="button" class="btn btn-info" onclick="sendFile()">UPLOAD</button>
</span>
</div>
</div>
</div>
<div id="lower_half" style="visibility: hidden;">
<div class="col-sm-12">
<div class="form-group">
<label for="server">{{ translate('serverWizard', 'selectRoot', data['lang']) }} <small>{{
translate('serverWizard', 'explainRoot', data['lang']) }}</small></label>
<br>
<button class="btn btn-primary mr-2" id="root_upload_button" type="button">{{
translate('serverWizard', 'clickRoot', data['lang']) }}</button>
</div>
</div>
<div class="col-sm-12">
<div class="form-group">
<label for="server_jar">{{ translate('serverWizard', 'serverJar', data['lang']) }}</label>
<input type="text" class="form-control" id="server_jar" name="server_jar" value=""
placeholder="paper.jar" required>
</div>
</div>
<div class="col-sm-12">
<h4 class="card-title">{{ translate('serverWizard', 'quickSettings', data['lang']) }} <small
style="text-transform: none;"> - {{ translate('serverWizard', 'quickSettingsDescription',
data['lang']) }}</small></h4>
<hr>
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label for="min_memory3">{{ translate('serverWizard', 'minMem', data['lang']) }} <small> - {{
translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
<input type="number" class="form-control" id="min_memory3" name="min_memory" value="1"
step="0.5" min="0.5" required>
</div>
</div>
<div class="col-sm-12">
<div class="form-group">
<label for="max_memory3">{{ translate('serverWizard', 'maxMem', data['lang']) }} <small> - {{
translate('serverWizard', 'sizeInGB', data['lang']) }}</small></label>
<input type="number" class="form-control" id="max_memory3" name="max_memory" value="2"
step="0.5" min="0.5" required>
</div>
</div>
<div class="col-sm-12">
<div class="form-group">
<label for="port3">{{ translate('serverWizard', 'serverPort', data['lang']) }} <small> - {{
translate('serverWizard', 'defaultPort', data['lang']) }}</small></label>
<input type="number" class="form-control" id="port3" name="port" value="25565" step="1" min="1"
required>
</div>
</div>
<div class="col-sm-12">
<div class="form-group">
<div id="accordion-3">
<div class="card">
<div class="card-header p-2" id="Role-3">
<p class="mb-0 p-0" data-toggle="collapse" data-target="#collapseRole-3"
aria-expanded="true" aria-controls="collapseRole-3">
<i class="fas fa-chevron-down"></i> {{ translate('serverWizard', 'addRole',
data['lang'])
}} <small style="text-transform: none;"> - {{ translate('serverWizard', 'autoCreate',
data['lang']) }}</small>
</p>
</div>
<div id="collapseRole-3" class="collapse" aria-labelledby="Role-3" data-parent="">
<div class="card-body scroll">
<div class="form-group">
{% for r in data['roles'] %}
<span class="d-block menu-option"><label><input name="{{ r['role_id'] }}"
type="checkbox">&nbsp;
{{ r['role_name'].capitalize() }}</label></span>
{% end %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-12" style="visibility: hidden;">
<div class="form-group">
<input type="text" class="form-control" id="zip_root_path" name="zip_root_path">
</div>
</div>
<div class="modal fade" id="dir_select" tabindex="-1" role="dialog" aria-labelledby="dir_select"
aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLongTitle">{{ translate('serverWizard',
'selectZipDir', data['lang']) }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="tree-ctx-item" id="main-tree-div" data-path=""
style="overflow: scroll; max-height:75%;">
<input type="radio" id="main-tree-input-upload" name="root_path" value="" checked>
<span id="main-tree" class="files-tree-title tree-caret-down root-dir" data-path="">
<i class="far fa-folder"></i>
<i class="far fa-folder-open"></i>
{{ translate('serverFiles', 'files', data['lang']) }}
</span>
</input>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{
translate('serverWizard', 'close', data['lang']) }}</button>
<button type="button" id="modal-okay" data-dismiss="modal" class="btn btn-primary">{{
translate('serverWizard', 'save', data['lang']) }}</button>
</div>
</div>
</div>
</div>
</div>
</div>
<button id="upload_submit" type="submit" title="You must select server root dir first" disabled
class="btn btn-primary mr-2">{{ translate('serverWizard', 'importServerButton', data['lang'])
}}</button>
<button type="reset" class="btn btn-danger mr-2">{{ translate('serverWizard', 'resetForm', data['lang'])
}}</button>
</div>
</div>
</form>
</p>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -526,6 +687,43 @@
{% block js%} {% block js%}
<script> <script>
var file;
function sendFile() {
file = $("#file")[0].files[0]
document.getElementById("upload_input").innerHTML = '<div class="progress"><div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%">&nbsp;<i class="fa-solid fa-spinner"></i></div></div>'
let xmlHttpRequest = new XMLHttpRequest();
let token = getCookie("_xsrf")
let fileName = file.name
let target = '/upload'
let mimeType = file.type
let size = file.size
let type = 'server_import'
xmlHttpRequest.open('POST', target, true);
xmlHttpRequest.setRequestHeader('X-Content-Type', mimeType);
xmlHttpRequest.setRequestHeader('X-XSRFToken', token);
xmlHttpRequest.setRequestHeader('X-Content-Length', size);
xmlHttpRequest.setRequestHeader('X-Content-Disposition', 'attachment; filename="' + fileName + '"');
xmlHttpRequest.setRequestHeader('X-Content-Upload-Type', type);
xmlHttpRequest.setRequestHeader('X-FileName', fileName);
xmlHttpRequest.addEventListener('load', (event) => {
if (event.target.responseText == 'success') {
console.log('Upload for file', file.name, 'was successful!')
document.getElementById("upload_input").innerHTML = '<div class="card-header header-sm d-flex justify-content-between align-items-center"><span id="file-uploaded" style="color: gray;">' + fileName + '</span> 🔒</div>';
document.getElementById("lower_half").style.visibility = "visible";
}
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);
}
document.getElementById("root_files_button").addEventListener("click", function () { document.getElementById("root_files_button").addEventListener("click", function () {
if (document.forms["zip"]["server_path"].value != "") { if (document.forms["zip"]["server_path"].value != "") {
if (document.getElementById('root_files_button').classList.contains('clicked')) { if (document.getElementById('root_files_button').classList.contains('clicked')) {
@ -549,6 +747,28 @@
bootbox.alert("You must input a path before selecting this button"); bootbox.alert("You must input a path before selecting this button");
} }
}); });
document.getElementById("root_upload_button").addEventListener("click", function () {
if (file) {
if (document.getElementById('root_upload_button').classList.contains('clicked')) {
document.getElementById('main-tree-div').innerHTML = '<input type="radio" id="main-tree-input-upload" name="root_path" value="" checked><span id="main-tree" class="files-tree-title tree-caret-down root-dir" data-path=""><i class="far fa-folder"></i><i class="far fa-folder-open"></i>{{ translate("serverFiles", "files", data["lang"]) }}</span></input>'
} else {
document.getElementById('root_upload_button').classList.add('clicked')
}
var token = getCookie("_xsrf");
var dialog = bootbox.dialog({
message: '<p class="text-center mb-0"><i class="fa fa-spin fa-cog"></i> Please wait while we gather your files...</p>',
closeButton: false
});
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/ajax/unzip_server?id=-1&file=' + file.name,
});
} else {
bootbox.alert("You must input a path before selecting this button");
}
});
</script> </script>
<script> <script>
@ -606,7 +826,12 @@
} }
function getTreeView(path) { function getTreeView(path) {
//If this value is still hidden we know the user is executing a zip import and not an upload
if (document.getElementById("lower_half").visibility == "hidden") {
document.getElementById('zip_submit').disabled = false; document.getElementById('zip_submit').disabled = false;
} else {
document.getElementById('upload_submit').disabled = false;
}
path = path path = path
$.ajax({ $.ajax({
@ -700,6 +925,7 @@
x.remove() x.remove()
} }
document.getElementById('main-tree-input').setAttribute('value', data.path) document.getElementById('main-tree-input').setAttribute('value', data.path)
document.getElementById('main-tree-input-upload').setAttribute('value', data.path)
getTreeView(data.path); getTreeView(data.path);
show_file_tree(); show_file_tree();

View File

@ -502,6 +502,7 @@
"importServer": "Import an Existing Server", "importServer": "Import an Existing Server",
"importServerButton": "Import Server!", "importServerButton": "Import Server!",
"importZip": "Import from a Zip File", "importZip": "Import from a Zip File",
"uploadZip": "Upload Zip File For Server Import",
"maxMem": "Maximum Memory", "maxMem": "Maximum Memory",
"minMem": "Minimum Memory", "minMem": "Minimum Memory",
"myNewServer": "My New Server", "myNewServer": "My New Server",