diff --git a/app/classes/minecraft/serverjars.py b/app/classes/minecraft/serverjars.py index faa12a7d..447cf80b 100644 --- a/app/classes/minecraft/serverjars.py +++ b/app/classes/minecraft/serverjars.py @@ -8,6 +8,7 @@ import requests from app.classes.controllers.servers_controller import ServersController from app.classes.models.server_permissions import PermissionsServers +from app.classes.shared.websocket_manager import WebSocketManager logger = logging.getLogger(__name__) @@ -179,9 +180,7 @@ class ServerJars: try: ServersController.set_import(server_id) for user in server_users: - self.helper.websocket_helper.broadcast_user( - user, "send_start_reload", {} - ) + WebSocketManager().broadcast_user(user, "send_start_reload", {}) break except Exception as ex: @@ -206,11 +205,9 @@ class ServerJars: server_users = PermissionsServers.get_server_user_list(server_id) for user in server_users: - self.helper.websocket_helper.broadcast_user( + WebSocketManager().broadcast_user( user, "notification", "Executable download finished" ) time.sleep(3) - self.helper.websocket_helper.broadcast_user( - user, "send_start_reload", {} - ) + WebSocketManager().broadcast_user(user, "send_start_reload", {}) return success diff --git a/app/classes/models/management.py b/app/classes/models/management.py index 2c64a8ff..1213bda0 100644 --- a/app/classes/models/management.py +++ b/app/classes/models/management.py @@ -17,6 +17,7 @@ from app.classes.models.users import HelperUsers from app.classes.models.servers import Servers from app.classes.models.server_permissions import PermissionsServers from app.classes.shared.main_models import DatabaseShortcuts +from app.classes.shared.websocket_manager import WebSocketManager logger = logging.getLogger(__name__) @@ -158,9 +159,7 @@ class HelpersManagement: server_users = PermissionsServers.get_server_user_list(server_id) for user in server_users: try: - self.helper.websocket_helper.broadcast_user( - user, "notification", audit_msg - ) + WebSocketManager().broadcast_user(user, "notification", audit_msg) except Exception as e: logger.error(f"Error broadcasting to user {user} - {e}") diff --git a/app/classes/shared/command.py b/app/classes/shared/command.py index 26fdd2f0..413f74b2 100644 --- a/app/classes/shared/command.py +++ b/app/classes/shared/command.py @@ -11,6 +11,7 @@ from app.classes.shared.helpers import Helpers from app.classes.shared.tasks import TasksManager from app.classes.shared.migration import MigrationManager from app.classes.shared.main_controller import Controller +from app.classes.shared.websocket_manager import WebSocketManager logger = logging.getLogger(__name__) @@ -115,7 +116,7 @@ class MainPrompt(cmd.Cmd): Console.info( "Stopping all server daemons / threads - This may take a few seconds" ) - self.helper.websocket_helper.disconnect_all() + WebSocketManager().disconnect_all() Console.info("Waiting for main thread to stop") while True: if self.tasks_manager.get_main_thread_run_status(): diff --git a/app/classes/shared/file_helpers.py b/app/classes/shared/file_helpers.py index 4005e965..f42930f7 100644 --- a/app/classes/shared/file_helpers.py +++ b/app/classes/shared/file_helpers.py @@ -8,6 +8,7 @@ from zipfile import ZipFile, ZIP_DEFLATED from app.classes.shared.helpers import Helpers from app.classes.shared.console import Console +from app.classes.shared.websocket_manager import WebSocketManager logger = logging.getLogger(__name__) @@ -149,7 +150,7 @@ class FileHelpers: "percent": 0, "total_files": self.helper.human_readable_file_size(dir_bytes), } - self.helper.websocket_helper.broadcast_page_params( + WebSocketManager().broadcast_page_params( "/panel/server_detail", {"id": str(server_id)}, "backup_status", @@ -194,7 +195,7 @@ class FileHelpers: "percent": percent, "total_files": self.helper.human_readable_file_size(dir_bytes), } - self.helper.websocket_helper.broadcast_page_params( + WebSocketManager().broadcast_page_params( "/panel/server_detail", {"id": str(server_id)}, "backup_status", @@ -215,7 +216,7 @@ class FileHelpers: "percent": 0, "total_files": self.helper.human_readable_file_size(dir_bytes), } - self.helper.websocket_helper.broadcast_page_params( + WebSocketManager().broadcast_page_params( "/panel/server_detail", {"id": str(server_id)}, "backup_status", @@ -274,7 +275,7 @@ class FileHelpers: "total_files": self.helper.human_readable_file_size(dir_bytes), } # send status results to page. - self.helper.websocket_helper.broadcast_page_params( + WebSocketManager().broadcast_page_params( "/panel/server_detail", {"id": str(server_id)}, "backup_status", @@ -325,3 +326,20 @@ class FileHelpers: else: return "false" return + + # TODO Look if not redundant with the precendent function prefixed ajax_ de differentiate and not broke things + + def ajax_unzip_server(self, zip_path, user_id): + if Helpers.check_file_perms(zip_path): + temp_dir = tempfile.mkdtemp() + with zipfile.ZipFile(zip_path, "r") as zip_ref: + # extracts archive to temp directory + zip_ref.extractall(temp_dir) + if user_id: + WebSocketManager().broadcast_user( + user_id, "send_temp_path", {"path": temp_dir} + ) + + def ajax_backup_select(self, path, user_id): + if user_id: + WebSocketManager().broadcast_user(user_id, "send_temp_path", {"path": path}) diff --git a/app/classes/shared/helpers.py b/app/classes/shared/helpers.py index 489115ae..30f70d3a 100644 --- a/app/classes/shared/helpers.py +++ b/app/classes/shared/helpers.py @@ -29,7 +29,6 @@ from app.classes.shared.null_writer import NullWriter from app.classes.shared.console import Console from app.classes.shared.installer import installer from app.classes.shared.translation import Translation -from app.classes.web.websocket_helper import WebSocketHelper with redirect_stderr(NullWriter()): import psutil @@ -78,7 +77,6 @@ class Helpers: self.passhasher = PasswordHasher() self.exiting = False - self.websocket_helper = WebSocketHelper(self) self.translation = Translation(self) self.update_available = False self.ignored_names = ["crafty_managed.txt", "db_stats"] @@ -1216,23 +1214,6 @@ class Helpers:
  • """ return output - def unzip_server(self, zip_path, user_id): - if Helpers.check_file_perms(zip_path): - temp_dir = tempfile.mkdtemp() - with zipfile.ZipFile(zip_path, "r") as zip_ref: - # extracts archive to temp directory - zip_ref.extractall(temp_dir) - if user_id: - self.websocket_helper.broadcast_user( - user_id, "send_temp_path", {"path": temp_dir} - ) - - def backup_select(self, path, user_id): - if user_id: - self.websocket_helper.broadcast_user( - user_id, "send_temp_path", {"path": path} - ) - @staticmethod def unzip_backup_archive(backup_path, zip_name): zip_path = os.path.join(backup_path, zip_name) diff --git a/app/classes/shared/import_helper.py b/app/classes/shared/import_helper.py index e3762aad..1acf7a03 100644 --- a/app/classes/shared/import_helper.py +++ b/app/classes/shared/import_helper.py @@ -9,6 +9,7 @@ from app.classes.controllers.server_perms_controller import PermissionsServers from app.classes.controllers.servers_controller import ServersController from app.classes.shared.helpers import Helpers from app.classes.shared.file_helpers import FileHelpers +from app.classes.shared.websocket_manager import WebSocketManager logger = logging.getLogger(__name__) @@ -64,7 +65,7 @@ class ImportHelpers: ServersController.finish_import(new_id) server_users = PermissionsServers.get_server_user_list(new_id) for user in server_users: - self.helper.websocket_helper.broadcast_user(user, "send_start_reload", {}) + WebSocketManager().broadcast_user(user, "send_start_reload", {}) def import_java_zip_server(self, temp_dir, new_server_dir, port, new_id): import_thread = threading.Thread( @@ -108,7 +109,7 @@ class ImportHelpers: server_users = PermissionsServers.get_server_user_list(new_id) ServersController.finish_import(new_id) for user in server_users: - self.helper.websocket_helper.broadcast_user(user, "send_start_reload", {}) + WebSocketManager().broadcast_user(user, "send_start_reload", {}) # deletes temp dir FileHelpers.del_dirs(temp_dir) @@ -162,7 +163,7 @@ class ImportHelpers: ServersController.finish_import(new_id) server_users = PermissionsServers.get_server_user_list(new_id) for user in server_users: - self.helper.websocket_helper.broadcast_user(user, "send_start_reload", {}) + WebSocketManager().broadcast_user(user, "send_start_reload", {}) def import_bedrock_zip_server( self, temp_dir, new_server_dir, full_jar_path, port, new_id @@ -209,7 +210,7 @@ class ImportHelpers: ServersController.finish_import(new_id) server_users = PermissionsServers.get_server_user_list(new_id) for user in server_users: - self.helper.websocket_helper.broadcast_user(user, "send_start_reload", {}) + WebSocketManager().broadcast_user(user, "send_start_reload", {}) if os.name != "nt": if Helpers.check_file_exists(full_jar_path): os.chmod(full_jar_path, 0o2760) @@ -253,4 +254,4 @@ class ImportHelpers: ServersController.finish_import(new_id) server_users = PermissionsServers.get_server_user_list(new_id) for user in server_users: - self.helper.websocket_helper.broadcast_user(user, "send_start_reload", {}) + WebSocketManager().broadcast_user(user, "send_start_reload", {}) diff --git a/app/classes/shared/main_controller.py b/app/classes/shared/main_controller.py index 95872884..2a299934 100644 --- a/app/classes/shared/main_controller.py +++ b/app/classes/shared/main_controller.py @@ -32,6 +32,7 @@ from app.classes.shared.helpers import Helpers from app.classes.shared.file_helpers import FileHelpers from app.classes.shared.import_helper import ImportHelpers from app.classes.minecraft.serverjars import ServerJars +from app.classes.shared.websocket_manager import WebSocketManager logger = logging.getLogger(__name__) @@ -101,7 +102,7 @@ class Controller: self.del_support_file(exec_user["support_logs"]) # pausing so on screen notifications can run for user time.sleep(7) - self.helper.websocket_helper.broadcast_user( + WebSocketManager().broadcast_user( exec_user["user_id"], "notification", "Preparing your support logs" ) self.helper.ensure_dir_exists( @@ -197,17 +198,15 @@ class Controller: ) as f: f.write(sys_info_string) FileHelpers.make_compressed_archive(temp_zip_storage, temp_dir, sys_info_string) - if len(self.helper.websocket_helper.clients) > 0: - self.helper.websocket_helper.broadcast_user( + if len(WebSocketManager().clients) > 0: + WebSocketManager().broadcast_user( exec_user["user_id"], "support_status_update", Helpers.calc_percent(temp_dir, temp_zip_storage + ".zip"), ) temp_zip_storage += ".zip" - self.helper.websocket_helper.broadcast_user( - exec_user["user_id"], "send_logs_bootbox", {} - ) + WebSocketManager().broadcast_user(exec_user["user_id"], "send_logs_bootbox", {}) self.users.set_support_path(exec_user["user_id"], temp_zip_storage) @@ -240,8 +239,8 @@ class Controller: results = Helpers.calc_percent(source_path, dest_path) self.log_stats = results - if len(self.helper.websocket_helper.clients) > 0: - self.helper.websocket_helper.broadcast_user( + if len(WebSocketManager().clients) > 0: + WebSocketManager().broadcast_user( exec_user["user_id"], "support_status_update", results ) @@ -1025,7 +1024,7 @@ class Controller: def t_update_master_server_dir(self, new_server_path, user_id): new_server_path = self.helper.wtol_path(new_server_path) new_server_path = os.path.join(new_server_path, "servers") - self.helper.websocket_helper.broadcast_page( + WebSocketManager().broadcast_page( "/panel/panel_config", "move_status", "Checking dir" ) current_master = self.helper.wtol_path( @@ -1035,7 +1034,7 @@ class Controller: logger.info( "Admin tried to change server dir to current server dir. Canceling..." ) - self.helper.websocket_helper.broadcast_page( + WebSocketManager().broadcast_page( "/panel/panel_config", "move_status", "done", @@ -1046,18 +1045,18 @@ class Controller: "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( + WebSocketManager().broadcast_page( "/panel/panel_config", "move_status", "done", ) return - self.helper.websocket_helper.broadcast_page( + WebSocketManager().broadcast_page( "/panel/panel_config", "move_status", "Checking permissions" ) if not self.helper.ensure_dir_exists(new_server_path): - self.helper.websocket_helper.broadcast_user( + WebSocketManager().broadcast_user( user_id, "send_start_error", { @@ -1079,7 +1078,7 @@ class Controller: new_server_path, server.get("server_uuid") ) if os.path.isdir(server_path): - self.helper.websocket_helper.broadcast_page( + WebSocketManager().broadcast_page( "/panel/panel_config", "move_status", f"Moving {server.get('server_name')}", @@ -1120,7 +1119,7 @@ class Controller: self.servers.update_unloaded_server(server_obj) self.servers.init_all_servers() self.helper.dir_migration = False - self.helper.websocket_helper.broadcast_page( + WebSocketManager().broadcast_page( "/panel/panel_config", "move_status", "done", diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py index c1a11158..abf63362 100644 --- a/app/classes/shared/server.py +++ b/app/classes/shared/server.py @@ -32,6 +32,8 @@ from app.classes.shared.console import Console from app.classes.shared.helpers import Helpers from app.classes.shared.file_helpers import FileHelpers from app.classes.shared.null_writer import NullWriter +from app.classes.shared.websocket_manager import WebSocketManager +from app.classes.shared.websocket_manager import WebSocketManager with redirect_stderr(NullWriter()): import psutil @@ -92,12 +94,13 @@ class ServerOutBuf: # TODO: Do not send data to clients who do not have permission to view # this server's console - self.helper.websocket_helper.broadcast_page_params( - "/panel/server_detail", - {"id": self.server_id}, - "vterm_new_line", - {"line": highlighted + "
    "}, - ) + if len(WebSocketManager().auth_clients) > 0: + WebSocketManager().broadcast_page_params( + "/panel/server_detail", + {"id": self.server_id}, + "vterm_new_line", + {"line": highlighted + "
    "}, + ) # ********************************************************************************** @@ -322,7 +325,7 @@ class ServerInstance: # Checks if user is currently attempting to move global server # dir if self.helper.dir_migration: - self.helper.websocket_helper.broadcast_user( + WebSocketManager().broadcast_user( user_id, "send_start_error", { @@ -337,7 +340,7 @@ class ServerInstance: if self.stats_helper.get_import_status() and not forge_install: if user_id: - self.helper.websocket_helper.broadcast_user( + WebSocketManager().broadcast_user( user_id, "send_start_error", { @@ -383,7 +386,7 @@ class ServerInstance: e_flag = True if not e_flag and self.settings["type"] == "minecraft-java": if user_id: - self.helper.websocket_helper.broadcast_user( + WebSocketManager().broadcast_user( user_id, "send_eula_bootbox", {"id": self.server_id} ) else: @@ -416,7 +419,7 @@ class ServerInstance: except: if user_id: - self.helper.websocket_helper.broadcast_user( + WebSocketManager().broadcast_user( user_id, "send_start_error", { @@ -452,7 +455,7 @@ class ServerInstance: f"Server {self.name} failed to start with error code: {ex}" ) if user_id: - self.helper.websocket_helper.broadcast_user( + WebSocketManager().broadcast_user( user_id, "send_start_error", { @@ -479,7 +482,7 @@ class ServerInstance: # Checks for java on initial fail if not self.helper.detect_java(): if user_id: - self.helper.websocket_helper.broadcast_user( + WebSocketManager().broadcast_user( user_id, "send_start_error", { @@ -493,7 +496,7 @@ class ServerInstance: f"Server {self.name} failed to start with error code: {ex}" ) if user_id: - self.helper.websocket_helper.broadcast_user( + WebSocketManager().broadcast_user( user_id, "send_start_error", { @@ -540,7 +543,7 @@ class ServerInstance: self.stats_helper.set_first_run() loc_server_port = self.stats_helper.get_server_stats()["server_port"] # Sends port reminder message. - self.helper.websocket_helper.broadcast_user( + WebSocketManager().broadcast_user( user_id, "send_start_error", { @@ -552,15 +555,11 @@ class ServerInstance: server_users = PermissionsServers.get_server_user_list(self.server_id) for user in server_users: if user != user_id: - self.helper.websocket_helper.broadcast_user( - user, "send_start_reload", {} - ) + WebSocketManager().broadcast_user(user, "send_start_reload", {}) else: server_users = PermissionsServers.get_server_user_list(self.server_id) for user in server_users: - self.helper.websocket_helper.broadcast_user( - user, "send_start_reload", {} - ) + WebSocketManager().broadcast_user(user, "send_start_reload", {}) else: logger.warning( f"Server PID {self.process.pid} died right after starting " @@ -592,7 +591,7 @@ class ServerInstance: def check_internet_thread(self, user_id, user_lang): if user_id: if not Helpers.check_internet(): - self.helper.websocket_helper.broadcast_user( + WebSocketManager().broadcast_user( user_id, "send_start_error", { @@ -719,9 +718,7 @@ class ServerInstance: server_users = PermissionsServers.get_server_user_list(self.server_id) for user in server_users: - self.helper.websocket_helper.broadcast_user( - user, "send_start_reload", {} - ) + WebSocketManager().broadcast_user(user, "send_start_reload", {}) break def stop_crash_detection(self): @@ -834,7 +831,7 @@ class ServerInstance: self.record_server_stats() for user in server_users: - self.helper.websocket_helper.broadcast_user(user, "send_start_reload", {}) + WebSocketManager().broadcast_user(user, "send_start_reload", {}) def restart_threaded_server(self, user_id): bu_conf = HelpersManagement.get_backup_config(self.server_id) @@ -1034,8 +1031,8 @@ class ServerInstance: logger.info(f"Backup Thread started for server {self.settings['server_name']}.") def a_backup_server(self): - if len(self.helper.websocket_helper.clients) > 0: - self.helper.websocket_helper.broadcast_page_params( + if len(WebSocketManager().auth_clients) > 0: + WebSocketManager().broadcast_page_params( "/panel/server_detail", {"id": str(self.server_id)}, "backup_reload", @@ -1045,7 +1042,7 @@ class ServerInstance: logger.info(f"Starting server {self.name} (ID {self.server_id}) backup") server_users = PermissionsServers.get_server_user_list(self.server_id) for user in server_users: - self.helper.websocket_helper.broadcast_user( + WebSocketManager().broadcast_user( user, "notification", self.helper.translation.translate( @@ -1120,8 +1117,8 @@ class ServerInstance: self.is_backingup = False logger.info(f"Backup of server: {self.name} completed") results = {"percent": 100, "total_files": 0, "current_file": 0} - if len(self.helper.websocket_helper.clients) > 0: - self.helper.websocket_helper.broadcast_page_params( + if len(WebSocketManager().auth_clients) > 0: + WebSocketManager().broadcast_page_params( "/panel/server_detail", {"id": str(self.server_id)}, "backup_status", @@ -1129,7 +1126,7 @@ class ServerInstance: ) server_users = PermissionsServers.get_server_user_list(self.server_id) for user in server_users: - self.helper.websocket_helper.broadcast_user( + WebSocketManager().broadcast_user( user, "notification", self.helper.translation.translate( @@ -1158,8 +1155,8 @@ class ServerInstance: f"Failed to create backup of server {self.name} (ID {self.server_id})" ) results = {"percent": 100, "total_files": 0, "current_file": 0} - if len(self.helper.websocket_helper.clients) > 0: - self.helper.websocket_helper.broadcast_page_params( + if len(WebSocketManager().clients) > 0: + WebSocketManager().broadcast_page_params( "/panel/server_detail", {"id": str(self.server_id)}, "backup_status", @@ -1176,8 +1173,8 @@ class ServerInstance: def backup_status(self, source_path, dest_path): results = Helpers.calc_percent(source_path, dest_path) self.backup_stats = results - if len(self.helper.websocket_helper.clients) > 0: - self.helper.websocket_helper.broadcast_page_params( + if len(WebSocketManager().auth_clients) > 0: + WebSocketManager().broadcast_page_params( "/panel/server_detail", {"id": str(self.server_id)}, "backup_status", @@ -1280,14 +1277,14 @@ class ServerInstance: self.stop_threaded_server() else: was_started = False - if len(self.helper.websocket_helper.clients) > 0: + if len(WebSocketManager().auth_clients) > 0: # There are clients self.check_update() message = ( ' UPDATING...' ) for user in server_users: - self.helper.websocket_helper.broadcast_user_page( + WebSocketManager().broadcast_user_page( "/panel/server_detail", user, "update_button_status", @@ -1340,7 +1337,7 @@ class ServerInstance: # check if backup was successful if self.last_backup_failed: for user in server_users: - self.helper.websocket_helper.broadcast_user( + WebSocketManager().broadcast_user( user, "notification", "Backup failed for " + self.name + ". canceling update.", @@ -1386,11 +1383,11 @@ class ServerInstance: logger.info("Executable updated successfully. Starting Server") self.stats_helper.set_update(False) - if len(self.helper.websocket_helper.clients) > 0: + if len(WebSocketManager().auth_clients) > 0: # There are clients self.check_update() for user in server_users: - self.helper.websocket_helper.broadcast_user( + WebSocketManager().broadcast_user( user, "notification", "Executable update finished for " + self.name, @@ -1398,7 +1395,7 @@ class ServerInstance: # sleep so first notif can completely run time.sleep(3) for user in server_users: - self.helper.websocket_helper.broadcast_user_page( + WebSocketManager().broadcast_user_page( "/panel/server_detail", user, "update_button_status", @@ -1408,10 +1405,10 @@ class ServerInstance: "wasRunning": was_started, }, ) - self.helper.websocket_helper.broadcast_user_page( + WebSocketManager().broadcast_user_page( user, "/panel/dashboard", "send_start_reload", {} ) - self.helper.websocket_helper.broadcast_user( + WebSocketManager().broadcast_user( user, "notification", "Executable update finished for " + self.name, @@ -1428,7 +1425,7 @@ class ServerInstance: self.run_threaded_server(HelperUsers.get_user_id_by_name("system")) else: for user in server_users: - self.helper.websocket_helper.broadcast_user( + WebSocketManager().broadcast_user( user, "notification", "Executable update failed for " @@ -1438,7 +1435,7 @@ class ServerInstance: logger.error("Executable download failed.") self.stats_helper.set_update(False) for user in server_users: - self.helper.websocket_helper.broadcast_user(user, "remove_spinner", {}) + WebSocketManager().broadcast_user(user, "remove_spinner", {}) def start_dir_calc_task(self): server_dt = HelperServers.get_server_data_by_id(self.server_id) @@ -1467,7 +1464,7 @@ class ServerInstance: def realtime_stats(self): # only get stats if clients are connected. # no point in burning cpu - if len(self.helper.websocket_helper.clients) > 0: + if len(WebSocketManager().public_clients | WebSocketManager().auth_clients) > 0: total_players = 0 max_players = 0 servers_ping = [] @@ -1498,8 +1495,8 @@ class ServerInstance: "crashed": self.is_crashed, } ) - if len(self.helper.websocket_helper.clients) > 0: - self.helper.websocket_helper.broadcast_page_params( + if len(WebSocketManager().auth_clients) > 0: + WebSocketManager().broadcast_page_params( "/panel/server_detail", {"id": str(self.server_id)}, "update_server_details", @@ -1532,14 +1529,17 @@ class ServerInstance: self.record_server_stats() - if (len(servers_ping) > 0) & ( - len(self.helper.websocket_helper.clients) > 0 - ): + if (len(servers_ping) > 0) & (len(WebSocketManager().auth_clients) > 0): try: - self.helper.websocket_helper.broadcast_page( + WebSocketManager().broadcast_page( "/panel/dashboard", "update_server_status", servers_ping ) - self.helper.websocket_helper.broadcast_page( + except: + Console.critical("Can't broadcast server status to websocket") + + if (len(servers_ping) > 0) & (len(WebSocketManager().public_clients) > 0): + try: + WebSocketManager().broadcast_page( "/status", "update_server_status", servers_ping ) except: diff --git a/app/classes/shared/tasks.py b/app/classes/shared/tasks.py index acdc1cac..d35cf8dd 100644 --- a/app/classes/shared/tasks.py +++ b/app/classes/shared/tasks.py @@ -20,6 +20,7 @@ from app.classes.shared.file_helpers import FileHelpers from app.classes.shared.helpers import Helpers from app.classes.shared.main_controller import Controller from app.classes.web.tornado_handler import Webserver +from app.classes.shared.websocket_manager import WebSocketManager logger = logging.getLogger("apscheduler") scheduler_intervals = { @@ -688,10 +689,10 @@ class TasksManager: # Stats are different host_stats = HelpersManagement.get_latest_hosts_stats() - if len(self.helper.websocket_helper.clients) > 0: + if len(WebSocketManager().auth_clients) > 0: # There are clients try: - self.helper.websocket_helper.broadcast_page( + WebSocketManager().broadcast_page( "/panel/dashboard", "update_host_stats", { @@ -708,7 +709,7 @@ class TasksManager: }, ) except: - self.helper.websocket_helper.broadcast_page( + WebSocketManager().broadcast_page( "/panel/dashboard", "update_host_stats", { diff --git a/app/classes/web/websocket_helper.py b/app/classes/shared/websocket_manager.py similarity index 63% rename from app/classes/web/websocket_helper.py rename to app/classes/shared/websocket_manager.py index cd70df50..b1729c76 100644 --- a/app/classes/web/websocket_helper.py +++ b/app/classes/shared/websocket_manager.py @@ -1,44 +1,65 @@ import json import logging +from enum import Enum +from app.classes.shared.singleton import Singleton from app.classes.shared.console import Console +from app.classes.models.users import HelperUsers logger = logging.getLogger(__name__) -class WebSocketHelper: - def __init__(self, helper): - self.helper = helper - self.clients = set() +class EnumWebSocketState(Enum): + WS_UNKNOWN = -1 + WS_PUBLIC = 0 + WS_USER_AUTH = 1 + + +class WebSocketManager(metaclass=Singleton): + def __init__(self): + self.auth_clients = set() + self.public_clients = set() def add_client(self, client): - self.clients.add(client) + if client.ws_state == EnumWebSocketState.WS_PUBLIC: + self.public_clients.add(client) + elif client.ws_state == EnumWebSocketState.WS_USER_AUTH: + self.auth_clients.add(client) + else: + logging.debug("Unknown WebSocket") + client.close() def remove_client(self, client): - self.clients.remove(client) - - def send_message(self, client, event_type: str, data): - if client.check_auth(): - message = str(json.dumps({"event": event_type, "data": data})) - client.write_message_helper(message) + if client.ws_state == EnumWebSocketState.WS_PUBLIC: + self.public_clients.remove(client) + elif client.ws_state == EnumWebSocketState.WS_USER_AUTH: + self.auth_clients.remove(client) def broadcast(self, event_type: str, data): logger.debug( - f"Sending to {len(self.clients)} clients: " + f"Sending to {len(self.public_clients | self.auth_clients)} clients: " f"{json.dumps({'event': event_type, 'data': data})}" ) - for client in self.clients: + for client in self.public_clients | self.auth_clients: try: - self.send_message(client, event_type, data) + client.send_message(event_type, data) except Exception as e: logger.exception( f"Error caught while sending WebSocket message to " f"{client.get_remote_ip()} {e}" ) + def broadcast_to_admins(self, event_type: str, data): + def filter_fn(client): + if client.get_user_id in HelperUsers.get_super_user_list(): + return True + return False + + self.broadcast_with_fn(filter_fn, event_type, data) + def broadcast_page(self, page: str, event_type: str, data): def filter_fn(client): - return client.page == page + return client.check_policy(event_type) and client.page == page self.broadcast_with_fn(filter_fn, event_type, data) @@ -87,16 +108,16 @@ class WebSocketHelper: def broadcast_with_fn(self, filter_fn, event_type: str, data): # assign self.clients to a static variable here so hopefully # the set size won't change - static_clients = self.clients + static_clients = self.public_clients | self.auth_clients clients = list(filter(filter_fn, static_clients)) logger.debug( - f"Sending to {len(clients)} out of {len(self.clients)} " + f"Sending to {len(clients)} out of {len(self.public_clients | self.auth_clients)} " f"clients: {json.dumps({'event': event_type, 'data': data})}" ) for client in clients[:]: try: - self.send_message(client, event_type, data) + client.send_message(event_type, data) except Exception as e: logger.exception( f"Error catched while sending WebSocket message to " @@ -105,6 +126,6 @@ class WebSocketHelper: def disconnect_all(self): Console.info("Disconnecting WebSocket clients") - for client in self.clients: + for client in self.public_clients | self.auth_clients: client.close() Console.info("Disconnected WebSocket clients") diff --git a/app/classes/web/ajax_handler.py b/app/classes/web/ajax_handler.py index efe8d2fa..7202bfd6 100644 --- a/app/classes/web/ajax_handler.py +++ b/app/classes/web/ajax_handler.py @@ -12,8 +12,10 @@ import tornado.escape from app.classes.models.server_permissions import EnumPermissionsServer from app.classes.shared.console import Console from app.classes.shared.helpers import Helpers +from app.classes.shared.file_helpers import FileHelpers from app.classes.shared.server import ServerOutBuf from app.classes.web.base_handler import BaseHandler +from app.classes.shared.websocket_manager import WebSocketManager logger = logging.getLogger(__name__) @@ -558,13 +560,13 @@ class AjaxHandler(BaseHandler): urllib.parse.unquote(self.get_argument("file", "")), ) if Helpers.check_file_exists(path): - self.helper.unzip_server(path, exec_user["user_id"]) + FileHelpers.ajax_unzip_server(path, exec_user["user_id"]) else: user_id = exec_user["user_id"] if user_id: time.sleep(5) user_lang = self.controller.users.get_user_lang_by_id(user_id) - self.helper.websocket_helper.broadcast_user( + WebSocketManager().broadcast_user( user_id, "send_start_error", { @@ -577,7 +579,7 @@ class AjaxHandler(BaseHandler): elif page == "backup_select": path = self.get_argument("path", None) - self.helper.backup_select(path, exec_user["user_id"]) + FileHelpers.ajax_backup_select(path, exec_user["user_id"]) return elif page == "jar_cache": @@ -593,7 +595,7 @@ class AjaxHandler(BaseHandler): return for server in self.controller.servers.get_all_servers_stats(): if server["stats"]["running"]: - self.helper.websocket_helper.broadcast_user( + WebSocketManager().broadcast_user( exec_user["user_id"], "send_start_error", { diff --git a/app/classes/web/server_handler.py b/app/classes/web/server_handler.py index eae3ce0c..dbff9003 100644 --- a/app/classes/web/server_handler.py +++ b/app/classes/web/server_handler.py @@ -11,6 +11,7 @@ from app.classes.shared.helpers import Helpers from app.classes.shared.file_helpers import FileHelpers from app.classes.shared.main_models import DatabaseShortcuts from app.classes.web.base_handler import BaseHandler +from app.classes.shared.websocket_manager import WebSocketManager logger = logging.getLogger(__name__) @@ -207,7 +208,7 @@ class ServerHandler(BaseHandler): ) ): time.sleep(3) - self.helper.websocket_helper.broadcast_user( + WebSocketManager().broadcast_user( exec_user["user_id"], "send_start_error", { diff --git a/app/classes/web/tornado_handler.py b/app/classes/web/tornado_handler.py index d2b047d7..0a7f0cb8 100644 --- a/app/classes/web/tornado_handler.py +++ b/app/classes/web/tornado_handler.py @@ -34,7 +34,7 @@ from app.classes.web.api_handler import ( ListServers, SendCommand, ) -from app.classes.web.websocket_handler import SocketHandler +from app.classes.web.websocket_handler import AuthSocketHandler, PublicSocketHandler from app.classes.web.static_handler import CustomStaticHandler from app.classes.web.upload_handler import UploadHandler from app.classes.web.http_handler import HTTPHandler, HTTPHandlerPage @@ -48,7 +48,7 @@ class Webserver: controller: Controller helper: Helpers - def __init__(self, helper, controller, tasks_manager): + def __init__(self, helper: Helpers, controller: Controller, tasks_manager): self.ioloop = None self.http_server = None self.https_server = None @@ -153,7 +153,8 @@ class Webserver: (r"/server/(.*)", ServerHandler, handler_args), (r"/ajax/(.*)", AjaxHandler, handler_args), (r"/files/(.*)", FileHandler, handler_args), - (r"/ws", SocketHandler, handler_args), + (r"/ws/auth", AuthSocketHandler, handler_args), + (r"/ws/public", PublicSocketHandler, handler_args), (r"/upload", UploadHandler, handler_args), (r"/status", StatusHandler, handler_args), # API Routes V1 diff --git a/app/classes/web/upload_handler.py b/app/classes/web/upload_handler.py index adce3ab9..21b6c64c 100644 --- a/app/classes/web/upload_handler.py +++ b/app/classes/web/upload_handler.py @@ -12,6 +12,7 @@ from app.classes.shared.console import Console from app.classes.shared.helpers import Helpers from app.classes.shared.main_controller import Controller from app.classes.web.base_handler import BaseHandler +from app.classes.shared.websocket_manager import WebSocketManager logger = logging.getLogger(__name__) @@ -113,7 +114,7 @@ class UploadHandler(BaseHandler): self.request.headers.get("X-FileName", None) ) if not str(filename).endswith(".zip"): - self.helper.websocket_helper.broadcast("close_upload_box", "error") + WebSocketManager().broadcast("close_upload_box", "error") self.finish("error") full_path = os.path.join(path, filename) @@ -313,13 +314,13 @@ class UploadHandler(BaseHandler): if self.do_upload: time.sleep(5) if files_left == 0: - self.helper.websocket_helper.broadcast("close_upload_box", "success") + WebSocketManager().broadcast("close_upload_box", "success") self.finish("success") # Nope, I'm sending "success" self.f.close() else: time.sleep(5) if files_left == 0: - self.helper.websocket_helper.broadcast("close_upload_box", "error") + WebSocketManager().broadcast("close_upload_box", "error") self.finish("error") def data_received(self, chunk): diff --git a/app/classes/web/websocket_handler.py b/app/classes/web/websocket_handler.py index 78b33951..d15d6c7d 100644 --- a/app/classes/web/websocket_handler.py +++ b/app/classes/web/websocket_handler.py @@ -4,15 +4,20 @@ import asyncio from urllib.parse import parse_qsl import tornado.websocket +from app.classes.shared.main_controller import Controller from app.classes.shared.helpers import Helpers +from app.classes.shared.websocket_manager import WebSocketManager, EnumWebSocketState logger = logging.getLogger(__name__) -class SocketHandler(tornado.websocket.WebSocketHandler): +class BaseSocketHandler(tornado.websocket.WebSocketHandler): + ws_state = EnumWebSocketState.WS_UNKNOWN # Must be overridden at init + ws_authorized_pages = {} # Must be overridden at init + ws_authorized_events = {} # Must be overridden at init page = None page_query_params = None - controller = None + controller: Controller = None tasks_manager = None translator = None io_loop = None @@ -34,6 +39,102 @@ class SocketHandler(tornado.websocket.WebSocketHandler): ) return remote_ip + # pylint: disable=arguments-differ + def open(self): + """ + This method must be overridden + """ + raise NotImplementedError + + def handle(self): + """ + This method must be overridden + """ + raise NotImplementedError + + def get_user_id(self): + """ + This method must be overridden + """ + raise NotImplementedError + + def check_auth(self): + """ + This method must be overridden + """ + raise NotImplementedError + + # pylint: disable=arguments-renamed + def on_message(self, 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']}") + + def on_close(self): + WebSocketManager().remove_client(self) + logger.debug("Closed WebSocket connection") + + async def write_message_int(self, message): + self.write_message(message) + + def write_message_async(self, message): + asyncio.run_coroutine_threadsafe( + self.write_message_int(message), self.io_loop.asyncio_loop + ) + + def send_message(self, event_type: str, data): + message = str(json.dumps({"event": event_type, "data": data})) + self.write_message_async(message) + + def check_policy(self, event_type: str): + # Looking if the client is the right one for the page + if self.page.split("/")[1] not in self.ws_authorized_pages: + return False + # Looking if the event is send to the right page + if event_type not in self.ws_authorized_events: + return False + # All seams good so we can agree + return True + + +class AuthSocketHandler(BaseSocketHandler): + ws_state = EnumWebSocketState.WS_USER_AUTH + ws_authorized_pages = {"panel", "server", "ajax", "files", "upload", "api"} + ws_authorized_events = { + "notification", + "update_host_stats", + "update_server_details", + "update_server_status", + "send_start_reload", + "send_start_error", + # TODO "send_temp_path", + "support_status_update", + "send_logs_bootbox", + "move_status", + "vterm_new_line", + "send_eula_bootbox", + "backup_reload", + "backup_status", + "update_button_status", + "remove_spinner", + "close_upload_box", + } # Must be overridden at init + page = None + page_query_params = None + controller = None + tasks_manager = None + translator = None + io_loop = None + + def initialize( + self, helper=None, controller=None, tasks_manager=None, translator=None + ): + self.helper = helper + self.controller = controller + self.tasks_manager = tasks_manager + self.translator = translator + self.io_loop = tornado.ioloop.IOLoop.current() + def get_user_id(self): _, _, user = self.controller.authentication.check(self.get_cookie("token")) return user["user_id"] @@ -47,10 +148,10 @@ class SocketHandler(tornado.websocket.WebSocketHandler): if self.check_auth(): self.handle() else: - self.helper.websocket_helper.send_message( + WebSocketManager().broadcast_to_admins( self, "notification", "Not authenticated for WebSocket connection" ) - self.close() + self.close(1011, "Forbidden WS Access") self.controller.management.add_to_audit_log_raw( "unknown", 0, @@ -58,7 +159,7 @@ class SocketHandler(tornado.websocket.WebSocketHandler): "Someone tried to connect via WebSocket without proper authentication", self.get_remote_ip(), ) - self.helper.websocket_helper.broadcast( + WebSocketManager().broadcast( "notification", "Someone tried to connect via WebSocket without proper authentication", ) @@ -73,24 +174,47 @@ class SocketHandler(tornado.websocket.WebSocketHandler): Helpers.remove_prefix(self.get_query_argument("page_query_params"), "?") ) ) - self.helper.websocket_helper.add_client(self) + WebSocketManager().add_client(self) logger.debug("Opened WebSocket connection") - # 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']}") - def on_close(self): - self.helper.websocket_helper.remove_client(self) - logger.debug("Closed WebSocket connection") +class PublicSocketHandler(BaseSocketHandler): + ws_state = EnumWebSocketState.WS_PUBLIC + ws_authorized_pages = {"404", "error", "login", "offline", "status"} + ws_authorized_events = {"update_server_status"} # Must be overridden at init + page = None + page_query_params = None + controller = None + tasks_manager = None + translator = None + io_loop = None - async def write_message_int(self, message): - self.write_message(message) + def initialize( + self, helper=None, controller=None, tasks_manager=None, translator=None + ): + self.helper = helper + self.controller = controller + self.tasks_manager = tasks_manager + self.translator = translator + self.io_loop = tornado.ioloop.IOLoop.current() - def write_message_helper(self, message): - asyncio.run_coroutine_threadsafe( - self.write_message_int(message), self.io_loop.asyncio_loop + def get_user_id(self): + return None + + def check_auth(self): + return False + + # pylint: disable=arguments-differ + def open(self): + logger.debug("Checking Public WebSocket") + self.handle() + + def handle(self): + self.page = self.get_query_argument("page") + self.page_query_params = dict( + parse_qsl( + Helpers.remove_prefix(self.get_query_argument("page_query_params"), "?") + ) ) + WebSocketManager().add_client(self) + logger.debug("Opened Public WebSocket connection") diff --git a/app/frontend/templates/base.html b/app/frontend/templates/base.html index 58d49c53..72e73cbd 100755 --- a/app/frontend/templates/base.html +++ b/app/frontend/templates/base.html @@ -14,11 +14,10 @@ - + - + @@ -50,15 +49,9 @@ - - - + + + @@ -91,8 +84,7 @@ {% include notify.html %} - @@ -183,8 +175,7 @@ - + @@ -264,7 +255,7 @@ function startWebSocket() { console.log('%c[Crafty Controller] %cConnecting the WebSocket', 'font-weight: 900; color: #800080;', 'font-weight: 900; color: #eee;'); try { - var wsInternal = new WebSocket('wss://' + location.host + '/ws?' + wsPage + '&' + wsPageQueryParams); + var wsInternal = new WebSocket('wss://' + location.host + '/ws/auth?' + wsPage + '&' + wsPageQueryParams); wsInternal.onopen = function () { console.log('opened WebSocket connection:', wsInternal) wsOpen = true; @@ -538,7 +529,7 @@ }); $(document).ready(() => { if ('serviceWorker' in navigator) { - navigator.serviceWorker.register('/static/assets/js/shared/service-worker.js', {scope: '/'}) + navigator.serviceWorker.register('/static/assets/js/shared/service-worker.js', { scope: '/' }) .then(function (registration) { console.log('Service Worker Registered'); }); diff --git a/app/frontend/templates/public/status.html b/app/frontend/templates/public/status.html index a190745a..b93c0479 100644 --- a/app/frontend/templates/public/status.html +++ b/app/frontend/templates/public/status.html @@ -30,9 +30,6 @@ - {% if data['running'] != 0 %} - - {% end %} {% for server in data['servers'] %} {% if server['server_data']['show_status'] %} @@ -47,8 +44,7 @@ {% if server['stats']['desc'] != 'False' %} - icon + icon {{ server['stats']['desc'] }}
    {% end %} @@ -89,12 +85,9 @@
    -