diff --git a/CHANGELOG.md b/CHANGELOG.md index ec750a2d..f15d616c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,18 @@ # Changelog ## --- [4.0.21] - 2023/TBD ### New features -TBD +- Add better feedback for uploads with a progress bar ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/546)) +- Add ignored exit codes for crash detection ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/553)) ### Bug fixes -TBD +- Fix exception related to page data on server start ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/544)) +- Fix logical issue with uploading dynamic files ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/555)) +- Fix backups failing by correctly using tz objects ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/556)) ### Tweaks -TBD +- Cleanup authentication helpers ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/545)) +- Optimize file upload progress WS ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/546)) +- Truncate sidebar servers to a max of 10 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/552)) ### Lang -TBD +- Add additional translations to backups page strings ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/543))

## --- [4.0.20] - 2023/01/29 diff --git a/app/classes/models/servers.py b/app/classes/models/servers.py index 69d05866..96f606a9 100644 --- a/app/classes/models/servers.py +++ b/app/classes/models/servers.py @@ -40,6 +40,7 @@ class Servers(BaseModel): show_status = BooleanField(default=1) created_by = IntegerField(default=-100) shutdown_timeout = IntegerField(default=60) + ignored_exits = CharField(default="0") class Meta: table_name = "servers" diff --git a/app/classes/shared/authentication.py b/app/classes/shared/authentication.py index d9b2c053..1a809bfc 100644 --- a/app/classes/shared/authentication.py +++ b/app/classes/shared/authentication.py @@ -13,7 +13,6 @@ logger = logging.getLogger(__name__) class Authentication: def __init__(self, helper): self.helper = helper - self.secret = "my secret" try: self.secret = ManagementController.get_crafty_api_key() if self.secret == "": diff --git a/app/classes/shared/helpers.py b/app/classes/shared/helpers.py index 5964d09a..e5d609c2 100644 --- a/app/classes/shared/helpers.py +++ b/app/classes/shared/helpers.py @@ -1023,7 +1023,7 @@ class Helpers: for item in file_list: if os.path.isdir(os.path.join(folder, item)): dir_list.append(item) - elif str(item) != "crafty.sqlite": + elif str(item) != self.ignored_names: unsorted_files.append(item) file_list = sorted(dir_list, key=str.casefold) + sorted( unsorted_files, key=str.casefold @@ -1064,7 +1064,7 @@ class Helpers: for item in file_list: if os.path.isdir(os.path.join(folder, item)): dir_list.append(item) - elif str(item) != "crafty.sqlite": + elif str(item) != self.ignored_names: unsorted_files.append(item) file_list = sorted(dir_list, key=str.casefold) + sorted( unsorted_files, key=str.casefold diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py index 3a1ae2d6..03f39d23 100644 --- a/app/classes/shared/server.py +++ b/app/classes/shared/server.py @@ -12,6 +12,8 @@ import html import urllib.request import glob +from zoneinfo import ZoneInfo + # TZLocal is set as a hidden import on win pipeline from tzlocal import get_localzone from tzlocal.utils import ZoneInfoNotFoundError @@ -136,7 +138,7 @@ class ServerInstance: logger.error( "Could not capture time zone from system. Falling back to Europe/London" ) - self.tz = "Europe/London" + self.tz = ZoneInfo("Europe/London") self.server_scheduler = BackgroundScheduler(timezone=str(self.tz)) self.server_scheduler.start() self.backup_thread = threading.Thread( @@ -732,26 +734,26 @@ class ServerInstance: self.server_thread.join() def stop_server(self): - if self.settings["stop_command"]: - self.send_command(self.settings["stop_command"]) - if self.settings["crash_detection"]: - # remove crash detection watcher - logger.info(f"Removing crash watcher for server {self.name}") - try: - self.server_scheduler.remove_job("c_" + str(self.server_id)) - except: - logger.error( - f"Removing crash watcher for server {self.name} failed. " - f"Assuming it was never started." - ) - else: - # windows will need to be handled separately for Ctrl+C - self.process.terminate() running = self.check_running() if not running: logger.info(f"Can't stop server {self.name} if it's not running") Console.info(f"Can't stop server {self.name} if it's not running") return + if self.settings["crash_detection"]: + # remove crash detection watcher + logger.info(f"Removing crash watcher for server {self.name}") + try: + self.server_scheduler.remove_job("c_" + str(self.server_id)) + except: + logger.error( + f"Removing crash watcher for server {self.name} failed. " + f"Assuming it was never started." + ) + if self.settings["stop_command"]: + self.send_command(self.settings["stop_command"]) + else: + # windows will need to be handled separately for Ctrl+C + self.process.terminate() i = 0 # caching the name and pid number @@ -921,7 +923,7 @@ class ServerInstance: if running: return # check the exit code -- This could be a fix for /stop - if self.process.returncode == 0: + if str(self.process.returncode) in self.settings["ignored_exits"].split(","): logger.warning( f"Process {self.process.pid} exited with code " f"{self.process.returncode}. This is considered a clean exit" diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index ddfdd5a2..7d82933e 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -1588,6 +1588,8 @@ class PanelHandler(BaseHandler): crash_detection = int(float(self.get_argument("crash_detection", "0"))) logs_delete_after = int(float(self.get_argument("logs_delete_after", "0"))) java_selection = self.get_argument("java_selection", None) + # make sure there is no whitespace + ignored_exits = self.get_argument("ignored_exits", "").replace(" ", "") # subpage = self.get_argument('subpage', None) server_id = self.check_server_id() @@ -1672,6 +1674,7 @@ class PanelHandler(BaseHandler): server_obj.auto_start = auto_start server_obj.crash_detection = crash_detection server_obj.logs_delete_after = logs_delete_after + server_obj.ignored_exits = ignored_exits failed = False for servers in self.controller.servers.failed_servers: if servers["server_id"] == int(server_id): diff --git a/app/classes/web/server_handler.py b/app/classes/web/server_handler.py index 1c0d5962..da854cc5 100644 --- a/app/classes/web/server_handler.py +++ b/app/classes/web/server_handler.py @@ -183,6 +183,7 @@ class ServerHandler(BaseHandler): "version_data": "version_data_here", # TODO "user_data": exec_user, "show_contribute": self.helper.get_setting("show_contribute_link", True), + "background": self.controller.cached_login, "lang": self.controller.users.get_user_lang_by_id(exec_user["user_id"]), "lang_page": Helpers.get_lang_page( self.controller.users.get_user_lang_by_id(exec_user["user_id"]) diff --git a/app/classes/web/status_handler.py b/app/classes/web/status_handler.py index 410e3a36..ff4eb290 100644 --- a/app/classes/web/status_handler.py +++ b/app/classes/web/status_handler.py @@ -7,12 +7,12 @@ logger = logging.getLogger(__name__) class StatusHandler(BaseHandler): def get(self): - page_data = {"background": self.controller.cached_login} - page_data["lang"] = self.helper.get_setting("language") - page_data["lang_page"] = self.helper.get_lang_page( - self.helper.get_setting("language") - ) - page_data["servers"] = self.controller.servers.get_all_servers_stats() + page_data = { + "background": self.controller.cached_login, + "lang": self.helper.get_setting("language"), + "lang_page": self.helper.get_lang_page(self.helper.get_setting("language")), + "servers": self.controller.servers.get_all_servers_stats(), + } running = 0 for srv in page_data["servers"]: if srv["stats"]["running"]: diff --git a/app/classes/web/upload_handler.py b/app/classes/web/upload_handler.py index 785d5783..11e1c4b8 100644 --- a/app/classes/web/upload_handler.py +++ b/app/classes/web/upload_handler.py @@ -52,18 +52,19 @@ class UploadHandler(BaseHandler): f"User with ID {user_id} attempted to upload a file that" f" exceeded the max body size." ) - self.helper.websocket_helper.broadcast_user( - user_id, - "send_start_error", + + return self.finish_json( + 413, { - "error": self.helper.translation.translate( + "status": "error", + "error": "TOO LARGE", + "info": self.helper.translation.translate( "error", "fileTooLarge", self.controller.users.get_user_lang_by_id(user_id), ), }, ) - return self.do_upload = True if superuser: @@ -141,48 +142,50 @@ class UploadHandler(BaseHandler): f"User with ID {user_id} attempted to upload a file that" f" exceeded the max body size." ) - self.helper.websocket_helper.broadcast_user( - user_id, - "send_start_error", + + return self.finish_json( + 413, { - "error": self.helper.translation.translate( + "status": "error", + "error": "TOO LARGE", + "info": self.helper.translation.translate( "error", "fileTooLarge", self.controller.users.get_user_lang_by_id(user_id), ), }, ) - return self.do_upload = True if not superuser: - self.helper.websocket_helper.broadcast_user( - user_id, - "send_start_error", + return self.finish_json( + 401, { - "error": self.helper.translation.translate( + "status": "error", + "error": "UNAUTHORIZED ACCESS", + "info": self.helper.translation.translate( "error", "superError", self.controller.users.get_user_lang_by_id(user_id), ), }, ) - return if not self.request.headers.get("X-Content-Type", None).startswith( "image/" ): - self.helper.websocket_helper.broadcast_user( - user_id, - "send_start_error", + + return self.finish_json( + 415, { - "error": self.helper.translation.translate( + "status": "error", + "error": "TYPE ERROR", + "info": self.helper.translation.translate( "error", "fileError", self.controller.users.get_user_lang_by_id(user_id), ), }, ) - return if user_id is None: logger.warning("User ID not found in upload handler call") Console.warning("User ID not found in upload handler call") @@ -219,18 +222,19 @@ class UploadHandler(BaseHandler): f"User with ID {user_id} attempted to upload a file that" f" exceeded the max body size." ) - self.helper.websocket_helper.broadcast_user( - user_id, - "send_start_error", + + return self.finish_json( + 413, { - "error": self.helper.translation.translate( + "status": "error", + "error": "TOO LARGE", + "info": self.helper.translation.translate( "error", "fileTooLarge", self.controller.users.get_user_lang_by_id(user_id), ), }, ) - return self.do_upload = True if superuser: diff --git a/app/frontend/templates/base.html b/app/frontend/templates/base.html index ed4c5a74..4dbfd719 100755 --- a/app/frontend/templates/base.html +++ b/app/frontend/templates/base.html @@ -15,7 +15,7 @@ + href="https://cdn.datatables.net/v/bs4/dt-1.10.22/fh-3.1.7/r-2.2.6/sc-2.0.3/sp-1.2.2/datatables.min.css" /> @@ -41,14 +41,14 @@ + integrity="sha512-ElRFoEQdI5Ht6kZvyzXhYG9NqjtkmlkfYk0wr6wHxU9JEHakS7UJZNeml5ALk+8IKlU6jDgMabC3vkumRokgJA==" + crossorigin="anonymous" referrerpolicy="no-referrer"> + integrity="sha512-UXumZrZNiOwnTcZSHLOfcTs0aos2MzBWHXOHOuB0J/R44QB0dwY5JgfbvljXcklVf65Gc4El6RjZ+lnwd2az2g==" + crossorigin="anonymous" referrerpolicy="no-referrer"> + integrity="sha512-klQv6lz2YR+MecyFYMFRuU2eAl8IPRo6zHnsc9n142TJuJHS8CG0ix4Oq9na9ceeg1u5EkBfZsFcV3U7J51iew==" + crossorigin="anonymous" referrerpolicy="no-referrer"> @@ -82,7 +82,7 @@ {% include notify.html %} @@ -174,7 +174,7 @@ + src="https://cdn.datatables.net/v/bs4/dt-1.10.22/fh-3.1.7/r-2.2.6/sc-2.0.3/sp-1.2.2/datatables.min.js"> diff --git a/app/frontend/templates/main_menu.html b/app/frontend/templates/main_menu.html index 6f5da3d0..e5e8f7ab 100644 --- a/app/frontend/templates/main_menu.html +++ b/app/frontend/templates/main_menu.html @@ -75,13 +75,13 @@
  • ' + name + '
  • '); - } else { + } else if (expanded == true) { $(par_el).append('
  • ' + name + '
  • '); } setTreeViewContext(); @@ -751,7 +769,22 @@ $(`#upload-progress-bar-${i + 1}`).html('') } else { - alert('Upload failed with response: ' + event.target.responseText); + let response_text = JSON.parse(event.target.responseText); + var x = document.querySelector('.bootbox'); + if (x) { + x.remove() + } + var x = document.querySelector('.modal-content'); + if (x) { + x.remove() + } + console.log(JSON.parse(event.target.responseText).info) + bootbox.alert({ + message: JSON.parse(event.target.responseText).info, + callback: function () { + window.location.reload(); + } + }); doUpload = false; } }, false); @@ -788,7 +821,7 @@ var height = files.files.length * 50; var waitMessage = '

    ' + - '' + + ' ' + "{{ translate('serverFiles', 'waitUpload', data['lang']) }}" + '
    ' + '' + "{{ translate('serverFiles', 'stayHere', data['lang']) }}" + '' + '

    ' + @@ -826,7 +859,7 @@ await sendFile(files.files[i], path, serverId, nFiles - i - 1, i, (progress) => { $(`#upload-progress-bar-${i + 1}`).attr('aria-valuenow', progress) - $(`#upload-progress-bar-${i + 1}`).css('width', progress + '%') + $(`#upload-progress-bar-${i + 1}`).css('width', progress + '%'); }); } hideUploadBox(); diff --git a/app/frontend/templates/server/bedrock_wizard.html b/app/frontend/templates/server/bedrock_wizard.html index 38d0b021..9c9db1a1 100644 --- a/app/frontend/templates/server/bedrock_wizard.html +++ b/app/frontend/templates/server/bedrock_wizard.html @@ -562,7 +562,7 @@ var file; function sendFile() { file = $("#file")[0].files[0] - document.getElementById("upload_input").innerHTML = '
     
    ' + document.getElementById("upload_input").innerHTML = '
     
    ' let xmlHttpRequest = new XMLHttpRequest(); let token = getCookie("_xsrf") let fileName = encodeURIComponent(file.name) @@ -571,6 +571,15 @@ let size = file.size let type = 'server_import' + xmlHttpRequest.upload.addEventListener('progress', function (e) { + + if (e.loaded <= size) { + var percent = Math.round(e.loaded / size * 100); + $(`#upload-progress-bar`).css('width', percent + '%'); + $(`#upload-progress-bar`).html(percent + '%'); + } + }); + xmlHttpRequest.open('POST', target, true); xmlHttpRequest.setRequestHeader('X-Content-Type', mimeType); xmlHttpRequest.setRequestHeader('X-XSRFToken', token); @@ -585,7 +594,15 @@ document.getElementById("lower_half").style.visibility = "visible"; } else { - alert('Upload failed with response: ' + event.target.responseText); + let response_text = JSON.parse(event.target.responseText); + var x = document.querySelector('.bootbox'); + console.log(JSON.parse(event.target.responseText).info) + bootbox.alert({ + message: JSON.parse(event.target.responseText).info, + callback: function () { + window.location.reload(); + } + }); doUpload = false; } }, false); diff --git a/app/frontend/templates/server/wizard.html b/app/frontend/templates/server/wizard.html index 0f70d6b1..b562c8c3 100644 --- a/app/frontend/templates/server/wizard.html +++ b/app/frontend/templates/server/wizard.html @@ -804,7 +804,7 @@ var file; function sendFile() { file = $("#file")[0].files[0] - document.getElementById("upload_input").innerHTML = '
     
    ' + document.getElementById("upload_input").innerHTML = '
     
    ' let xmlHttpRequest = new XMLHttpRequest(); let token = getCookie("_xsrf") let fileName = file.name @@ -813,6 +813,15 @@ let size = file.size let type = 'server_import' + xmlHttpRequest.upload.addEventListener('progress', function (e) { + + if (e.loaded <= size) { + var percent = Math.round(e.loaded / size * 100); + $(`#upload-progress-bar`).css('width', percent + '%'); + $(`#upload-progress-bar`).html(percent + '%'); + } + }); + xmlHttpRequest.open('POST', target, true); xmlHttpRequest.setRequestHeader('X-Content-Type', mimeType); xmlHttpRequest.setRequestHeader('X-XSRFToken', token); @@ -827,7 +836,15 @@ document.getElementById("lower_half").style.visibility = "visible"; } else { - alert('Upload failed with response: ' + event.target.responseText); + let response_text = JSON.parse(event.target.responseText); + var x = document.querySelector('.bootbox'); + console.log(JSON.parse(event.target.responseText).info) + bootbox.alert({ + message: JSON.parse(event.target.responseText).info, + callback: function () { + window.location.reload(); + } + }); doUpload = false; } }, false); diff --git a/app/migrations/20220223_ignored_exits.py b/app/migrations/20220223_ignored_exits.py new file mode 100644 index 00000000..e4647925 --- /dev/null +++ b/app/migrations/20220223_ignored_exits.py @@ -0,0 +1,16 @@ +# Generated by database migrator +import peewee + + +def migrate(migrator, database, **kwargs): + migrator.add_columns("servers", ignored_exits=peewee.CharField(default="0")) + """ + Write your migrations here. + """ + + +def rollback(migrator, database, **kwargs): + migrator.drop_columns("servers", ["ignored_exits"]) + """ + Write your rollback migrations here. + """ diff --git a/app/translations/en_EN.json b/app/translations/en_EN.json index 519be067..2d147c9d 100644 --- a/app/translations/en_EN.json +++ b/app/translations/en_EN.json @@ -299,7 +299,9 @@ "shutdown": "Shutdown server for duration of backup", "size": "Size", "storageLocation": "Storage Location", - "storageLocationDesc": "Where do you want to store backups?" + "storageLocationDesc": "Where do you want to store backups?", + "before": "Run command before backup", + "after": "Run command after backup" }, "serverConfig": { "bePatientDelete": "Please be patient while we remove your server from the Crafty panel. This screen will close in a few moments.", @@ -354,7 +356,9 @@ "timeoutExplain1": "How long Crafty will wait for your server to shutdown after executing the", "timeoutExplain2": "command before it forces the process down.", "statsHint1": "The port your server is running on should go here. This is just how Crafty opens a connection to your server for stats.", - "statsHint2": "This does not change the port of your server. You must still change the port in your server config file." + "statsHint2": "This does not change the port of your server. You must still change the port in your server config file.", + "ignoredExits": "Ignored Crash Exit Codes", + "ignoredExitsExplain": "Exit codes Crafty's Crash detection should ignore as a normal 'stop' (separated by commas)" }, "serverConfigHelp": { "desc": "Here is where you can change the configuration of your server",