Merge branch 'pretzel' into 'dev'

Added Upload and Unzip functions. Added better context menu

See merge request crafty-controller/crafty-commander!48
This commit is contained in:
Andrew 2021-08-20 19:03:02 +00:00
commit fc719d07d9
8 changed files with 319 additions and 71 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@ -286,7 +286,10 @@ class Controller:
tempDir = tempfile.mkdtemp()
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
zip_ref.extractall(tempDir)
test = zip_ref.filelist[1].filename
for i in range(len(zip_ref.filelist)):
if len(zip_ref.filelist) > 1 or not zip_ref.filelist[i].filename.endswith('/'):
test = zip_ref.filelist[i].filename
break
path_list = test.split('/')
root_path = path_list[0]
if len(path_list) > 1:

View File

@ -2,6 +2,7 @@ import os
import re
import sys
import json
import tempfile
import time
import uuid
import string
@ -258,6 +259,44 @@ class Helpers:
logger.critical("Unable to write to {} - Error: {}".format(path, e))
return False
def unzipFile(self, zip_path):
new_dir_list = zip_path.split('/')
new_dir = ''
for i in range(len(new_dir_list)-1):
if i == 0:
new_dir += new_dir_list[i]
else:
new_dir += '/'+new_dir_list[i]
if helper.check_file_perms(zip_path) and os.path.isfile(zip_path):
helper.ensure_dir_exists(new_dir)
tempDir = tempfile.mkdtemp()
try:
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
zip_ref.extractall(tempDir)
for i in range(len(zip_ref.filelist)):
if len(zip_ref.filelist) > 1 or not zip_ref.filelist[i].filename.endswith('/'):
test = zip_ref.filelist[i].filename
break
path_list = test.split('/')
root_path = path_list[0]
if len(path_list) > 1:
for i in range(len(path_list) - 2):
root_path = os.path.join(root_path, path_list[i + 1])
full_root_path = os.path.join(tempDir, root_path)
for item in os.listdir(full_root_path):
try:
shutil.move(os.path.join(full_root_path, item), os.path.join(new_dir, item))
except Exception as ex:
logger.error('ERROR IN ZIP IMPORT: {}'.format(ex))
except Exception as ex:
print(ex)
else:
return "false"
return
def ensure_logging_setup(self):
log_file = os.path.join(os.path.curdir, 'logs', 'commander.log')
session_log_file = os.path.join(os.path.curdir, 'logs', 'session.log')

View File

@ -503,6 +503,7 @@ class Server:
while db_helper.get_server_stats_by_id(self.server_id)['updating']:
if downloaded and not self.is_backingup:
print("Backup Status: " + str(self.is_backingup))
logger.info("Executable updated successfully. Starting Server")
db_helper.set_update(self.server_id, False)

View File

@ -1,5 +1,8 @@
import json
import logging
import tempfile
import zipfile
import tornado.web
import tornado.escape
import bleach
@ -183,10 +186,55 @@ class AjaxHandler(BaseHandler):
logger.warning("Invalid path in create_dir ajax call ({})".format(dir_path))
console.warning("Invalid path in create_dir ajax call ({})".format(dir_path))
return
# Create the directory
os.mkdir(dir_path)
elif page == "unzip_file":
server_id = self.get_argument('id', None)
path = self.get_argument('path', None)
helper.unzipFile(path)
self.redirect("/panel/server_detail?id={}&subpage=files".format(server_id))
return
elif page == "upload_files":
server_id = self.get_argument('id', None)
path = self.get_argument('path', None)
if helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], path):
try:
files = self.request.files['files']
for file in files:
if file['filename'].split('.') is not None:
self._upload_file(file['body'], path, file['filename'])
else:
logger.error("Directory Detected. Skipping")
self.redirect("/panel/server_detail?id={}&subpage=files".format(server_id))
except Exception as e:
self.redirect("/panel/server_detail?id={}&subpage=files".format(server_id))
else:
logger.error("Invalid directory requested. Canceling upload")
return
def _upload_file(self, file_data, file_path, file_name):
error = ""
file_full_path = os.path.join(file_path, file_name)
if os.path.exists(file_full_path):
error = "A file with this name already exists."
if not helper.check_writeable(file_path):
error = "Unwritable Path"
if error != "":
logger.error("Unable to save uploaded file due to: {}".format(error))
return False
output_file = open(file_full_path, 'wb')
output_file.write(file_data)
logger.info('Saving File: {}'.format(file_full_path))
return True
@tornado.web.authenticated
def delete(self, page):
if page == "del_file":

View File

@ -79,4 +79,60 @@ body { background-color: var(--dark) !important; /* Firefox */ }
.actions_serverlist > a > i {
cursor: pointer;
}
}
/* The Overlay (background) */
.overlay {
/* Height & width depends on how you want to reveal the overlay (see JS below) */
height: 0;
width: 100vw;
position: fixed; /* Stay in place */
z-index: 1031; /* Sit on top */
left: 0;
top: 0;
background-color: rgb(0,0,0); /* Black fallback color */
background-color: rgba(0,0,0, 0.9); /* Black w/opacity */
overflow-x: hidden; /* Disable horizontal scroll */
transition: 0.5s; /* 0.5 second transition effect to slide in or slide down the overlay (height or width, depending on reveal) */
}
/* Position the content inside the overlay */
.overlay-content {
position: relative;
top: 25%; /* 25% from the top */
width: 100%; /* 100% width */
text-align: center; /* Centered text/links */
margin-top: 30px; /* 30px top margin to avoid conflict with the close button on smaller screens */
}
/* The navigation links inside the overlay */
.overlay a {
padding: 8px;
text-decoration: none;
font-size: 36px;
color: #818181;
display: block; /* Display block instead of inline */
transition: 0.3s; /* Transition effects on hover (color) */
}
/* When you mouse over the navigation links, change their color */
.overlay a:hover, .overlay a:focus {
color: #f1f1f1;
}
/* Position the close button (top right corner) */
.overlay .closebtn {
position: absolute;
top: 20px;
right: 45px;
font-size: 60px;
}
/* When the height of the screen is less than 450 pixels, change the font-size of the links and position the close button again, so they don't overlap */
@media screen and (max-height: 450px) {
.overlay a {font-size: 20px}
.overlay .closebtn {
font-size: 40px;
top: 15px;
right: 35px;
}
}

View File

@ -16188,41 +16188,35 @@ body.avgrund-active {
border-radius: 2px; }
/* Context Menu */
.context-menu-icon:before {
color: #000;
font: normal normal normal 15px/1 "Material Design Icons"; }
.context-menu {
position: absolute;
text-align: center;
background: lightgray;
border: 1px solid black;
display: none;
}
.context-menu-icon.context-menu-icon-cut:before {
content: '\F190'; }
.context-menu ul {
padding: 0px;
margin: 0px;
min-width: 150px;
list-style: none;
}
.context-menu-icon.context-menu-icon-edit:before {
content: '\F3EF'; }
.context-menu ul li {
padding-bottom: 7px;
padding-top: 7px;
border: 1px solid black;
}
.context-menu-icon.context-menu-icon-copy:before {
content: '\F18F'; }
.context-menu-icon.context-menu-icon-paste:before {
content: '\F613'; }
.context-menu-icon.context-menu-icon-delete:before {
content: '\F6CB'; }
.context-menu-icon.context-menu-icon-quit:before {
content: '\F156'; }
.context-menu-list {
-webkit-box-shadow: none;
box-shadow: none;
border: 1px solid #dee2e6; }
.context-menu-list .context-menu-item span {
color: #000;
font-size: 0.75rem;
font-family: "roboto", sans-serif; }
.context-menu-list .context-menu-item.context-menu-hover {
background: #000; }
.context-menu-list .context-menu-item.context-menu-hover span {
color: #fff; }
.context-menu ul li a {
text-decoration: none;
color: black;
}
.context-menu ul li:hover {
background: darkgray;
}
/* Clockpicker */
.clockpicker-popover {
background-color: #dee2e6; }

View File

@ -72,7 +72,6 @@
<div id="files-tree-nav" class="overlay">
<!-- Button to close the overlay navigation -->
<a href="javascript:void(0)" class="closebtn" onclick="document.getElementById('files-tree-nav').style.height = '0%';">&times;</a>
<!-- Overlay content -->
<div id="files-tree-nav-content" class="overlay-content">
@ -81,55 +80,67 @@
<a onclick="renameItemE(event)" href="javascript:void(0)" id="renameItem" href="#">{{ translate('serverFiles', 'rename') }}</a>
<a onclick="deleteFileE(event)" href="javascript:void(0)" id="deleteFile" href="#">{{ translate('serverFiles', 'delete') }}</a>
<a onclick="deleteDirE(event)" href="javascript:void(0)" id="deleteDir" href="#">{{ translate('serverFiles', 'delete') }}</a>
<a onclick="uploadFilesE(event)" href="javascript:void(0)" id="upload" href="#">Upload Files</a>
<a onclick="unzipFilesE(event)" href="javascript:void(0)" id="unzip" href="#">Unzip</a>
<a href="javascript:void(0)" class="closebtn" style="color: red;" onclick="document.getElementById('files-tree-nav').style.display = 'none';">Close</a>
</div>
</div>
<style>
/* The Overlay (background) */
.overlay {
/* Height & width depends on how you want to reveal the overlay (see JS below) */
height: 0;
width: 100vw;
position: fixed; /* Stay in place */
z-index: 1031; /* Sit on top */
left: 0;
top: 0;
background-color: rgb(0,0,0); /* Black fallback color */
background-color: rgba(0,0,0, 0.9); /* Black w/opacity */
overflow-x: hidden; /* Disable horizontal scroll */
transition: 0.5s; /* 0.5 second transition effect to slide in or slide down the overlay (height or width, depending on reveal) */
display: none;
flex-direction: column;
background-color: #fff;
border-radius: 10px;
box-shadow: 0 10px 20px rgb(64 64 64 / 5%);
padding: 10px 0;
z-index: 10000;
overflow: scroll;
}
/* Position the content inside the overlay */
.overlay-content {
position: relative;
top: 25%; /* 25% from the top */
width: 100%; /* 100% width */
text-align: center; /* Centered text/links */
margin-top: 30px; /* 30px top margin to avoid conflict with the close button on smaller screens */
overflow: scroll;
display: flex;
flex-direction: column;
background-color: #fff;
border-radius: 10px;
box-shadow: 0 10px 20px rgb(64 64 64 / 5%);
padding: 10px 0;
}
/* The navigation links inside the overlay */
.overlay a {
padding: 8px;
text-decoration: none;
font-size: 36px;
color: #818181;
display: block; /* Display block instead of inline */
transition: 0.3s; /* Transition effects on hover (color) */
font: inherit;
border: 0;
padding: 10px 30px 10px 15px;
width: 100%;
display: flex;
align-items: center;
position: relative;
text-decoration: unset;
color: #000;
font-weight: 500;
transition: 0.5s linear;
-webkit-transition: 0.5s linear;
-moz-transition: 0.5s linear;
-ms-transition: 0.5s linear;
-o-transition: 0.5s linear;
}
/* When you mouse over the navigation links, change their color */
.overlay a:hover, .overlay a:focus {
color: #f1f1f1;
background:#f1f3f7;
color: #4b00ff;
}
/* Position the close button (top right corner) */
.overlay .closebtn {
position: absolute;
top: 20px;
right: 45px;
font-size: 60px;
.overlay .closebtn .closebtn:hover {
background-color: red;
color: red;
z-index: 10000;
}
/* When the height of the screen is less than 450 pixels, change the font-size of the links and position the close button again, so they don't overlap */
@ -519,6 +530,71 @@
});
}
function unZip(path, callback) {
var token = getCookie("_xsrf")
$.ajax({
type: "POST",
headers: {'X-XSRFToken': token},
url: '/ajax/unzip_file?id={{ data['server_stats']['server_id']['server_id'] }}',
data: {
path: path
},
});
window.location.href = "/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=files"
}
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" action="/ajax/upload_files?id=' + server_id +'&path='+ path +'"method="post">{% raw xsrf_form_html() %}'+"<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');
}
}
}
});
var fileList = document.getElementById("files");
fileList.addEventListener("change", function (e) {
var list = "";
for (var i = 0; i < this.files.length; i++) {
list += "<li class='col-xs-12 file-list'>" + this.files[i].name + "</li>"
}
document.getElementById("fileList").innerHTML = list;
}, 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",
@ -572,6 +648,7 @@
$('#createFile').toggle(isDir);
$('#createDir').toggle(isDir);
$('#deleteDir').toggle(isDir);
$('#upload').toggle(isDir);
var isFile = event.target.classList.contains('tree-file');
$('#deleteFile').toggle(isFile);
@ -583,20 +660,46 @@
$('#renameItem').hide();
$('#deleteDir').hide();
$('#deleteFile').hide();
$('#upload').show();
}
if(event.target.textContent.endsWith('.zip')){
$('#unzip').show();
console.log(event.target.textContent)
}else{
$('#unzip').hide();}
var clientX = event.clientX;
var clientY = event.clientY;
document.getElementById('files-tree-nav-content')
.setAttribute('data-path', ctxmenuPath);
document.getElementById('files-tree-nav-content')
.setAttribute('data-name', ctxmenuName);
document.getElementById("files-tree-nav").style.height = "100%";
document.getElementById("files-tree-nav").style.display = "flex";
document.getElementById("files-tree-nav").style.position = "fixed";
domRect = document.getElementById("files-tree-nav").getBoundingClientRect();
sum = (clientY+domRect['height']) - window.innerHeight
if(domRect['height']+clientY > window.innerHeight){
clientY = clientY - sum
}
document.getElementById("files-tree-nav").style.top = clientY + 'px';
document.getElementById("files-tree-nav").style.left = clientX + 'px';
console.log(domRect)
console.log(window.innerHeight)
})
}
}
document.addEventListener('click', function(e){
let inside = (e.target.closest('#files-tree-nav'));
if(!inside){
let contextMenu = document.getElementById('files-tree-nav');
contextMenu.setAttribute('style', 'display:none');
}
});
function createFileE(event) {
bootbox.prompt("{% raw translate('serverFiles', 'createFileQuestion') %}", function(result) {
@ -606,7 +709,7 @@
createFile(path, result, function () {
getTreeView()
document.getElementById('files-tree-nav').style.height = '0%';
document.getElementById('files-tree-nav').style.display = 'none';
});
})
}
@ -619,7 +722,7 @@
createDir(path, result, function () {
getTreeView()
document.getElementById('files-tree-nav').style.height = '0%';
document.getElementById('files-tree-nav').style.display = 'none';
});
})
}
@ -632,10 +735,14 @@
renameItem(path, result, function () {
getTreeView()
document.getElementById('files-tree-nav').style.height = '0%';
document.getElementById('files-tree-nav').style.display = 'none';
});
})
}
function unzipFilesE(event) {
path = event.target.parentElement.getAttribute('data-path');
unZip(path)
}
function deleteFileE(event) {
path = event.target.parentElement.getAttribute('data-path');
@ -659,7 +766,7 @@
if (!result) return;
deleteFile(path, function () {
getTreeView()
document.getElementById('files-tree-nav').style.height = '0%';
document.getElementById('files-tree-nav').style.display = 'none';
});
}
});
@ -687,7 +794,7 @@
if (!result) return;
deleteDir(path, function () {
getTreeView()
document.getElementById('files-tree-nav').style.height = '0%';
document.getElementById('files-tree-nav').style.display = 'none';
});
}
});