diff --git a/CHANGELOG.md b/CHANGELOG.md index ed216490..9bbd40fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ ### New features - 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)) +- Allow users to change the directory where Crafty Stores Servers ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/539))
+ *(Only for non-docker, docker users should change host volume mount)* +- Add host storage display option to the dashboard ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/551)) ### Bug fixes - 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)) @@ -12,7 +15,8 @@ - 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)) -- Upgrade to FA 6. Add Translations ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/549)) +- Upgrade to FA 6. Add Translations ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/549))([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/558)) +- Forge installer and Java Detection improvements ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/559)) ### Lang - Add additional translations to backups page strings ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/543)) - Add additional missing translations ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/549)) diff --git a/app/classes/controllers/management_controller.py b/app/classes/controllers/management_controller.py index 2811dce4..7c423da9 100644 --- a/app/classes/controllers/management_controller.py +++ b/app/classes/controllers/management_controller.py @@ -195,3 +195,14 @@ class ManagementController: def del_excluded_backup_dir(self, server_id: int, dir_to_del: str): self.management_helper.del_excluded_backup_dir(server_id, dir_to_del) + + # ********************************************************************************** + # Crafty Methods + # ********************************************************************************** + @staticmethod + def get_master_server_dir(): + return HelpersManagement.get_master_server_dir() + + @staticmethod + def set_master_server_dir(server_dir): + HelpersManagement.set_master_server_dir(server_dir) diff --git a/app/classes/controllers/servers_controller.py b/app/classes/controllers/servers_controller.py index 4c97a6c7..608a1ced 100644 --- a/app/classes/controllers/servers_controller.py +++ b/app/classes/controllers/servers_controller.py @@ -102,6 +102,7 @@ class ServersController(metaclass=Singleton): server_obj.server_id ) server_instance.update_server_instance() + return ret def get_history_stats(self, server_id, days): @@ -163,10 +164,9 @@ class ServersController(metaclass=Singleton): return server["server_obj"] logger.warning(f"Unable to find server object for server id {server_id}") - raise Exception(f"Unable to find server object for server id {server_id}") + raise ValueError(f"Unable to find server object for server id {server_id}") def init_all_servers(self): - servers = self.get_all_defined_servers() self.failed_servers = [] @@ -227,7 +227,6 @@ class ServersController(metaclass=Singleton): ) def check_server_loaded(self, server_id_to_check: int): - logger.info(f"Checking to see if we already registered {server_id_to_check}") for server in self.servers_list: diff --git a/app/classes/minecraft/mc_ping.py b/app/classes/minecraft/mc_ping.py index 1c52ab98..f760a8f9 100644 --- a/app/classes/minecraft/mc_ping.py +++ b/app/classes/minecraft/mc_ping.py @@ -19,7 +19,6 @@ class Server: self.description = data.get("description") # print(self.description) if isinstance(self.description, dict): - # cat server if "translate" in self.description: self.description = self.description["translate"] @@ -124,7 +123,7 @@ def ping(ip, port): try: k = sock.recv(1) if not k: - raise Exception() + raise ValueError() except: return 0 k = k[0] diff --git a/app/classes/minecraft/serverjars.py b/app/classes/minecraft/serverjars.py index 3ecfdb8f..b70a8c40 100644 --- a/app/classes/minecraft/serverjars.py +++ b/app/classes/minecraft/serverjars.py @@ -104,7 +104,6 @@ class ServerJars: logger.error(f"Unable to update serverjars.com cache file: {e}") def refresh_cache(self): - cache_file = self.helper.serverjar_cache cache_old = self.helper.is_file_older_than_x_days(cache_file) diff --git a/app/classes/minecraft/stats.py b/app/classes/minecraft/stats.py index 07dd9c0d..8001b1d1 100644 --- a/app/classes/minecraft/stats.py +++ b/app/classes/minecraft/stats.py @@ -211,7 +211,6 @@ class Stats: @staticmethod def get_world_size(server_path): - total_size = 0 total_size = Helpers.get_dir_size(server_path) @@ -221,7 +220,6 @@ class Stats: return level_total_size def get_server_players(self, server_id): - server = HelperServers.get_server_data_by_id(server_id) logger.info(f"Getting players for server {server}") @@ -295,7 +293,6 @@ class Stats: @staticmethod def parse_server_raknet_ping(ping_obj: object): - try: server_icon = base64.encodebytes(ping_obj["icon"]) except Exception as e: diff --git a/app/classes/models/crafty_permissions.py b/app/classes/models/crafty_permissions.py index 22383408..7430f332 100644 --- a/app/classes/models/crafty_permissions.py +++ b/app/classes/models/crafty_permissions.py @@ -15,6 +15,7 @@ from app.classes.shared.permission_helper import PermissionHelper logger = logging.getLogger(__name__) + # ********************************************************************************** # User_Crafty Class # ********************************************************************************** diff --git a/app/classes/models/management.py b/app/classes/models/management.py index c2b5afde..73a0d9f4 100644 --- a/app/classes/models/management.py +++ b/app/classes/models/management.py @@ -20,6 +20,7 @@ from app.classes.shared.main_models import DatabaseShortcuts logger = logging.getLogger(__name__) + # ********************************************************************************** # Audit_Log Class # ********************************************************************************** @@ -46,6 +47,7 @@ class CraftySettings(BaseModel): cookie_secret = CharField(default="") login_photo = CharField(default="login_1.jpg") login_opacity = IntegerField(default=100) + master_server_dir = CharField(default="") class Meta: table_name = "crafty_settings" @@ -271,6 +273,19 @@ class HelpersManagement: CraftySettings.id == 1 ).execute() + @staticmethod + def get_master_server_dir(): + settings = CraftySettings.select(CraftySettings.master_server_dir).where( + CraftySettings.id == 1 + ) + return settings[0].master_server_dir + + @staticmethod + def set_master_server_dir(server_dir): + CraftySettings.update({CraftySettings.master_server_dir: server_dir}).where( + CraftySettings.id == 1 + ).execute() + # ********************************************************************************** # Schedules Methods # ********************************************************************************** diff --git a/app/classes/models/roles.py b/app/classes/models/roles.py index 541f67e8..abc98735 100644 --- a/app/classes/models/roles.py +++ b/app/classes/models/roles.py @@ -15,6 +15,7 @@ from app.classes.shared.helpers import Helpers logger = logging.getLogger(__name__) + # ********************************************************************************** # Roles Class # ********************************************************************************** diff --git a/app/classes/models/server_permissions.py b/app/classes/models/server_permissions.py index 8844b3df..eb5e3f35 100644 --- a/app/classes/models/server_permissions.py +++ b/app/classes/models/server_permissions.py @@ -16,6 +16,7 @@ from app.classes.shared.permission_helper import PermissionHelper logger = logging.getLogger(__name__) + # ********************************************************************************** # Role Servers Class # ********************************************************************************** diff --git a/app/classes/models/server_stats.py b/app/classes/models/server_stats.py index ccb21879..14f85ad3 100644 --- a/app/classes/models/server_stats.py +++ b/app/classes/models/server_stats.py @@ -29,6 +29,7 @@ logger = logging.getLogger(__name__) peewee_logger = logging.getLogger("peewee") peewee_logger.setLevel(logging.INFO) + # ********************************************************************************** # Servers Stats Class # ********************************************************************************** diff --git a/app/classes/models/servers.py b/app/classes/models/servers.py index 96f606a9..a83fd0a2 100644 --- a/app/classes/models/servers.py +++ b/app/classes/models/servers.py @@ -15,6 +15,7 @@ from app.classes.models.base_model import BaseModel logger = logging.getLogger(__name__) + # ********************************************************************************** # Servers Model # ********************************************************************************** diff --git a/app/classes/models/users.py b/app/classes/models/users.py index 9b4805a3..496e8d2c 100644 --- a/app/classes/models/users.py +++ b/app/classes/models/users.py @@ -21,6 +21,7 @@ from app.classes.models.roles import Roles, HelperRoles logger = logging.getLogger(__name__) + # ********************************************************************************** # Users Class # ********************************************************************************** @@ -58,6 +59,7 @@ PUBLIC_USER_ATTRS: t.Final = [ "lang", # maybe remove? ] + # ********************************************************************************** # API Keys Class # ********************************************************************************** diff --git a/app/classes/shared/authentication.py b/app/classes/shared/authentication.py index 1a809bfc..fad8b730 100644 --- a/app/classes/shared/authentication.py +++ b/app/classes/shared/authentication.py @@ -76,7 +76,7 @@ class Authentication: output = self.check(token) if output is None: - raise Exception("Invalid token") + raise ValueError("Invalid token") return output def check_bool(self, token) -> bool: diff --git a/app/classes/shared/command.py b/app/classes/shared/command.py index 7e1e9456..26fdd2f0 100644 --- a/app/classes/shared/command.py +++ b/app/classes/shared/command.py @@ -58,7 +58,6 @@ class MainPrompt(cmd.Cmd): Console.info("Unknown migration command") def do_set_passwd(self, line): - try: username = str(line).lower() # If no user is found it returns None diff --git a/app/classes/shared/helpers.py b/app/classes/shared/helpers.py index 6320d60e..7ab49bdb 100644 --- a/app/classes/shared/helpers.py +++ b/app/classes/shared/helpers.py @@ -15,6 +15,7 @@ import html import zipfile import pathlib import ctypes +import shutil import subprocess import itertools from datetime import datetime @@ -94,7 +95,7 @@ class Helpers: try: # Get tags from Gitlab, select the latest and parse the semver response = get( - "https://gitlab.com/api/v4/projects/20430749/repository/tags" + "https://gitlab.com/api/v4/projects/20430749/repository/tags", timeout=1 ) if response.status_code == 200: remote_version = pkg_version.parse(json.loads(response.text)[0]["name"]) @@ -131,7 +132,7 @@ class Helpers: try: # Get minecraft server download page # (hopefully the don't change the structure) - download_page = get(url, headers=headers) + download_page = get(url, headers=headers, timeout=1) # Search for our string targets win_download_url = re.search(target_win, download_page.text).group(0) @@ -145,6 +146,22 @@ class Helpers: logger.error(f"Unable to resolve remote bedrock download url! \n{e}") return False + def detect_java(self): + if len(self.find_java_installs()) > 0: + return True + + # We'll use this as a fallback for systems + # That do not properly setup reg keys or + # Update alternatives + if self.is_os_windows(): + if shutil.which("java.exe"): + return True + else: + if shutil.which("java"): + return True + + return False + @staticmethod def find_java_installs(): # If we're windows return oracle java versions, @@ -281,7 +298,7 @@ class Helpers: @staticmethod def check_port(server_port): try: - ip = get("https://api.ipify.org").content.decode("utf8") + ip = get("https://api.ipify.org", timeout=1).content.decode("utf8") except: ip = "google.com" a_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -417,6 +434,7 @@ class Helpers: "allow_nsfw_profile_pictures": False, "enable_user_self_delete": False, "reset_secrets_on_next_boot": False, + "monitored_mounts": Helpers.get_all_mounts(), } def get_all_settings(self): @@ -436,11 +454,27 @@ class Helpers: return data @staticmethod - def is_subdir(server_path, root_dir): + def get_all_mounts(): + mounts = [] + for item in psutil.disk_partitions(all=False): + mounts.append(item.mountpoint) + + return mounts + + def is_subdir(self, 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 self.is_os_windows(): + try: + relative = os.path.relpath(server_path, root_dir) + except: + # Windows will crash out if two paths are on different + # Drives We can happily return false if this is the case. + # Since two different drives will not be relative to eachother. + return False + else: + relative = os.path.relpath(server_path, root_dir) if relative.startswith(os.pardir): return False @@ -596,7 +630,6 @@ class Helpers: # open our file with open(file_name, "r", encoding="utf-8") as f: - # seek f.seek(0, 2) @@ -752,7 +785,7 @@ class Helpers: use_ssl=True, ) # + "?d=404" try: - if requests.head(url).status_code != 404: + if requests.head(url, timeout=1).status_code != 404: profile_url = url except Exception as e: logger.debug(f"Could not pull resource from Gravatar with error {e}") @@ -761,7 +794,6 @@ class Helpers: @staticmethod def get_file_contents(path: str, lines=100): - contents = "" if os.path.exists(path) and os.path.isfile(path): @@ -782,12 +814,10 @@ class Helpers: return False def create_session_file(self, ignore=False): - if ignore and os.path.exists(self.session_file): os.remove(self.session_file) if os.path.exists(self.session_file): - file_data = self.get_file_contents(self.session_file) try: data = json.loads(file_data) @@ -887,15 +917,16 @@ class Helpers: try: os.makedirs(path) logger.debug(f"Created Directory : {path}") + return True # directory already exists - non-blocking error except FileExistsError: - pass + return True except PermissionError as e: logger.critical(f"Check generated exception due to permssion error: {e}") + return False def create_self_signed_cert(self, cert_dir=None): - if cert_dir is None: cert_dir = os.path.join(self.config_dir, "web", "certs") @@ -977,6 +1008,15 @@ class Helpers: def is_os_windows(): return os.name == "nt" + @staticmethod + def is_env_docker(): + path = "/proc/self/cgroup" + return ( + os.path.exists("/.dockerenv") + or os.path.isfile(path) + and any("docker" in line for line in open(path, encoding="utf-8")) + ) + @staticmethod def wtol_path(w_path): l_path = w_path.replace("\\", "/") @@ -1026,9 +1066,9 @@ class Helpers: if filename not in self.ignored_names: output += f"""
  • - \n
    - @@ -1047,7 +1087,6 @@ class Helpers: return output def generate_dir(self, folder, output=""): - dir_list = [] unsorted_files = [] file_list = os.listdir(folder) @@ -1068,9 +1107,9 @@ class Helpers: if filename not in self.ignored_names: output += f"""
  • - \n
    - diff --git a/app/classes/shared/import_helper.py b/app/classes/shared/import_helper.py index 88ee91fc..e3762aad 100644 --- a/app/classes/shared/import_helper.py +++ b/app/classes/shared/import_helper.py @@ -226,7 +226,6 @@ class ImportHelpers: download_thread.start() def download_threaded_bedrock_server(self, path, new_id): - # downloads zip from remote url try: bedrock_url = Helpers.get_latest_bedrock_url() diff --git a/app/classes/shared/installer.py b/app/classes/shared/installer.py index d82d2399..6e0cec77 100644 --- a/app/classes/shared/installer.py +++ b/app/classes/shared/installer.py @@ -10,7 +10,6 @@ class Install: ) def do_install(self): - # are we in a venv? if not self.is_venv(): print("Crafty Requires a venv to install") diff --git a/app/classes/shared/main_controller.py b/app/classes/shared/main_controller.py index 3bbe05f8..11c50bc2 100644 --- a/app/classes/shared/main_controller.py +++ b/app/classes/shared/main_controller.py @@ -6,6 +6,7 @@ import platform import shutil import time import logging +import threading from peewee import DoesNotExist # TZLocal is set as a hidden import on win pipeline @@ -347,7 +348,7 @@ class Controller: elif root_create_data["create_type"] == "import_zip": # TODO: Copy files from the zip file to the new server directory server_file = create_data["jarfile"] - raise Exception("Not yet implemented") + raise NotImplementedError("Not yet implemented") _create_server_properties_if_needed( create_data["server_properties_port"], ) @@ -379,7 +380,7 @@ class Controller: logger.error(f"Server import failed with error: {ex}") elif root_create_data["create_type"] == "import_zip": # TODO: Copy files from the zip file to the new server directory - raise Exception("Not yet implemented") + raise NotImplementedError("Not yet implemented") _create_server_properties_if_needed(0, True) @@ -401,7 +402,7 @@ class Controller: logger.error(f"Server import failed with error: {ex}") elif root_create_data["create_type"] == "import_zip": # TODO: Copy files from the zip file to the new server directory - raise Exception("Not yet implemented") + raise NotImplementedError("Not yet implemented") _create_server_properties_if_needed(0, True) @@ -941,7 +942,6 @@ class Controller: def remove_server(self, server_id, files): counter = 0 for server in self.servers.servers_list: - # if this is the droid... im mean server we are looking for... if str(server["server_id"]) == str(server_id): server_data = self.servers.get_server_data(server_id) @@ -1003,3 +1003,122 @@ class Controller: @staticmethod def clear_support_status(): HelperUsers.clear_support_status() + + def set_master_server_dir(self, server_dir): + # This method should only be used on a first run basis if the server dir is "" + self.helper.servers_dir = server_dir + HelpersManagement.set_master_server_dir(server_dir) + + def update_master_server_dir(self, server_dir, user_id): + move_thread = threading.Thread( + name="dir_move", + target=self.t_update_master_server_dir, + daemon=True, + args=( + server_dir, + user_id, + ), + ) + move_thread.start() + + def t_update_master_server_dir(self, server_dir, user_id): + server_dir = self.helper.wtol_path(server_dir) + self.helper.websocket_helper.broadcast_page( + "/panel/panel_config", "move_status", "Checking dir" + ) + current_master = self.helper.wtol_path( + HelpersManagement.get_master_server_dir() + ) + if current_master == server_dir: + logger.info( + "Admin tried to change server dir to current server dir. Canceling..." + ) + self.helper.websocket_helper.broadcast_page( + "/panel/panel_config", + "move_status", + "done", + ) + return + if self.helper.is_subdir(server_dir, current_master): + logger.info( + "Admin tried to change server dir to be inside a sub directory of the" + " current server dir. This will result in a copy loop." + ) + self.helper.websocket_helper.broadcast_page( + "/panel/panel_config", + "move_status", + "done", + ) + return + + self.helper.websocket_helper.broadcast_page( + "/panel/panel_config", "move_status", "Checking permissions" + ) + if not self.helper.ensure_dir_exists(os.path.join(server_dir, "servers")): + self.helper.websocket_helper.broadcast_user( + user_id, + "send_start_error", + { + "error": "Crafty failed to move server dir. " + "It seems Crafty lacks permission to write to " + "the new directory." + }, + ) + return + # set the cached serve dir + self.helper.servers_dir = server_dir + # set DB server dir + HelpersManagement.set_master_server_dir(server_dir) + servers = self.servers.get_all_defined_servers() + # move the servers + for server in servers: + server_path = server.get("path") + new_server_path = os.path.join( + server_dir, "servers", server.get("server_uuid") + ) + if os.path.isdir(server_path): + self.helper.websocket_helper.broadcast_page( + "/panel/panel_config", + "move_status", + f"Moving {server.get('server_name')}", + ) + try: + self.file_helper.move_dir( + server_path, + new_server_path, + ) + except FileExistsError as e: + logger.error(f"Failed to move server with error: {e}") + + server_obj = self.servers.get_server_obj(server.get("server_id")) + + # reset executable path + if current_master in server["executable"]: + server_obj.executable = str(server["executable"]).replace( + current_master, server_dir + ) + # reset run command path + if current_master in server["execution_command"]: + server_obj.execution_command = str(server["execution_command"]).replace( + current_master, server_dir + ) + # reset log path + if current_master in server["log_path"]: + server_obj.log_path = str(server["log_path"]).replace( + current_master, server_dir + ) + server_obj.path = new_server_path + failed = False + for s in self.servers.failed_servers: + if int(s["server_id"]) == int(server.get("server_id")): + failed = True + if not failed: + self.servers.update_server(server_obj) + else: + self.servers.update_unloaded_server(server_obj) + self.servers.init_all_servers() + self.helper.websocket_helper.broadcast_page( + "/panel/panel_config", + "move_status", + "done", + ) diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py index 8a6cd4cd..2b9799aa 100644 --- a/app/classes/shared/server.py +++ b/app/classes/shared/server.py @@ -158,6 +158,8 @@ class ServerInstance: self.jar_update_url = server_data.executable_update_url self.name = server_data.server_name self.server_object = server_data + self.stats_helper.select_database() + self.reload_server_settings() def reload_server_settings(self): server_data = HelperServers.get_server_data_by_id(self.server_id) @@ -445,7 +447,7 @@ class ServerInstance: ) except Exception as ex: # Checks for java on initial fail - if os.system("java -version") == 32512: + if not self.helper.detect_java(): if user_id: self.helper.websocket_helper.broadcast_user( user_id, @@ -590,7 +592,6 @@ class ServerInstance: # We need to grab the exact forge version number. # We know we can find it here in the run.sh/bat script. try: - # Getting the forge version from the executable command version = re.findall( r"forge-([0-9\.]+)((?:)|(?:-([0-9\.]+)-[a-zA-Z]+)).jar", @@ -850,7 +851,6 @@ class ServerInstance: return True def crash_detected(self, name): - # clear the old scheduled watcher task self.server_scheduler.remove_job(f"c_{self.server_id}") # remove the stats polling job since server is stopped @@ -912,7 +912,6 @@ class ServerInstance: return self.process.pid if self.process is not None else None def detect_crash(self): - logger.info(f"Detecting possible crash for server: {self.name} ") running = self.check_running() @@ -935,7 +934,6 @@ class ServerInstance: self.stats_helper.sever_crashed() # if we haven't tried to restart more 3 or more times if self.restart_count <= 3: - # start the server if needed server_restarted = self.crash_detected(self.name) @@ -1461,7 +1459,6 @@ class ServerInstance: Console.critical("Can't broadcast server status to websocket") def get_servers_stats(self): - server_stats = {} logger.info("Getting Stats for Server " + self.name + " ...") @@ -1548,7 +1545,6 @@ class ServerInstance: return server_stats def get_server_players(self): - server = HelperServers.get_server_data_by_id(self.server_id) logger.info(f"Getting players for server {server}") @@ -1569,7 +1565,6 @@ class ServerInstance: return [] def get_raw_server_stats(self, server_id): - try: server = HelperServers.get_server_obj(server_id) except: @@ -1718,7 +1713,6 @@ class ServerInstance: return server_stats def record_server_stats(self): - server_stats = self.get_servers_stats() self.stats_helper.insert_server_stats(server_stats) diff --git a/app/classes/shared/tasks.py b/app/classes/shared/tasks.py index e96acd14..8c511c7e 100644 --- a/app/classes/shared/tasks.py +++ b/app/classes/shared/tasks.py @@ -4,6 +4,7 @@ import logging import threading import asyncio import datetime +import json from tzlocal import get_localzone from tzlocal.utils import ZoneInfoNotFoundError @@ -672,7 +673,6 @@ class TasksManager: host_stats = HelpersManagement.get_latest_hosts_stats() while True: - if host_stats.get( "cpu_usage" ) != HelpersManagement.get_latest_hosts_stats().get( @@ -687,18 +687,37 @@ class TasksManager: host_stats = HelpersManagement.get_latest_hosts_stats() if len(self.helper.websocket_helper.clients) > 0: # There are clients - self.helper.websocket_helper.broadcast_page( - "/panel/dashboard", - "update_host_stats", - { - "cpu_usage": host_stats.get("cpu_usage"), - "cpu_cores": host_stats.get("cpu_cores"), - "cpu_cur_freq": host_stats.get("cpu_cur_freq"), - "cpu_max_freq": host_stats.get("cpu_max_freq"), - "mem_percent": host_stats.get("mem_percent"), - "mem_usage": host_stats.get("mem_usage"), - }, - ) + try: + self.helper.websocket_helper.broadcast_page( + "/panel/dashboard", + "update_host_stats", + { + "cpu_usage": host_stats.get("cpu_usage"), + "cpu_cores": host_stats.get("cpu_cores"), + "cpu_cur_freq": host_stats.get("cpu_cur_freq"), + "cpu_max_freq": host_stats.get("cpu_max_freq"), + "mem_percent": host_stats.get("mem_percent"), + "mem_usage": host_stats.get("mem_usage"), + "disk_usage": json.loads( + host_stats.get("disk_json").replace("'", '"') + ), + "mounts": self.helper.get_setting("monitored_mounts"), + }, + ) + except: + self.helper.websocket_helper.broadcast_page( + "/panel/dashboard", + "update_host_stats", + { + "cpu_usage": host_stats.get("cpu_usage"), + "cpu_cores": host_stats.get("cpu_cores"), + "cpu_cur_freq": host_stats.get("cpu_cur_freq"), + "cpu_max_freq": host_stats.get("cpu_max_freq"), + "mem_percent": host_stats.get("mem_percent"), + "mem_usage": host_stats.get("mem_usage"), + "disk_usage": {}, + }, + ) time.sleep(1) def check_for_updates(self): diff --git a/app/classes/web/ajax_handler.py b/app/classes/web/ajax_handler.py index 97de6b13..d7dc90ee 100644 --- a/app/classes/web/ajax_handler.py +++ b/app/classes/web/ajax_handler.py @@ -575,6 +575,31 @@ class AjaxHandler(BaseHandler): self.controller.server_jars.manual_refresh_cache() return + elif page == "update_server_dir": + for server in self.controller.servers.get_all_servers_stats(): + if server["stats"]["running"]: + self.helper.websocket_helper.broadcast_user( + exec_user["user_id"], + "send_start_error", + { + "error": "You must stop all servers before " + "starting a storage migration." + }, + ) + return + if not superuser: + self.redirect("/panel/error?error=Not a super user") + return + if self.helper.is_env_docker(): + self.redirect( + "/panel/error?error=This feature is not" + " supported on docker environments" + ) + return + new_dir = urllib.parse.unquote(self.get_argument("server_dir")) + self.controller.update_master_server_dir(new_dir, exec_user["user_id"]) + return + @tornado.web.authenticated def delete(self, page): api_key, _, exec_user = self.current_user diff --git a/app/classes/web/default_handler.py b/app/classes/web/default_handler.py index dc5c79bb..f41878f1 100644 --- a/app/classes/web/default_handler.py +++ b/app/classes/web/default_handler.py @@ -6,7 +6,6 @@ logger = logging.getLogger(__name__) class DefaultHandler(BaseHandler): - # Override prepare() instead of get() to cover all possible HTTP methods. def prepare(self, page=None): # pylint: disable=arguments-differ if page is not None: diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index 24112877..27758bef 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -290,9 +290,11 @@ 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, + "docker": self.helper.is_env_docker(), "background": self.controller.cached_login, "login_opacity": self.controller.management.get_login_opacity(), "serverTZ": tz, + "monitored": self.helper.get_setting("monitored_mounts"), "version_data": self.helper.get_version_string(), "failed_servers": self.controller.servers.failed_servers, "user_data": exec_user, @@ -332,7 +334,12 @@ class PanelHandler(BaseHandler): else None, "superuser": superuser, } - + try: + page_data["hosts_data"]["disk_json"] = json.loads( + page_data["hosts_data"]["disk_json"].replace("'", '"') + ) + except: + page_data["hosts_data"]["disk_json"] = {} if page == "unauthorized": template = "panel/denied.html" @@ -841,6 +848,9 @@ class PanelHandler(BaseHandler): page_data["auth-servers"] = auth_servers page_data["role-servers"] = auth_role_servers page_data["user-roles"] = user_roles + page_data[ + "servers_dir" + ] = self.controller.management.get_master_server_dir() page_data["users"] = self.controller.users.user_query(exec_user["user_id"]) page_data["roles"] = self.controller.users.user_role_query( @@ -880,6 +890,7 @@ class PanelHandler(BaseHandler): page_data["config-json"] = data page_data["availables_languages"] = [] page_data["all_languages"] = [] + page_data["all_partitions"] = self.helper.get_all_mounts() for file in sorted( os.listdir( diff --git a/app/classes/web/public_handler.py b/app/classes/web/public_handler.py index d5bff692..dad74881 100644 --- a/app/classes/web/public_handler.py +++ b/app/classes/web/public_handler.py @@ -10,7 +10,6 @@ logger = logging.getLogger(__name__) class PublicHandler(BaseHandler): def set_current_user(self, user_id: str = None): - expire_days = self.helper.get_setting("cookie_expire") # if helper comes back with false @@ -29,7 +28,6 @@ class PublicHandler(BaseHandler): # self.clear_cookie("user_data") def get(self, page=None): - error = bleach.clean(self.get_argument("error", "Invalid Login!")) error_msg = bleach.clean(self.get_argument("error_msg", "")) @@ -81,7 +79,6 @@ class PublicHandler(BaseHandler): ) def post(self, page=None): - error = bleach.clean(self.get_argument("error", "Invalid Login!")) error_msg = bleach.clean(self.get_argument("error_msg", "")) @@ -96,7 +93,6 @@ class PublicHandler(BaseHandler): page_data["query"] = self.request.query if page == "login": - next_page = "/login" if self.request.query: next_page = "/login?" + self.request.query diff --git a/app/classes/web/routes/api/auth/login.py b/app/classes/web/routes/api/auth/login.py index 8583dce5..84ae2815 100644 --- a/app/classes/web/routes/api/auth/login.py +++ b/app/classes/web/routes/api/auth/login.py @@ -26,7 +26,6 @@ login_schema = { class ApiAuthLoginHandler(BaseApiHandler): def post(self): - try: data = json.loads(self.request.body) except json.decoder.JSONDecodeError as e: diff --git a/app/classes/web/routes/api/servers/index.py b/app/classes/web/routes/api/servers/index.py index a68e845b..edfec8fc 100644 --- a/app/classes/web/routes/api/servers/index.py +++ b/app/classes/web/routes/api/servers/index.py @@ -631,7 +631,6 @@ class ApiServersIndexHandler(BaseApiHandler): self.finish_json(200, {"status": "ok", "data": auth_data[0]}) def post(self): - auth_data = self.authenticate_user() if not auth_data: return diff --git a/app/classes/web/server_handler.py b/app/classes/web/server_handler.py index da854cc5..8b533bdf 100644 --- a/app/classes/web/server_handler.py +++ b/app/classes/web/server_handler.py @@ -404,6 +404,14 @@ class ServerHandler(BaseHandler): jar_type, server_type, server_version = server_parts # TODO: add server type check here and call the correct server # add functions if not a jar + if server_type == "forge" and not self.helper.detect_java(): + translation = self.helper.translation.translate( + "error", + "installerJava", + self.controller.users.get_user_lang_by_id(exec_user["user_id"]), + ).format(server_name) + self.redirect(f"/panel/error?error={translation}") + return new_server_id = self.controller.create_jar_server( jar_type, server_type, @@ -552,7 +560,6 @@ class ServerHandler(BaseHandler): self.get_remote_ip(), ) else: - new_server_id = self.controller.create_bedrock_server( server_name, exec_user["user_id"], diff --git a/app/classes/web/tornado_handler.py b/app/classes/web/tornado_handler.py index d0413beb..d2b047d7 100644 --- a/app/classes/web/tornado_handler.py +++ b/app/classes/web/tornado_handler.py @@ -59,7 +59,6 @@ class Webserver: @staticmethod def log_function(handler): - info = { "Status_Code": handler.get_status(), "Method": handler.request.method, @@ -103,7 +102,6 @@ class Webserver: logger.debug("Applied asyncio patch") def run_tornado(self): - # let's verify we have an SSL cert self.helper.create_self_signed_cert() diff --git a/app/classes/web/upload_handler.py b/app/classes/web/upload_handler.py index 11e1c4b8..39752a35 100644 --- a/app/classes/web/upload_handler.py +++ b/app/classes/web/upload_handler.py @@ -18,7 +18,6 @@ logger = logging.getLogger(__name__) @tornado.web.stream_request_body class UploadHandler(BaseHandler): - # noinspection PyAttributeOutsideInit def initialize( self, @@ -173,7 +172,6 @@ class UploadHandler(BaseHandler): if not self.request.headers.get("X-Content-Type", None).startswith( "image/" ): - return self.finish_json( 415, { diff --git a/app/classes/web/websocket_handler.py b/app/classes/web/websocket_handler.py index 64648fd8..78b33951 100644 --- a/app/classes/web/websocket_handler.py +++ b/app/classes/web/websocket_handler.py @@ -79,7 +79,6 @@ class SocketHandler(tornado.websocket.WebSocketHandler): # pylint: disable=arguments-renamed @staticmethod def on_message(raw_message): - logger.debug(f"Got message from WebSocket connection {raw_message}") message = json.loads(raw_message) logger.debug(f"Event Type: {message['event']}, Data: {message['data']}") diff --git a/app/frontend/templates/panel/config_json.html b/app/frontend/templates/panel/config_json.html index 67162890..9cc28919 100644 --- a/app/frontend/templates/panel/config_json.html +++ b/app/frontend/templates/panel/config_json.html @@ -71,7 +71,7 @@
    + });">{{ translate('panelConfig', 'enableLang', data['lang']) }} + {% for mount in data['all_partitions'] %} + {% if mount in item[1] %} + + {% else %} + + {% end %} + {% end %} + + +
    {% elif isinstance(item[1], list) %} @@ -160,6 +179,9 @@ let selected_Lang = $('#lang_select').val(); $('#disabled_lang').val(selected_Lang); + let mounts = $('#mount_select').val(); + $('#monitored_mounts').val(mounts); + let class_list = document.getElementsByClassName("list"); let form_json = convertFormToJSON($("#config-form")); for (let i = 0; i < class_list.length; i++) { diff --git a/app/frontend/templates/panel/dashboard.html b/app/frontend/templates/panel/dashboard.html index adfe7645..981a4163 100644 --- a/app/frontend/templates/panel/dashboard.html +++ b/app/frontend/templates/panel/dashboard.html @@ -42,7 +42,6 @@ }, }); }); - {% end %}
    @@ -50,7 +49,7 @@
    -
    +
    {{ translate('dashboard', 'host', data['lang']) }} @@ -72,7 +71,7 @@
    -
    +
    {{ translate('dashboard', 'servers', data['lang']) }} @@ -88,7 +87,7 @@
    -
    +
    {{ translate('dashboard', 'players', data['lang']) }} @@ -101,6 +100,43 @@
    +
    +
    +
    +
    Storage +
    +
    +
    + {% for item in data.get('hosts_data').get('disk_json') %} + {% if item["mount"] in data["monitored"] %} +
    +

    + {{item["mount"]}}

    +
    +
    {{item["used"]}} / + {{item["total"]}} +
    +
    +
    + {% end %} + {% end %} +
    +
    +
    +
    +
    @@ -891,6 +927,32 @@ cpu_usage.textContent = hostStats.cpu_usage; mem_usage.setAttribute('data-original-title', `{% raw translate("dashboard", "memUsage", data['lang']) %}: ${hostStats.mem_usage}`); mem_percent.textContent = hostStats.mem_percent + '%'; + + var storage_html = '
    '; + for (i = 0; i < hostStats.disk_usage.length; i++) { + if (hostStats.mounts.includes(hostStats.disk_usage[i].mount)) { + storage_html += `
    +

    ${hostStats.disk_usage[i].mount}

    +
    +
    ${hostStats.disk_usage[i].used} / ${hostStats.disk_usage[i].total} +
    +
    +
    `; + } + } + storage_html += `
    `; + $(".storage-heading").tooltip('hide'); + $("#storage_data").html(storage_html); + $("#storage_data").tooltip({ selector: '.storage-heading' }); }); } diff --git a/app/frontend/templates/panel/panel_config.html b/app/frontend/templates/panel/panel_config.html index 249daf70..535fe094 100644 --- a/app/frontend/templates/panel/panel_config.html +++ b/app/frontend/templates/panel/panel_config.html @@ -50,7 +50,9 @@

    {{ translate('panelConfig', 'users', data['lang']) }}

    {% if data['user_data']['hints'] %} - + {% end %}
    - {% if data['superuser'] %} + {% if data['superuser'] and not data["docker"] %}
    @@ -234,6 +238,24 @@

    {{ translate('panelConfig', 'adminControls', data['lang']) }}

    +
    +
    +
    + +
    + +
    + /servers/ +
    +
    +
    +   +
    + +
    @@ -247,10 +269,37 @@
    @@ -258,6 +307,40 @@ {% block js %}