Merge branch 'dev' into dev-Silversthorn

This commit is contained in:
Silversthorn 2021-08-21 16:44:02 +02:00
commit 31436bdc62
14 changed files with 336 additions and 114 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@ -306,7 +306,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')
@ -559,14 +598,6 @@ class Helpers:
return json.loads(content)
@staticmethod
def zip_directory(file, path, compression=zipfile.ZIP_LZMA):
with zipfile.ZipFile(file, 'w', compression) as zf:
for root, dirs, files in os.walk(path):
for file in files:
zf.write(os.path.join(root, file),
os.path.relpath(os.path.join(root, file),
os.path.join(path, '..')))
@staticmethod
def copy_files(source, dest):
if os.path.isfile(source):
shutil.copyfile(source, dest)

View File

@ -321,7 +321,6 @@ class db_shortcuts:
def get_all_authorized_servers(user_id):
user_servers = User_Servers.select().where(User_Servers.user_id == user_id)
user_roles = User_Roles.select().where(User_Roles.user_id == user_id)
server_data = []
roles_list = []
role_server = []
server_data = []
@ -596,6 +595,11 @@ class db_shortcuts:
def add_user_server(server_id, user_id, us_permissions):
servers = User_Servers.insert({User_Servers.server_id: server_id, User_Servers.user_id: user_id, User_Servers.permissions: us_permissions}).execute()
return servers
@staticmethod
def add_role_server(server_id, role_id):
servers = Role_Servers.insert({Role_Servers.server_id: server_id, Role_Servers.role_id: role_id}).execute()
return servers
@staticmethod

View File

@ -11,6 +11,8 @@ import schedule
import logging.config
import zipfile
from threading import Thread
import shutil
import zlib
import html
@ -416,17 +418,15 @@ class Server:
self.is_backingup = True
conf = db_helper.get_backup_config(self.server_id)
try:
backup_filename = "{}/{}.zip".format(self.settings['backup_path'], datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'))
backup_filename = "{}/{}".format(self.settings['backup_path'], datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'))
logger.info("Creating backup of server '{}' (ID#{}) at '{}'".format(self.settings['server_name'], self.server_id, backup_filename))
helper.zip_directory(backup_filename, self.server_path)
while len(self.list_backups()) > conf["max_backups"]:
shutil.make_archive(backup_filename, 'zip', self.server_path)
while len(self.list_backups()) > conf["max_backups"] and conf["max_backups"] > 0:
backup_list = self.list_backups()
oldfile = backup_list[0]
backup_path = self.settings['backup_path']
old_file_name = oldfile['path']
back_old_file = os.path.join(backup_path, old_file_name)
logger.info("Removing old backup '{}'".format(oldfile))
os.remove(back_old_file)
oldfile_path = "{}/{}".format(conf['backup_path'], oldfile['path'])
logger.info("Removing old backup '{}'".format(oldfile['path']))
os.remove(oldfile_path)
self.is_backingup = False
logger.info("Backup of server: {} completed".format(self.name))
return
@ -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

@ -113,16 +113,6 @@ class TasksManager:
elif command == "update_executable":
svr.jar_update()
elif command == "delete_server":
logger.info(
"Removing server from panel for server: {}".format(c['server_id']['server_name']))
self.controller.remove_server(c['server_id']['server_id'], False)
elif command == "delete_server_files":
logger.info(
"Removing server and all associated files for server: {}".format(c['server_id']['server_name']))
self.controller.remove_server(c['server_id']['server_id'], True)
db_helper.mark_command_complete(c.get('command_id', None))
time.sleep(1)

View File

@ -1,5 +1,9 @@
import json
import logging
import tempfile
import threading
import zipfile
import tornado.web
import tornado.escape
import bleach
@ -183,10 +187,58 @@ 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)
files = self.request.files['files']
upload_thread = threading.Thread(target=self.do_upload, daemon=True, name=files[0]['filename'],
args=(server_id, path, files))
upload_thread.start()
self.redirect("/panel/server_detail?id={}&subpage=files".format(server_id))
def do_upload(self, server_id, path, files):
if helper.in_path(db_helper.get_server_data_by_id(server_id)['path'], path):
try:
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")
except Exception as e:
logger.error("Error while uploading files: {}".format(e))
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":
@ -229,6 +281,18 @@ class AjaxHandler(BaseHandler):
# os.rmdir(dir_path) # Would only remove empty directories
shutil.rmtree(dir_path) # Removes also when there are contents
elif page == "delete_server":
server_id = self.get_argument('id', None)
logger.info(
"Removing server from panel for server: {}".format(db_helper.get_server_friendly_name(server_id)))
self.controller.remove_server(server_id, False)
elif page == "delete_server_files":
server_id = self.get_argument('id', None)
logger.info(
"Removing server and all associated files for server: {}".format(db_helper.get_server_friendly_name(server_id)))
self.controller.remove_server(server_id, True)
@tornado.web.authenticated
def put(self, page):
if page == "save_file":

View File

@ -338,17 +338,9 @@ class PanelHandler(BaseHandler):
elif page == "edit_user":
user_id = self.get_argument('id', None)
user_servers = db_helper.get_authorized_servers(user_id)
role_servers = db_helper.get_authorized_servers_from_roles(user_id)
page_role_servers = []
servers = set()
for server in user_servers:
flag = False
for rserver in role_servers:
if rserver['server_id'] == server['server_id']:
flag = True
if not flag:
servers.add(server['server_id'])
for server in role_servers:
page_role_servers.append(server['server_id'])
page_data['new_user'] = False
@ -657,7 +649,6 @@ class PanelHandler(BaseHandler):
"enabled": enabled,
"regen_api": regen_api,
"roles": roles,
"servers": servers,
}
db_helper.update_user(user_id, user_data=user_data)
@ -713,7 +704,6 @@ class PanelHandler(BaseHandler):
user_id = db_helper.add_user(username, password=password0, enabled=enabled)
user_data = {
"roles": roles,
"servers": servers,
}
db_helper.update_user(user_id, user_data)

View File

@ -195,8 +195,11 @@ class ServerHandler(BaseHandler):
return
server_type, server_version = server_parts
# todo: add server type check here and call the correct server add functions if not a jar
role_ids = db_helper.get_user_roles_id(exec_user_id)
new_server_id = self.controller.create_jar_server(server_type, server_version, server_name, min_mem, max_mem, port)
db_helper.add_user_server(new_server_id, exec_user_id, "11111111")
for role in role_ids:
db_helper.add_role_server(new_server_id, role)
db_helper.add_to_audit_log(exec_user_data['user_id'],
"created a {} {} server named \"{}\"".format(server_version, str(server_type).capitalize(), server_name), # Example: Admin created a 1.16.5 Bukkit server named "survival"
new_server_id,

View File

@ -79,4 +79,4 @@ body { background-color: var(--dark) !important; /* Firefox */ }
.actions_serverlist > a > i {
cursor: pointer;
}
}

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

@ -121,12 +121,10 @@
<tr>
<td>{{ server['server_name'] }}</td>
<td>
{% if server['server_id'] in data['servers'] %}
<input type="checkbox" class="form-check-input" id="server_{{ server['server_id'] }}_access" name="server_{{ server['server_id'] }}_access" checked="" value="1">
{% elif server['server_id'] in data['role-servers'] %}
{% if server['server_id'] in data['role-servers'] %}
<input type="checkbox" class="form-check-input" id="server_{{ server['server_id'] }}_access" name="server_{{ server['server_id'] }}_access" checked="" value="" disabled>
{% else %}
<input type="checkbox" class="form-check-input" id="server_{{ server['server_id'] }}_access" name="server_{{ server['server_id'] }}_access" value="1">
<input type="checkbox" class="form-check-input" id="server_{{ server['server_id'] }}_access" name="server_{{ server['server_id'] }}_access" value="1" disabled>
{% end %}
</td>
</tr>

View File

@ -173,6 +173,35 @@
});
function deleteServerE(callback) {
var token = getCookie("_xsrf")
$.ajax({
type: "DELETE",
headers: {'X-XSRFToken': token},
url: '/ajax/delete_server?id={{ data['server_stats']['server_id']['server_id'] }}',
data: {
},
success: function(data){
console.log("got response:");
console.log(data);
},
});
}
function deleteServerFilesE(path, callback) {
var token = getCookie("_xsrf")
$.ajax({
type: "DELETE",
headers: {'X-XSRFToken': token},
url: '/ajax/delete_server_files?id={{ data['server_stats']['server_id']['server_id'] }}',
data: {
},
success: function(data){
console.log("got response:");
console.log(data);
},
});
}
let server_id = '{{ data['server_stats']['server_id']['server_id'] }}';
function send_command (server_id, command){
@ -219,7 +248,7 @@ let server_id = '{{ data['server_stats']['server_id']['server_id'] }}';
},
callback: function(result) {
if (!result){
send_command(server_id, 'delete_server');
deleteServerE()
setTimeout(function(){ window.location = '/panel/dashboard'; }, 5000);
bootbox.dialog({
backdrop: true,
@ -230,7 +259,7 @@ let server_id = '{{ data['server_stats']['server_id']['server_id'] }}';
return;}
else{
send_command(server_id, 'delete_server_files');
deleteServerFilesE();
setTimeout(function(){ window.location = '/panel/dashboard'; }, 5000);
bootbox.dialog({
backdrop: true,

View File

@ -42,7 +42,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">
@ -51,55 +50,75 @@
<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;
}
.overlay::-webkit-scrollbar {
display: none;
}
/* Hide scrollbar for IE, Edge and Firefox */
.overlay {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
/* 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 */
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 */
@ -489,6 +508,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",
@ -542,6 +626,7 @@
$('#createFile').toggle(isDir);
$('#createDir').toggle(isDir);
$('#deleteDir').toggle(isDir);
$('#upload').toggle(isDir);
var isFile = event.target.classList.contains('tree-file');
$('#deleteFile').toggle(isFile);
@ -553,20 +638,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) {
@ -576,7 +687,7 @@
createFile(path, result, function () {
getTreeView()
document.getElementById('files-tree-nav').style.height = '0%';
document.getElementById('files-tree-nav').style.display = 'none';
});
})
}
@ -589,7 +700,7 @@
createDir(path, result, function () {
getTreeView()
document.getElementById('files-tree-nav').style.height = '0%';
document.getElementById('files-tree-nav').style.display = 'none';
});
})
}
@ -602,10 +713,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');
@ -629,7 +744,7 @@
if (!result) return;
deleteFile(path, function () {
getTreeView()
document.getElementById('files-tree-nav').style.height = '0%';
document.getElementById('files-tree-nav').style.display = 'none';
});
}
});
@ -657,7 +772,7 @@
if (!result) return;
deleteDir(path, function () {
getTreeView()
document.getElementById('files-tree-nav').style.height = '0%';
document.getElementById('files-tree-nav').style.display = 'none';
});
}
});