diff --git a/CHANGELOG.md b/CHANGELOG.md index f90dd136..a61a3c7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog -## --- [4.0.17] - 2022/11/30 +## --- [4.0.19] - 2022/TBD +### New features +TBD +### Bug fixes +- Fix port tooltip not showing on dash while server online. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/503)) +- Fix '+' char in path causing any file operation to fail. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/502)) +- Fix colours on public pages. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/504)) +- Fix bug where public background was not sent to public pages...like the error page resulting in an error...ironic...I know. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/505)) +- Be sure a user cannot server import crafty dir. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/506)) +- Remove Pathlib from sub path check ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/507)) +- Fix root dir selection in Upload Zip Import ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/508)) +- Fix stats error on mac M1 chips ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/512)) +- Fix window path escape on java override ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/513)) +### Tweaks +- Make server directories non-configurable ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/511)) +- Add popover to server port to detail it's purpose ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/514)) +### Lang +TBD +

+ +## --- [4.0.17/4.0.18] - 2022/11/30 ### New features - Automate forge install process through Crafty server creation for Forge server version 1.16 and greater. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/495)) - Tooltip for server port on dashboard. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/496)) @@ -7,8 +27,10 @@ ### Bug fixes - Fix no port on bedrock server creation. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/493)) ### Tweaks -- Docker🐋 | Update image base to Ubuntu 22.04 Jammy ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/497))
+- Docker🐋 | Update image base to Ubuntu 22.04 Jammy. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/497))
*(OpenJDK16 Removed, no jammy backport)* +### Hotfix (4.0.18) +- Apply custom login backgrounds on all public pages. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/499))

## --- [4.0.16] - 2022/10/23 diff --git a/README.md b/README.md index 5ae83d5f..93888c5c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![Crafty Logo](app/frontend/static/assets/images/logo_long.svg)](https://craftycontrol.com) -# Crafty Controller 4.0.17 +# Crafty Controller 4.0.19 > Python based Control Panel for your Minecraft Server ## What is Crafty Controller? diff --git a/app/classes/minecraft/stats.py b/app/classes/minecraft/stats.py index 5ef9550d..e3611509 100644 --- a/app/classes/minecraft/stats.py +++ b/app/classes/minecraft/stats.py @@ -86,7 +86,7 @@ class Stats: def get_node_stats(self) -> NodeStatsReturnDict: try: cpu_freq = psutil.cpu_freq() - except NotImplementedError: + except (NotImplementedError, FileNotFoundError): cpu_freq = None if cpu_freq is None: cpu_freq = psutil._common.scpufreq(current=-1, min=-1, max=-1) diff --git a/app/classes/shared/helpers.py b/app/classes/shared/helpers.py index c7bef77b..f853286b 100644 --- a/app/classes/shared/helpers.py +++ b/app/classes/shared/helpers.py @@ -377,6 +377,17 @@ class Helpers: return default_return + @staticmethod + def is_subdir(server_path, root_dir): + server_path = os.path.realpath(server_path) + root_dir = os.path.realpath(root_dir) + + relative = os.path.relpath(server_path, root_dir) + + if relative.startswith(os.pardir): + return False + return True + def set_setting(self, key, new_value): try: with open(self.settings_file, "r", encoding="utf-8") as f: diff --git a/app/classes/web/ajax_handler.py b/app/classes/web/ajax_handler.py index fe3fb14f..cdd67146 100644 --- a/app/classes/web/ajax_handler.py +++ b/app/classes/web/ajax_handler.py @@ -4,6 +4,7 @@ import pathlib import re import logging import time +import urllib.parse import bleach import tornado.web import tornado.escape @@ -507,12 +508,12 @@ class AjaxHandler(BaseHandler): self.redirect("/panel/dashboard") elif page == "unzip_server": - path = self.get_argument("path", None) + path = urllib.parse.unquote(self.get_argument("path", "")) if not path: path = os.path.join( self.controller.project_root, "imports", - self.get_argument("file", ""), + urllib.parse.unquote(self.get_argument("file", "")), ) if Helpers.check_file_exists(path): self.helper.unzip_server(path, exec_user["user_id"]) diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index d64774bd..020dee12 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -7,6 +7,7 @@ import json import logging import threading import shlex +import urllib.parse import bleach import requests import tornado.web @@ -289,6 +290,7 @@ class PanelHandler(BaseHandler): page_data: t.Dict[str, t.Any] = { # todo: make this actually pull and compare version data "update_available": self.helper.update_available, + "background": self.controller.cached_login, "serverTZ": tz, "version_data": self.helper.get_version_string(), "failed_servers": self.controller.servers.failed_servers, @@ -806,9 +808,15 @@ class PanelHandler(BaseHandler): user_roles_list = self.controller.users.get_user_roles_names( user.user_id ) - user_servers = self.controller.servers.get_authorized_servers( - user.user_id - ) + try: + user_servers = self.controller.servers.get_authorized_servers( + user.user_id + ) + except: + return self.redirect( + "/panel/error?error=Cannot load panel config" + " while servers are unloaded" + ) servers = [] for server in user_servers: if server.name not in servers: @@ -1386,9 +1394,10 @@ class PanelHandler(BaseHandler): template = "panel/activity_logs.html" elif page == "download_file": - file = Helpers.get_os_understandable_path(self.get_argument("path", "")) - name = self.get_argument("name", "") - + file = Helpers.get_os_understandable_path( + urllib.parse.unquote(self.get_argument("path", "")) + ) + name = urllib.parse.unquote(self.get_argument("name", "")) server_id = self.check_server_id() if server_id is None: return @@ -1551,7 +1560,10 @@ class PanelHandler(BaseHandler): return if java_selection: try: - execution_list = shlex.split(execution_command) + if self.helper.is_os_windows(): + execution_list = shlex.split(execution_command, posix=False) + else: + execution_list = shlex.split(execution_command, posix=True) except ValueError: self.redirect( "/panel/error?error=Invalid execution command. Java path" @@ -1603,7 +1615,6 @@ class PanelHandler(BaseHandler): if Helpers.validate_traversal( self.helper.get_servers_root_dir(), server_path ): - server_obj.path = server_path server_obj.log_path = log_path if Helpers.validate_traversal( self.helper.get_servers_root_dir(), executable @@ -1615,7 +1626,6 @@ class PanelHandler(BaseHandler): server_obj.executable_update_url = executable_update_url server_obj.show_status = show_status else: - server_obj.path = server_obj.path server_obj.log_path = server_obj.log_path server_obj.executable = server_obj.executable server_obj.execution_command = execution_command diff --git a/app/classes/web/public_handler.py b/app/classes/web/public_handler.py index bb31248f..7f3f0c26 100644 --- a/app/classes/web/public_handler.py +++ b/app/classes/web/public_handler.py @@ -39,6 +39,7 @@ class PublicHandler(BaseHandler): "lang": self.helper.get_setting("language"), "lang_page": self.helper.get_lang_page(self.helper.get_setting("language")), "query": "", + "background": self.controller.cached_login, } if self.request.query: @@ -48,7 +49,6 @@ class PublicHandler(BaseHandler): template = "public/404.html" if page == "login": - page_data["background"] = self.controller.cached_login template = "public/login.html" elif page == 404: diff --git a/app/classes/web/routes/api/servers/server/index.py b/app/classes/web/routes/api/servers/server/index.py index 11f8620b..3d5e3e2f 100644 --- a/app/classes/web/routes/api/servers/server/index.py +++ b/app/classes/web/routes/api/servers/server/index.py @@ -90,7 +90,8 @@ class ApiServersServerIndexHandler(BaseApiHandler): server_obj = self.controller.servers.get_server_obj(server_id) for key in data: # If we don't validate the input there could be security issues - setattr(server_obj, key, data[key]) + if key != "path": + setattr(server_obj, key, data[key]) self.controller.servers.update_server(server_obj) self.controller.management.add_to_audit_log( diff --git a/app/classes/web/server_handler.py b/app/classes/web/server_handler.py index 08162365..62c549e5 100644 --- a/app/classes/web/server_handler.py +++ b/app/classes/web/server_handler.py @@ -331,6 +331,15 @@ class ServerHandler(BaseHandler): return if import_type == "import_jar": + if not self.helper.is_subdir( + import_server_path, self.controller.project_root + ): + self.redirect( + "/panel/error?error=Loop Error: The selected path will cause" + " an infinite copy loop. Make sure Crafty's directory is not" + " in your server path." + ) + return good_path = self.controller.verify_jar_server( import_server_path, import_server_jar ) @@ -480,6 +489,15 @@ class ServerHandler(BaseHandler): return if import_type == "import_jar": + if self.helper.is_subdir( + import_server_path, self.controller.project_root + ): + self.redirect( + "/panel/error?error=Loop Error: The selected path will cause" + " an infinite copy loop. Make sure Crafty's directory is not" + " in your server path." + ) + return good_path = self.controller.verify_jar_server( import_server_path, import_server_exe ) diff --git a/app/classes/web/status_handler.py b/app/classes/web/status_handler.py index 6e73cdcb..410e3a36 100644 --- a/app/classes/web/status_handler.py +++ b/app/classes/web/status_handler.py @@ -7,7 +7,7 @@ logger = logging.getLogger(__name__) class StatusHandler(BaseHandler): def get(self): - page_data = {} + 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") diff --git a/app/classes/web/upload_handler.py b/app/classes/web/upload_handler.py index 2de4fe1f..785d5783 100644 --- a/app/classes/web/upload_handler.py +++ b/app/classes/web/upload_handler.py @@ -1,6 +1,7 @@ import logging import os import time +import urllib.parse import tornado.web import tornado.options import tornado.httpserver @@ -108,7 +109,9 @@ class UploadHandler(BaseHandler): logger.debug("Could not delete file on user server upload") self.helper.ensure_dir_exists(path) - filename = self.request.headers.get("X-FileName", None) + filename = urllib.parse.unquote( + self.request.headers.get("X-FileName", None) + ) if not str(filename).endswith(".zip"): self.helper.websocket_helper.broadcast("close_upload_box", "error") self.finish("error") diff --git a/app/config/version.json b/app/config/version.json index f97d9f2b..02ec3ae1 100644 --- a/app/config/version.json +++ b/app/config/version.json @@ -1,5 +1,5 @@ { "major": 4, "minor": 0, - "sub": 17 + "sub": 19 } diff --git a/app/frontend/static/assets/css/dark/style.css b/app/frontend/static/assets/css/dark/style.css index dbd6ea8b..c8ac5eb9 100755 --- a/app/frontend/static/assets/css/dark/style.css +++ b/app/frontend/static/assets/css/dark/style.css @@ -22155,7 +22155,7 @@ ul li { } .popover .popover-body { - color: #000; + color: var(--base-text); background: var(--card-banner-bg); } diff --git a/app/frontend/templates/panel/dashboard.html b/app/frontend/templates/panel/dashboard.html index 1fac58cf..171f2b8a 100644 --- a/app/frontend/templates/panel/dashboard.html +++ b/app/frontend/templates/panel/dashboard.html @@ -278,9 +278,10 @@ {% end %} - + +
{% if server['stats']['running'] %} {{ translate('dashboard', 'online', data['lang']) }} @@ -299,6 +300,7 @@ data-players="{{ server['stats']['online']}}" data-max="{{ server['stats']['max'] }}"> {% end %} +
{% for server in data['failed_servers'] %} diff --git a/app/frontend/templates/panel/server_config.html b/app/frontend/templates/panel/server_config.html index d0c0d42f..fb19ec05 100644 --- a/app/frontend/templates/panel/server_config.html +++ b/app/frontend/templates/panel/server_config.html @@ -62,9 +62,10 @@ - +
+ {{ data['server_stats']['server_id']['path'] }} + 🔒 +
{% if data['server_stats']['server_type'] != "minecraft-bedrock" %} @@ -157,7 +158,6 @@ -
{% end %} @@ -522,6 +527,20 @@ }); } + $("#server_port").focus(function () { + + $('[data-toggle="popover"]').popover(); + if ($(window).width() < 1000) { + $('.port-hint').attr("data-placement", "top") + } else { + $('.port-hint').attr("data-placement", "right") + } + $('.port-hint').popover("show"); + }); + $("#server_port").focusout(function () { + $('.port-hint').popover("hide"); + }); + $(document).ready(function () { webSocket.on('remove_spinner', function () { document.getElementById("update-spinner").style.visibility = "hidden"; diff --git a/app/frontend/templates/panel/server_files.html b/app/frontend/templates/panel/server_files.html index ebcf0d3b..af287b43 100644 --- a/app/frontend/templates/panel/server_files.html +++ b/app/frontend/templates/panel/server_files.html @@ -1027,7 +1027,9 @@ function downloadFileE(event) { path = event.target.parentElement.getAttribute('data-path'); name = event.target.parentElement.getAttribute('data-name'); - window.location.href = `/panel/download_file?id=${serverId}&path=${path}&name=${name}`; + encoded_path = encodeURIComponent(path) + encoded_name = encodeURIComponent(name) + window.location.href = `/panel/download_file?id=${serverId}&path=${encoded_path}&name=${encoded_name}`; } function renameItemE(event) { diff --git a/app/frontend/templates/public/404.html b/app/frontend/templates/public/404.html index fa301d26..480fe5cf 100644 --- a/app/frontend/templates/public/404.html +++ b/app/frontend/templates/public/404.html @@ -1,70 +1,81 @@ - - - - - Crafty Controller - - - - - - - - - - - - - - - - -
-
-
-
-
-
-
+ + + + + Crafty Controller + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ +
+


-
- -
-
+
-
- +
- - - - - - - - - - - - + +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/frontend/templates/public/error.html b/app/frontend/templates/public/error.html index 23a67863..e57e1d13 100644 --- a/app/frontend/templates/public/error.html +++ b/app/frontend/templates/public/error.html @@ -1,73 +1,86 @@ - - - - - Crafty Controller - - - - - - - - - - - - - - - - -
-
-
-
-
-
-
+ + + + + Crafty Controller + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ +
+


-
- -
-
+
-
- +
- - - - - - - - - - - - + +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/frontend/templates/public/login.html b/app/frontend/templates/public/login.html index c6621a11..e172a9a8 100644 --- a/app/frontend/templates/public/login.html +++ b/app/frontend/templates/public/login.html @@ -21,6 +21,13 @@ +
@@ -60,11 +67,6 @@ .login-input:focus { box-shadow: 0 12px 16px 0 hsla(0, 0%, 0%, 0.4); } - - .auth.auth-bg-1 { - background: url("../../static/assets/images/auth/{{data['background']}}"); - background-size: cover; - } {% if data['query'] %}
diff --git a/app/frontend/templates/public/status.html b/app/frontend/templates/public/status.html index 8a81638a..fcb24676 100644 --- a/app/frontend/templates/public/status.html +++ b/app/frontend/templates/public/status.html @@ -7,6 +7,13 @@ {% block content %} +