mirror of
https://gitlab.com/crafty-controller/crafty-4.git
synced 2024-08-30 18:23:09 +00:00
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:
commit
fc719d07d9
@ -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:
|
||||
|
@ -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')
|
||||
|
@ -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)
|
||||
|
@ -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":
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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; }
|
||||
|
@ -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%';">×</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';
|
||||
});
|
||||
}
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user