diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..a2f64789 Binary files /dev/null and b/.DS_Store differ diff --git a/app/classes/shared/controller.py b/app/classes/shared/controller.py index 26d0411f..a11e6fa5 100644 --- a/app/classes/shared/controller.py +++ b/app/classes/shared/controller.py @@ -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: diff --git a/app/classes/shared/helpers.py b/app/classes/shared/helpers.py index 5469bd06..19e6504c 100644 --- a/app/classes/shared/helpers.py +++ b/app/classes/shared/helpers.py @@ -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') diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py index e9a9f1c4..6baa8713 100644 --- a/app/classes/shared/server.py +++ b/app/classes/shared/server.py @@ -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) diff --git a/app/classes/web/ajax_handler.py b/app/classes/web/ajax_handler.py index c84983ad..cbf7c9e6 100644 --- a/app/classes/web/ajax_handler.py +++ b/app/classes/web/ajax_handler.py @@ -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": diff --git a/app/frontend/static/assets/css/crafty.css b/app/frontend/static/assets/css/crafty.css index ea94ad5c..c0812602 100644 --- a/app/frontend/static/assets/css/crafty.css +++ b/app/frontend/static/assets/css/crafty.css @@ -79,4 +79,60 @@ body { background-color: var(--dark) !important; /* Firefox */ } .actions_serverlist > a > i { cursor: pointer; -} \ No newline at end of file +} + /* 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; + } + } \ No newline at end of file diff --git a/app/frontend/static/assets/css/shared/style.css b/app/frontend/static/assets/css/shared/style.css index a7093450..35a40ab2 100755 --- a/app/frontend/static/assets/css/shared/style.css +++ b/app/frontend/static/assets/css/shared/style.css @@ -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; } diff --git a/app/frontend/templates/panel/server_files.html b/app/frontend/templates/panel/server_files.html index fe8501f9..b135b527 100644 --- a/app/frontend/templates/panel/server_files.html +++ b/app/frontend/templates/panel/server_files.html @@ -72,7 +72,6 @@
- ×
@@ -81,55 +80,67 @@ {{ translate('serverFiles', 'rename') }} {{ translate('serverFiles', 'delete') }} {{ translate('serverFiles', 'delete') }} + Upload Files + Unzip + Close