File Manager: DONE

This commit is contained in:
LukasDoesDev 2021-01-17 19:20:28 +02:00
parent eb06f1b33b
commit e3a359bbc6
6 changed files with 544 additions and 45 deletions

View File

@ -49,7 +49,7 @@ class Controller:
continue
# if this server path no longer exists - let's warn and bomb out
if not helper.check_path_exits(s['path']):
if not helper.check_path_exists(s['path']):
logger.warning("Unable to find server {} at path {}. Skipping this server".format(s['server_name'],
s['path']))

View File

@ -272,7 +272,7 @@ class Helpers:
return "%.1f%s%s" % (num, 'Y', suffix)
@staticmethod
def check_path_exits(path: str):
def check_path_exists(path: str):
logger.debug('Looking for path: {}'.format(path))
if os.path.exists(path):
@ -463,13 +463,21 @@ class Helpers:
print(filename)
rel = os.path.join(folder, raw_filename)
if os.path.isdir(rel):
output += '<li>\n<span class="tree-caret">{}</span>\n<ul class="tree-nested">'.format(filename)
output += \
"""<li class="tree-item" data-path="{}">
\n<div data-path="{}" data-name="{}" class="tree-caret tree-ctx-item tree-folder">{}</div>
\n<ul class="tree-nested">"""\
.format(os.path.join(folder, filename), os.path.join(folder, filename), filename, filename)
output += helper.generate_tree(rel)
output += '</ul>\n</li>'
else:
console.debug('os.path.isdir(rel): "{}", rel: "{}"'.format(os.path.isdir(rel), rel))
output += '<li data-path="{}" onclick="clickOnFile(event)">{}</li>'\
.format(os.path.join(folder, filename), filename)
output += """<li
class="tree-item tree-ctx-item tree-file"
data-path="{}"
data-name="{}"
onclick="clickOnFile(event)">{}</li>""".format(os.path.join(folder, filename), filename, filename)
return output
@staticmethod

View File

@ -96,7 +96,7 @@ class Server:
console.critical("Server executable path: {} does not seem to exist".format(full_path))
helper.do_exit()
if not helper.check_path_exits(self.server_path):
if not helper.check_path_exists(self.server_path):
logger.critical("Server path: {} does not seem to exits".format(self.server_path))
console.critical("Server path: {} does not seem to exits".format(self.server_path))
helper.do_exit()

View File

@ -4,6 +4,7 @@ import tornado.web
import tornado.escape
import bleach
import os
import shutil
from app.classes.shared.console import console
from app.classes.shared.models import Users, installer
@ -101,12 +102,6 @@ class AjaxHandler(BaseHandler):
or not helper.check_file_exists(os.path.abspath(file_path)):
logger.warning("Invalid path in get_file ajax call")
console.warning("Invalid path in get_file ajax call")
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], file_path):
console.warning('1')
elif not helper.check_file_exists(os.path.abspath(file_path)):
console.warning('2')
else:
console.warning('3')
return False
file = open(file_path)
@ -117,6 +112,26 @@ class AjaxHandler(BaseHandler):
self.write(file_contents)
self.finish()
elif page == "get_tree":
server_id = self.get_argument('id', None)
if server_id is None:
logger.warning("Server ID not found in get_file ajax call")
console.warning("Server ID not found in get_file ajax call")
return False
else:
server_id = bleach.clean(server_id)
# does this server id exist?
if not db_helper.server_id_exists(server_id):
logger.warning("Server ID not found in get_file ajax call")
console.warning("Server ID not found in get_file ajax call")
return False
self.write(db_helper.get_server_data_by_id(server_id)['path'] + '\n' +
helper.generate_tree(db_helper.get_server_data_by_id(server_id)['path']))
self.finish()
def post(self, page):
user_data = json.loads(self.get_secure_cookie("user_data"))
error = bleach.clean(self.get_argument('error', "WTF Error!"))
@ -127,8 +142,8 @@ class AjaxHandler(BaseHandler):
}
if page == "send_command":
command = bleach.clean(self.get_body_argument('command', default=None, strip=True))
server_id = bleach.clean(self.get_argument('id'))
command = self.get_body_argument('command', default=None, strip=True)
server_id = self.get_argument('id')
if server_id is None:
logger.warning("Server ID not found in send_command ajax call")
@ -140,9 +155,125 @@ class AjaxHandler(BaseHandler):
if srv_obj.check_running():
srv_obj.send_command(command)
elif page == "save_file":
file_contents = bleach.clean(self.get_body_argument('file_contents', default=None, strip=True))
file_path = bleach.clean(self.get_body_argument('file_path', default=None, strip=True))
elif page == "create_file":
file_parent = self.get_body_argument('file_parent', default=None, strip=True)
file_name = self.get_body_argument('file_name', default=None, strip=True)
file_path = os.path.join(file_parent, file_name)
server_id = self.get_argument('id', None)
print(server_id)
if server_id is None:
logger.warning("Server ID not found in create_file ajax call")
console.warning("Server ID not found in create_file ajax call")
return False
else:
server_id = bleach.clean(server_id)
# does this server id exist?
if not db_helper.server_id_exists(server_id):
logger.warning("Server ID not found in create_file ajax call")
console.warning("Server ID not found in create_file ajax call")
return False
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], file_path) \
or helper.check_file_exists(os.path.abspath(file_path)):
logger.warning("Invalid path in create_file ajax call")
console.warning("Invalid path in create_file ajax call")
return False
# Create the file by opening it
with open(file_path, 'w') as file_object:
file_object.close()
elif page == "create_dir":
dir_parent = self.get_body_argument('dir_parent', default=None, strip=True)
dir_name = self.get_body_argument('dir_name', default=None, strip=True)
dir_path = os.path.join(dir_parent, dir_name)
server_id = self.get_argument('id', None)
print(server_id)
if server_id is None:
logger.warning("Server ID not found in create_dir ajax call")
console.warning("Server ID not found in create_dir ajax call")
return False
else:
server_id = bleach.clean(server_id)
# does this server id exist?
if not db_helper.server_id_exists(server_id):
logger.warning("Server ID not found in create_dir ajax call")
console.warning("Server ID not found in create_dir ajax call")
return False
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], dir_path) \
or helper.check_path_exists(os.path.abspath(dir_path)):
logger.warning("Invalid path in create_dir ajax call")
console.warning("Invalid path in create_dir ajax call")
return False
# Create the directory
os.mkdir(dir_path)
def delete(self, page):
if page == "del_file":
file_path = self.get_body_argument('file_path', default=None, strip=True)
server_id = self.get_argument('id', None)
print(server_id)
if server_id is None:
logger.warning("Server ID not found in del_file ajax call")
console.warning("Server ID not found in del_file ajax call")
return False
else:
server_id = bleach.clean(server_id)
# does this server id exist?
if not db_helper.server_id_exists(server_id):
logger.warning("Server ID not found in del_file ajax call")
console.warning("Server ID not found in del_file ajax call")
return False
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], file_path) \
or not helper.check_file_exists(os.path.abspath(file_path)):
logger.warning("Invalid path in del_file ajax call")
console.warning("Invalid path in del_file ajax call")
return False
# Delete the file
os.remove(file_path)
elif page == "del_dir":
dir_path = self.get_body_argument('dir_path', default=None, strip=True)
server_id = self.get_argument('id', None)
print(server_id)
if server_id is None:
logger.warning("Server ID not found in del_file ajax call")
console.warning("Server ID not found in del_file ajax call")
return False
else:
server_id = bleach.clean(server_id)
# does this server id exist?
if not db_helper.server_id_exists(server_id):
logger.warning("Server ID not found in del_file ajax call")
console.warning("Server ID not found in del_file ajax call")
return False
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], dir_path) \
or not helper.check_path_exists(os.path.abspath(dir_path)):
logger.warning("Invalid path in del_file ajax call")
console.warning("Invalid path in del_file ajax call")
return False
# Delete the file
# os.rmdir(dir_path)
shutil.rmtree(dir_path) # Removes also when there are contents
def put(self, page):
if page == "save_file":
file_contents = self.get_body_argument('file_contents', default=None, strip=True)
file_path = self.get_body_argument('file_path', default=None, strip=True)
server_id = self.get_argument('id', None)
print(file_contents)
print(file_path)
@ -170,3 +301,44 @@ class AjaxHandler(BaseHandler):
# Open the file in write mode and store the content in file_object
with open(file_path, 'w') as file_object:
file_object.write(file_contents)
elif page == "rename_item":
item_path = self.get_body_argument('item_path', default=None, strip=True)
new_item_name = self.get_body_argument('new_item_name', default=None, strip=True)
server_id = self.get_argument('id', None)
print(server_id)
if server_id is None:
logger.warning("Server ID not found in rename_item ajax call")
console.warning("Server ID not found in rename_item ajax call")
return False
else:
server_id = bleach.clean(server_id)
# does this server id exist?
if not db_helper.server_id_exists(server_id):
logger.warning("Server ID not found in rename_item ajax call (1)")
console.warning("Server ID not found in rename_item ajax call (1)")
return False
if item_path is None or new_item_name is None:
logger.warning("Invalid path in rename_item ajax call (2)")
console.warning("Invalid path in rename_item ajax call (2)")
return False
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], item_path) \
or not helper.check_path_exists(os.path.abspath(item_path)):
logger.warning("Invalid path in rename_item ajax call (3)")
console.warning("Invalid path in rename_item ajax call (3)")
return False
new_item_path = os.path.join(os.path.split(item_path)[0], new_item_name)
if not helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], new_item_path) \
or helper.check_path_exists(os.path.abspath(new_item_path)):
logger.warning("Invalid path 2 in rename_item ajax call")
console.warning("Invalid path 2 in rename_item ajax call")
return False
# RENAME
os.rename(item_path, new_item_path)

View File

@ -109,9 +109,6 @@ class PanelHandler(BaseHandler):
console.debug('not a valid subpage')
subpage = 'term'
console.debug('Subpage: "{}"'.format(subpage))
if subpage == 'files':
console.debug('Subpage is "files"')
page_data['tree_html'] = helper.generate_tree(db_helper.get_server_data_by_id(server_id)['path'])
# server_data isn't needed since the server_stats also pulls server data
# page_data['server_data'] = db_helper.get_server_data_by_id(server_id)

View File

@ -65,9 +65,82 @@
<noscript>
The file manager does not work without JavaScript
</noscript>
<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">
<a onclick="createFileE(event)" href="javascript:void(0)" id="createFile" href="#">Create file</a>
<a onclick="createDirE(event)" href="javascript:void(0)" id="createDir" href="#">Create directory</a>
<a onclick="renameItemE(event)" href="javascript:void(0)" id="renameItem" href="#">Rename</a>
<a onclick="deleteFileE(event)" href="javascript:void(0)" id="deleteFile" href="#">Delete</a>
<a onclick="deleteDirE(event)" href="javascript:void(0)" id="deleteDir" href="#">Delete</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) */
}
/* 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;
}
}
</style>
<ul class="tree-view">
<li>
<span class="tree-caret">Files</span>
<div class="tree-caret tree-ctx-item files-tree-title">Files</div>
<ul class="tree-nested" id="files-tree">
<li>Error while getting files</li>
@ -113,28 +186,6 @@
<h3 id="file_warn"></h3>
<button class="btn btn-success" onclick="save()">Save</button>
</div>
<script>
var encoded = `{{ data['tree_html'] }}`;
var elem = document.createElement('textarea');
elem.innerHTML = encoded;
var decoded = elem.value;
document.getElementById('files-tree').innerHTML = decoded;
var toggler = document.getElementsByClassName("tree-caret");
var i;
for (i = 0; i < toggler.length; i++) {
toggler[i].addEventListener("click", function() {
this.parentElement.querySelector(".tree-nested").classList.toggle("d-block");
this.classList.toggle("tree-caret-down");
});
}
</script>
</div>
</div>
@ -316,7 +367,7 @@
var token = getCookie("_xsrf")
$.ajax({
type: "POST",
type: "PUT",
headers: {'X-XSRFToken': token},
url: '/ajax/save_file?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
data: {
@ -330,6 +381,277 @@
});
}
function createFile(parent, name, callback) {
var token = getCookie("_xsrf")
$.ajax({
type: "POST",
headers: {'X-XSRFToken': token},
url: '/ajax/create_file?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
data: {
file_parent: parent,
file_name: name
},
success: function(data){
console.log("got response:");
console.log(data);
callback();
},
});
}
function createDir(parent, name, callback) {
var token = getCookie("_xsrf")
$.ajax({
type: "POST",
headers: {'X-XSRFToken': token},
url: '/ajax/create_dir?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
data: {
dir_parent: parent,
dir_name: name
},
success: function(data){
console.log("got response:");
console.log(data);
callback();
},
});
}
function renameItem(path, name, callback) {
var token = getCookie("_xsrf")
$.ajax({
type: "PUT",
headers: {'X-XSRFToken': token},
url: '/ajax/rename_item?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
data: {
item_path: path,
new_item_name: name
},
success: function(data){
console.log("got response:");
console.log(data);
callback();
},
});
}
function deleteFile(path, callback) {
console.log('Deleting: ' + path)
var token = getCookie("_xsrf")
$.ajax({
type: "DELETE",
headers: {'X-XSRFToken': token},
url: '/ajax/del_file?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
data: {
file_path: path
},
success: function(data){
console.log("got response:");
console.log(data);
callback();
},
});
}
function deleteDir(path, callback) {
var token = getCookie("_xsrf")
$.ajax({
type: "DELETE",
headers: {'X-XSRFToken': token},
url: '/ajax/del_dir?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
data: {
dir_path: path
},
success: function(data){
console.log("got response:");
console.log(data);
callback();
},
});
}
function getTreeView() {
$.ajax({
type: "GET",
url: '/ajax/get_tree?id={{ data['server_stats'][0]['server_id']['server_id'] }}',
dataType: 'text',
success: function(data){
console.log("got response:");
console.log(data);
dataArr = data.split('\n');
serverDir = dataArr.shift(); // Remove & return first element (server directory)
text = dataArr.join('\n');
document.getElementById('files-tree').innerHTML = text;
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-path', serverDir);
document.getElementsByClassName('files-tree-title')[0].setAttribute('data-name', 'Files');
setTimeout(function () {setTreeViewContext()}, 1000);
var toggler = document.getElementsByClassName("tree-caret");
var i;
for (i = 0; i < toggler.length; i++) {
if (toggler[i].classList.contains('files-tree-title')) continue;
toggler[i].addEventListener("click", function caretListener() {
this.parentElement.querySelector(".tree-nested").classList.toggle("d-block");
this.classList.toggle("tree-caret-down");
});
}
},
});
}
function setTreeViewContext() {
var treeItems = document.getElementsByClassName('tree-ctx-item');
for (var i = 0; i < treeItems.length; i++) {
var treeItem = treeItems[i];
treeItem.addEventListener('contextmenu', function contextListener(event) {
event.preventDefault();
var ctxmenuPath = event.target.getAttribute('data-path');
var ctxmenuName = event.target.getAttribute('data-name');
if (!ctxmenuPath) {
console.log({ 'event.target': event.target, ctxmenuPath });
return;
}
$('#renameItem').show();
var isDir = event.target.classList.contains('tree-folder');
$('#createFile').toggle(isDir);
$('#createDir').toggle(isDir);
$('#deleteDir').toggle(isDir);
var isFile = event.target.classList.contains('tree-file');
$('#deleteFile').toggle(isFile);
console.log({ 'event.target': event.target, isDir, isFile });
if(event.target.classList.contains('files-tree-title')) {
$('#createFile').show();
$('#createDir').show();
$('#renameItem').hide();
$('#deleteDir').hide();
$('#deleteFile').hide();
}
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%";
})
}
}
function createFileE(event) {
bootbox.prompt('What name do you want for the new file?', function(result) {
path = event.target.parentElement.getAttribute('data-path');
name = event.target.parentElement.getAttribute('data-name');
if (!result) return;
createFile(path, result, function () {
getTreeView()
document.getElementById('files-tree-nav').style.height = '0%';
});
})
}
function createDirE(event) {
bootbox.prompt('What name do you want for the new directory?', function(result) {
path = event.target.parentElement.getAttribute('data-path');
name = event.target.parentElement.getAttribute('data-name');
if (!result) return;
createDir(path, result, function () {
getTreeView()
document.getElementById('files-tree-nav').style.height = '0%';
});
})
}
function renameItemE(event) {
bootbox.prompt('What should the new name be?', function(result) {
path = event.target.parentElement.getAttribute('data-path');
name = event.target.parentElement.getAttribute('data-name');
if (!result) return;
renameItem(path, result, function () {
getTreeView()
document.getElementById('files-tree-nav').style.height = '0%';
});
})
}
function deleteFileE(event) {
path = event.target.parentElement.getAttribute('data-path');
name = event.target.parentElement.getAttribute('data-name');
bootbox.confirm({
size: "",
title: "Are you sure you want to delete " + name + "?",
closeButton: false,
message: "You are deleting \"" + path + "\"!<br/><br/>This action will be irreversible and it'll be lost forever!",
buttons: {
confirm: {
label: 'Yes, I understand the consequences',
className: 'btn-danger'
},
cancel: {
label: 'No',
className: 'btn-link'
}
},
callback: function(result) {
if (!result) return;
deleteFile(path, function () {
getTreeView()
document.getElementById('files-tree-nav').style.height = '0%';
});
}
});
}
function deleteDirE(event) {
path = event.target.parentElement.getAttribute('data-path');
name = event.target.parentElement.getAttribute('data-name');
bootbox.confirm({
size: "",
title: "Are you sure you want to delete " + name + "?",
closeButton: false,
message: "You are deleting \"" + path + "\"!<br/><br/>This action will be irreversible and it'll be lost forever!",
buttons: {
confirm: {
label: 'Yes, I understand the consequences',
className: 'btn-danger'
},
cancel: {
label: 'No',
className: 'btn-link'
}
},
callback: function(result) {
if (!result) return;
deleteDir(path, function () {
getTreeView()
document.getElementById('files-tree-nav').style.height = '0%';
});
}
});
}
document.getElementsByClassName('files-tree-title')[0].addEventListener("click", function caretListener() {
this.parentElement.querySelector(".tree-nested").classList.toggle("d-block");
this.classList.toggle("tree-caret-down");
});
getTreeView();
setTreeViewContext();
</script>
{% end %}