From a0abc6819bb72e3b9dd6f9d8ecf4b99bff1aa2b5 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Wed, 17 May 2023 19:05:59 -0400 Subject: [PATCH 001/210] Initial webhook integration --- app/classes/shared/server.py | 13 +++++++++++++ app/classes/web/webhook_handler.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 app/classes/web/webhook_handler.py diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py index fa68eb62..9cecc6ce 100644 --- a/app/classes/shared/server.py +++ b/app/classes/shared/server.py @@ -31,6 +31,7 @@ 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.web.webhook_handler import WebhookHandler with redirect_stderr(NullWriter()): import psutil @@ -512,6 +513,9 @@ class ServerInstance: if self.process.poll() is None: logger.info(f"Server {self.name} running with PID {self.process.pid}") Console.info(f"Server {self.name} running with PID {self.process.pid}") + WebhookHandler.send_discord_webhook( + "Crafty Controller", f"{self.name} started!", 65354 + ) self.is_crashed = False self.stats_helper.server_crash_reset() self.record_server_stats() @@ -807,6 +811,9 @@ class ServerInstance: logger.info(f"Stopped Server {server_name} with PID {server_pid}") Console.info(f"Stopped Server {server_name} with PID {server_pid}") + WebhookHandler.send_discord_webhook( + "Crafty Controller", f"{self.name} was stopped!", 16748076 + ) # massive resetting of variables self.cleanup_server_object() @@ -890,6 +897,12 @@ class ServerInstance: f"The server {name} has crashed and will be restarted. " f"Restarting server" ) + + WebhookHandler.send_discord_webhook( + "Crafty Controller", + f"{self.name} crashed! Crafty Controller is attempting to start it back up!", + 16711680, + ) self.run_threaded_server(None) return True logger.critical( diff --git a/app/classes/web/webhook_handler.py b/app/classes/web/webhook_handler.py new file mode 100644 index 00000000..c972cff1 --- /dev/null +++ b/app/classes/web/webhook_handler.py @@ -0,0 +1,30 @@ +import json +import logging +import requests + +logger = logging.getLogger(__name__) + + +class WebhookHandler: + @staticmethod + def send_discord_webhook(title, message, color): + dataset = { + "username": "Crafty Webhooks", + "avatar_url": "https://docs.craftycontrol.com/img/favicon.ico", + "embeds": [ + { + "title": title, + "description": message, + "color": color, + } + ], + } + + logger.debug( + "Webhook response: " + + requests.post( + "https://discord.com/api/webhooks/1107017140004995081/leFCJ4g_Uw6ZwxaZXTLmi-L7njIFwVvFbf3JEHnAvUJAd90PoMknlivel0rosfnFed77", + data=json.dumps(dataset), + headers={"Content-type": "application/json"}, + ), + ) From e6723d9ebceacc24cec06021f3e8d1b02f2ef388 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sat, 20 May 2023 17:49:13 -0400 Subject: [PATCH 002/210] Add new backup routes --- app/classes/web/routes/api/api_handlers.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/classes/web/routes/api/api_handlers.py b/app/classes/web/routes/api/api_handlers.py index 29ee02c5..8e4b7ac3 100644 --- a/app/classes/web/routes/api/api_handlers.py +++ b/app/classes/web/routes/api/api_handlers.py @@ -26,6 +26,12 @@ from app.classes.web.routes.api.servers.server.stdin import ApiServersServerStdi from app.classes.web.routes.api.servers.server.tasks.index import ( ApiServersServerTasksIndexHandler, ) +from app.classes.web.routes.api.servers.server.backups.index import ( + ApiServersServerBackupsIndexHandler, +) +from app.classes.web.routes.api.servers.server.backups.backup.index import ( + ApiServersServerBackupsBackupIndexHandler, +) from app.classes.web.routes.api.servers.server.tasks.task.children import ( ApiServersServerTasksTaskChildrenHandler, ) @@ -112,6 +118,16 @@ def api_handlers(handler_args): ApiServersServerIndexHandler, handler_args, ), + ( + r"/api/v2/servers/([0-9]+)/backups/?", + ApiServersServerBackupsIndexHandler, + handler_args, + ), + ( + r"/api/v2/servers/([0-9]+)/backups/backup/?", + ApiServersServerBackupsBackupIndexHandler, + handler_args, + ), ( r"/api/v2/servers/([0-9]+)/tasks/?", ApiServersServerTasksIndexHandler, From fb16be6a6444bb8cd549803a585c5079dd458216 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sat, 20 May 2023 17:49:51 -0400 Subject: [PATCH 003/210] Add index handlers (aware of the lint issue) --- .../servers/server/backups/backup/index.py | 53 +++++++++ .../api/servers/server/backups/index.py | 104 ++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 app/classes/web/routes/api/servers/server/backups/backup/index.py create mode 100644 app/classes/web/routes/api/servers/server/backups/index.py diff --git a/app/classes/web/routes/api/servers/server/backups/backup/index.py b/app/classes/web/routes/api/servers/server/backups/backup/index.py new file mode 100644 index 00000000..5c7a6f86 --- /dev/null +++ b/app/classes/web/routes/api/servers/server/backups/backup/index.py @@ -0,0 +1,53 @@ +import logging +import json +import os +from app.classes.models.server_permissions import EnumPermissionsServer +from app.classes.shared.file_helpers import FileHelpers +from app.classes.web.base_api_handler import BaseApiHandler + +logger = logging.getLogger(__name__) + + +class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler): + def get(self, server_id: str): + auth_data = self.authenticate_user() + if not auth_data: + return + if ( + EnumPermissionsServer.BACKUP + not in self.controller.server_perms.get_user_id_permissions_list( + auth_data[4]["user_id"], server_id + ) + ): + # if the user doesn't have Schedule permission, return an error + return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) + self.finish_json(200, self.controller.management.get_backup_config(server_id)) + + def delete(self, server_id: str, backup_name: str): + auth_data = self.authenticate_user() + backup_conf = self.controller.management.get_backup_config(server_id) + if not auth_data: + return + if ( + EnumPermissionsServer.BACKUP + not in self.controller.server_perms.get_user_id_permissions_list( + auth_data[4]["user_id"], server_id + ) + ): + # if the user doesn't have Schedule permission, return an error + return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) + + try: + FileHelpers.del_file(os.path.join(backup_conf["backup_path"], backup_name)) + except Exception: + return self.finish_json( + 400, {"status": "error", "error": "NO BACKUP FOUND"} + ) + self.controller.management.add_to_audit_log( + auth_data[4]["user_id"], + f"Edited server {server_id}: removed backup {backup_name}", + server_id, + self.get_remote_ip(), + ) + + return self.finish_json(200, {"status": "ok"}) diff --git a/app/classes/web/routes/api/servers/server/backups/index.py b/app/classes/web/routes/api/servers/server/backups/index.py new file mode 100644 index 00000000..ac2111db --- /dev/null +++ b/app/classes/web/routes/api/servers/server/backups/index.py @@ -0,0 +1,104 @@ +import logging +import json +import os +from jsonschema import validate +from jsonschema.exceptions import ValidationError +from app.classes.models.server_permissions import EnumPermissionsServer +from app.classes.shared.file_helpers import FileHelpers +from app.classes.web.base_api_handler import BaseApiHandler + +logger = logging.getLogger(__name__) + +backup_patch_schema = { + "type": "object", + "properties": { + "path": {"type": "string", "minLength": 1}, + "max": {"type": "int"}, + "compress": {"type": "boolean"}, + "shutdown": {"type": "boolean"}, + "before_command": {"type": "string"}, + "after_command": {"type": "string"}, + "exclusions": {"type": "string"}, + }, + "additionalProperties": False, + "minProperties": 1, +} + +basic_backup_patch_schema = { + "type": "object", + "properties": { + "max": {"type": "int"}, + "compress": {"type": "boolean"}, + "shutdown": {"type": "boolean"}, + "before_command": {"type": "string"}, + "after_command": {"type": "string"}, + "exclusions": {"type": "string"}, + }, + "additionalProperties": False, + "minProperties": 1, +} + + +class ApiServersServerBackupsIndexHandler(BaseApiHandler): + def get(self, server_id: str): + auth_data = self.authenticate_user() + if not auth_data: + return + if ( + EnumPermissionsServer.BACKUP + not in self.controller.server_perms.get_user_id_permissions_list( + auth_data[4]["user_id"], server_id + ) + ): + # if the user doesn't have Schedule permission, return an error + return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) + self.finish_json(200, self.controller.management.get_backup_config(server_id)) + + def patch(self, server_id: str): + auth_data = self.authenticate_user() + if not auth_data: + return + + try: + data = json.loads(self.request.body) + except json.decoder.JSONDecodeError as e: + return self.finish_json( + 400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)} + ) + + try: + validate(data, backup_patch_schema) + except ValidationError as e: + return self.finish_json( + 400, + { + "status": "error", + "error": "INVALID_JSON_SCHEMA", + "error_data": str(e), + }, + ) + + if server_id not in [str(x["server_id"]) for x in auth_data[0]]: + # if the user doesn't have access to the server, return an error + return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) + + if ( + EnumPermissionsServer.BACKUP + not in self.controller.server_perms.get_user_id_permissions_list( + auth_data[4]["user_id"], server_id + ) + ): + # if the user doesn't have Schedule permission, return an error + return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) + + self.controller.management.set_backup_config( + data["server_id"], + data["backup_path"], + data["max_backups"], + data["excluded_dirs"], + data["compress"], + data["shutdown"], + data["before"], + data["after"], + ) + return self.finish(200, {"status": "ok"}) From 03cfdc0aa11159392f09ff7189f42ac6044d6748 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sat, 3 Jun 2023 12:00:03 -0400 Subject: [PATCH 004/210] Add perms for webhooks --- app/classes/web/panel_handler.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index b7219ac6..cb6fd9cc 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -576,6 +576,7 @@ class PanelHandler(BaseHandler): "Files": EnumPermissionsServer.FILES, "Config": EnumPermissionsServer.CONFIG, "Players": EnumPermissionsServer.PLAYERS, + "Webhooks": EnumPermissionsServer.WEBHOOKS, } page_data[ "user_permissions" @@ -749,6 +750,17 @@ class PanelHandler(BaseHandler): page_data["history_stats"] = self.controller.servers.get_history_stats( server_id, days ) + if subpage == "webhooks": + if ( + not page_data["permissions"]["Webhooks"] + in page_data["user_permissions"] + ): + if not superuser: + self.redirect( + "/panel/error?error=Unauthorized access to Webhooks Config" + ) + return + page_data[""] def get_banned_players_html(): banned_players = self.controller.servers.get_banned_players(server_id) From e228f04345d6846151cfe0e8fd4c78aa8fb47b37 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sat, 3 Jun 2023 12:18:07 -0400 Subject: [PATCH 005/210] Remove permissions work --- app/classes/web/panel_handler.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index 4297d698..692bcff4 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -576,7 +576,6 @@ class PanelHandler(BaseHandler): "Files": EnumPermissionsServer.FILES, "Config": EnumPermissionsServer.CONFIG, "Players": EnumPermissionsServer.PLAYERS, - "Webhooks": EnumPermissionsServer.WEBHOOKS, } page_data[ "user_permissions" @@ -752,7 +751,7 @@ class PanelHandler(BaseHandler): ) if subpage == "webhooks": if ( - not page_data["permissions"]["Webhooks"] + not page_data["permissions"]["Config"] in page_data["user_permissions"] ): if not superuser: @@ -760,7 +759,6 @@ class PanelHandler(BaseHandler): "/panel/error?error=Unauthorized access to Webhooks Config" ) return - page_data[""] def get_banned_players_html(): banned_players = self.controller.servers.get_banned_players(server_id) From b15477ca4ca894171885f4f26b476515d3d705ae Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sat, 3 Jun 2023 12:19:05 -0400 Subject: [PATCH 006/210] Add providers --- app/classes/web/webhook_handler.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/app/classes/web/webhook_handler.py b/app/classes/web/webhook_handler.py index c972cff1..15b48b34 100644 --- a/app/classes/web/webhook_handler.py +++ b/app/classes/web/webhook_handler.py @@ -6,6 +6,22 @@ logger = logging.getLogger(__name__) class WebhookHandler: + @staticmethod + def get_providers(): + return [ + "Discord", + "Home Assistant", + "Mattermost", + "Opsgenie", + "Signal", + "Slack", + "SMTP", + "Splunk", + "Teams", + "Telegram", + "Custom", + ] + @staticmethod def send_discord_webhook(title, message, color): dataset = { @@ -23,7 +39,7 @@ class WebhookHandler: logger.debug( "Webhook response: " + requests.post( - "https://discord.com/api/webhooks/1107017140004995081/leFCJ4g_Uw6ZwxaZXTLmi-L7njIFwVvFbf3JEHnAvUJAd90PoMknlivel0rosfnFed77", + "", data=json.dumps(dataset), headers={"Content-type": "application/json"}, ), From 7c72dfc241aba6724c8501a7a15f74550e8e6812 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sat, 3 Jun 2023 12:21:23 -0400 Subject: [PATCH 007/210] Add webhook front end --- .../panel/parts/m_server_controls_list.html | 3 + .../panel/parts/server_controls_list.html | 6 + .../templates/panel/server_webhooks.html | 280 ++++++++++++++++++ 3 files changed, 289 insertions(+) create mode 100644 app/frontend/templates/panel/server_webhooks.html diff --git a/app/frontend/templates/panel/parts/m_server_controls_list.html b/app/frontend/templates/panel/parts/m_server_controls_list.html index 63c6b98a..6c645881 100644 --- a/app/frontend/templates/panel/parts/m_server_controls_list.html +++ b/app/frontend/templates/panel/parts/m_server_controls_list.html @@ -31,6 +31,9 @@ {{ translate('serverDetails', 'playerControls', data['lang']) }} {% end %} {{ translate('serverDetails', 'metrics', data['lang']) }} + {% if data['permissions']['Config'] in data['user_permissions'] %} + Webhooks + {% end %} \ No newline at end of file diff --git a/app/frontend/templates/panel/parts/server_controls_list.html b/app/frontend/templates/panel/parts/server_controls_list.html index 4f5bb6b5..09c9b213 100644 --- a/app/frontend/templates/panel/parts/server_controls_list.html +++ b/app/frontend/templates/panel/parts/server_controls_list.html @@ -53,4 +53,10 @@ {{ translate('serverDetails', 'metrics', data['lang']) }} + {% if data['permissions']['Webhooks'] in data['user_permissions'] %} + + {% end %} \ No newline at end of file diff --git a/app/frontend/templates/panel/server_webhooks.html b/app/frontend/templates/panel/server_webhooks.html new file mode 100644 index 00000000..b8552401 --- /dev/null +++ b/app/frontend/templates/panel/server_webhooks.html @@ -0,0 +1,280 @@ +{% extends ../base.html %} + +{% block meta %} +{% end %} + +{% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %} + +{% block content %} + +
+ + +
+
+ +
+ +
+ + + {% include "parts/details_stats.html" %} + +
+ +
+
+
+ + + {% include "parts/server_controls_list.html" %} + + + {% include "parts/m_server_controls_list.html" %} + + +
+
+
+
+

{{ translate('serverSchedules', + 'scheduledTasks', data['lang']) }}

+ {% if data['user_data']['hints'] %} + + {% end %} +
+
+
+
+
+
+
+ +
+
+
+
+ + + + +
+ + + +{% end %} + +{% block js %} + + + +{% end %} \ No newline at end of file From fb2bcd7e4eebf02d0a1dc702c5eeda34e20faf53 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sat, 3 Jun 2023 12:22:10 -0400 Subject: [PATCH 008/210] Fix permissions on webhooks nav --- app/frontend/templates/panel/parts/server_controls_list.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/frontend/templates/panel/parts/server_controls_list.html b/app/frontend/templates/panel/parts/server_controls_list.html index 09c9b213..39360f72 100644 --- a/app/frontend/templates/panel/parts/server_controls_list.html +++ b/app/frontend/templates/panel/parts/server_controls_list.html @@ -53,7 +53,7 @@ {{ translate('serverDetails', 'metrics', data['lang']) }} - {% if data['permissions']['Webhooks'] in data['user_permissions'] %} + {% if data['permissions']['Config'] in data['user_permissions'] %}
  • """ 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} - ) - @staticmethod def unzip_backup_archive(backup_path, zip_name): zip_path = os.path.join(backup_path, zip_name) diff --git a/app/classes/shared/tasks.py b/app/classes/shared/tasks.py index acdc1cac..cc2a8671 100644 --- a/app/classes/shared/tasks.py +++ b/app/classes/shared/tasks.py @@ -41,10 +41,10 @@ scheduler_intervals = { class TasksManager: controller: Controller - def __init__(self, helper, controller): + def __init__(self, helper, controller, file_helper): self.helper: Helpers = helper self.controller: Controller = controller - self.tornado: Webserver = Webserver(helper, controller, self) + self.tornado: Webserver = Webserver(helper, controller, self, file_helper) try: self.tz = get_localzone() except ZoneInfoNotFoundError as e: diff --git a/app/classes/web/ajax_handler.py b/app/classes/web/ajax_handler.py index e38648f4..0fa17dcf 100644 --- a/app/classes/web/ajax_handler.py +++ b/app/classes/web/ajax_handler.py @@ -9,7 +9,7 @@ import bleach import tornado.web import tornado.escape -from app.classes.shared.console import Console +from app.classes.shared.file_helpers import FileHelpers from app.classes.shared.helpers import Helpers from app.classes.shared.server import ServerOutBuf from app.classes.web.base_handler import BaseHandler @@ -148,32 +148,6 @@ class AjaxHandler(BaseHandler): self.controller.cached_login = "login_1.jpg" return - elif page == "unzip_server": - path = urllib.parse.unquote(self.get_argument("path", "")) - if not path: - path = os.path.join( - self.controller.project_root, - "imports", - urllib.parse.unquote(self.get_argument("file", "")), - ) - if Helpers.check_file_exists(path): - self.helper.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( - user_id, - "send_start_error", - { - "error": self.helper.translation.translate( - "error", "no-file", user_lang - ) - }, - ) - return - elif page == "jar_cache": if not superuser: self.redirect("/panel/error?error=Not a super user") @@ -208,25 +182,3 @@ class AjaxHandler(BaseHandler): new_dir = urllib.parse.unquote(self.get_argument("server_dir")) self.controller.update_master_server_dir(new_dir, exec_user["user_id"]) return - - def check_server_id(self, server_id, page_name): - if server_id is None: - logger.warning( - f"Server ID not defined in {page_name} ajax call ({server_id})" - ) - Console.warning( - f"Server ID not defined in {page_name} ajax call ({server_id})" - ) - return - server_id = bleach.clean(server_id) - - # does this server id exist? - if not self.controller.servers.server_id_exists(server_id): - logger.warning( - f"Server ID not found in {page_name} ajax call ({server_id})" - ) - Console.warning( - f"Server ID not found in {page_name} ajax call ({server_id})" - ) - return - return True diff --git a/app/classes/web/base_handler.py b/app/classes/web/base_handler.py index e772d633..33fe9936 100644 --- a/app/classes/web/base_handler.py +++ b/app/classes/web/base_handler.py @@ -8,6 +8,7 @@ import tornado.web from app.classes.models.crafty_permissions import EnumPermissionsCrafty from app.classes.models.users import ApiKeys from app.classes.shared.helpers import Helpers +from app.classes.shared.file_helpers import FileHelpers from app.classes.shared.main_controller import Controller from app.classes.shared.translation import Translation from app.classes.models.management import DatabaseShortcuts @@ -24,15 +25,22 @@ class BaseHandler(tornado.web.RequestHandler): helper: Helpers controller: Controller translator: Translation + file_helper: FileHelpers # noinspection PyAttributeOutsideInit def initialize( - self, helper=None, controller=None, tasks_manager=None, translator=None + self, + helper=None, + controller=None, + tasks_manager=None, + translator=None, + file_helper=None, ): self.helper = helper self.controller = controller self.tasks_manager = tasks_manager self.translator = translator + self.file_helper = file_helper def set_default_headers(self) -> None: """ diff --git a/app/classes/web/routes/api/api_handlers.py b/app/classes/web/routes/api/api_handlers.py index 43d21d08..102161ab 100644 --- a/app/classes/web/routes/api/api_handlers.py +++ b/app/classes/web/routes/api/api_handlers.py @@ -51,6 +51,7 @@ from app.classes.web.routes.api.users.user.pfp import ApiUsersUserPfpHandler from app.classes.web.routes.api.users.user.public import ApiUsersUserPublicHandler from app.classes.web.routes.api.crafty.config.index import ApiCraftyConfigIndexHandler from app.classes.web.routes.api.crafty.clogs.index import ApiCraftyLogIndexHandler +from app.classes.web.routes.api.crafty.imports.index import ApiImportFilesIndexHandler def api_handlers(handler_args): @@ -76,6 +77,11 @@ def api_handlers(handler_args): ApiCraftyLogIndexHandler, handler_args, ), + ( + r"/api/v2/import/file/unzip/?", + ApiImportFilesIndexHandler, + handler_args, + ), # User routes ( r"/api/v2/users/?", diff --git a/app/classes/web/routes/api/crafty/imports/index.py b/app/classes/web/routes/api/crafty/imports/index.py new file mode 100644 index 00000000..3bcae9af --- /dev/null +++ b/app/classes/web/routes/api/crafty/imports/index.py @@ -0,0 +1,123 @@ +import os +import logging +import json +import html +from jsonschema import validate +from jsonschema.exceptions import ValidationError +from app.classes.models.crafty_permissions import EnumPermissionsCrafty +from app.classes.shared.helpers import Helpers +from app.classes.web.base_api_handler import BaseApiHandler + +logger = logging.getLogger(__name__) +files_get_schema = { + "type": "object", + "properties": { + "page": {"type": "string", "minLength": 1}, + "folder": {"type": "string"}, + "unzip": {"type": "boolean", "default": "True"}, + }, + "additionalProperties": False, + "minProperties": 1, +} + + +class ApiImportFilesIndexHandler(BaseApiHandler): + def post(self): + auth_data = self.authenticate_user() + if not auth_data: + return + + if ( + EnumPermissionsCrafty.SERVER_CREATION + not in self.controller.crafty_perms.get_crafty_permissions_list( + auth_data[4]["user_id"] + ) + and not auth_data[4]["superuser"] + ): + # if the user doesn't have Files or Backup permission, return an error + return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) + + try: + data = json.loads(self.request.body) + except json.decoder.JSONDecodeError as e: + return self.finish_json( + 400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)} + ) + try: + validate(data, files_get_schema) + except ValidationError as e: + return self.finish_json( + 400, + { + "status": "error", + "error": "INVALID_JSON_SCHEMA", + "error_data": str(e), + }, + ) + # TODO: limit some columns for specific permissions? + folder = data["folder"] + user_id = auth_data[4]["user_id"] + root_path = False + if data["unzip"]: + if Helpers.check_file_exists(folder): + folder = self.file_helper.unzip_server(folder, user_id) + root_path = True + else: + if user_id: + user_lang = self.controller.users.get_user_lang_by_id(user_id) + self.helper.websocket_helper.broadcast_user( + user_id, + "send_start_error", + { + "error": self.helper.translation.translate( + "error", "no-file", user_lang + ) + }, + ) + else: + if not self.helper.check_path_exists(folder): + if user_id: + user_lang = self.controller.users.get_user_lang_by_id(user_id) + self.helper.websocket_helper.broadcast_user( + user_id, + "send_start_error", + { + "error": self.helper.translation.translate( + "error", "no-file", user_lang + ) + }, + ) + return_json = { + "root_path": { + "path": folder, + "top": root_path, + } + } + + dir_list = [] + unsorted_files = [] + file_list = os.listdir(folder) + for item in file_list: + if os.path.isdir(os.path.join(folder, item)): + dir_list.append(item) + else: + unsorted_files.append(item) + file_list = sorted(dir_list, key=str.casefold) + sorted( + unsorted_files, key=str.casefold + ) + for raw_filename in file_list: + filename = html.escape(raw_filename) + rel = os.path.join(folder, raw_filename) + dpath = os.path.join(folder, filename) + dpath = self.helper.wtol_path(dpath) + if os.path.isdir(rel): + return_json[filename] = { + "path": dpath, + "dir": True, + } + else: + return_json[filename] = { + "path": dpath, + "dir": False, + } + self.finish_json(200, {"status": "ok", "data": return_json}) diff --git a/app/classes/web/tornado_handler.py b/app/classes/web/tornado_handler.py index d2b047d7..7b56220b 100644 --- a/app/classes/web/tornado_handler.py +++ b/app/classes/web/tornado_handler.py @@ -48,13 +48,14 @@ class Webserver: controller: Controller helper: Helpers - def __init__(self, helper, controller, tasks_manager): + def __init__(self, helper, controller, tasks_manager, file_helper): self.ioloop = None self.http_server = None self.https_server = None self.helper = helper self.controller = controller self.tasks_manager = tasks_manager + self.file_helper = file_helper self._asyncio_patch() @staticmethod @@ -146,6 +147,7 @@ class Webserver: "controller": self.controller, "tasks_manager": self.tasks_manager, "translator": self.helper.translation, + "file_helper": self.file_helper, } handlers = [ (r"/", DefaultHandler, handler_args), diff --git a/app/classes/web/websocket_handler.py b/app/classes/web/websocket_handler.py index 78b33951..a2e7f5a4 100644 --- a/app/classes/web/websocket_handler.py +++ b/app/classes/web/websocket_handler.py @@ -18,12 +18,18 @@ class SocketHandler(tornado.websocket.WebSocketHandler): io_loop = None def initialize( - self, helper=None, controller=None, tasks_manager=None, translator=None + self, + helper=None, + controller=None, + tasks_manager=None, + translator=None, + file_helper=None, ): self.helper = helper self.controller = controller self.tasks_manager = tasks_manager self.translator = translator + self.file_helper = file_helper self.io_loop = tornado.ioloop.IOLoop.current() def get_remote_ip(self): diff --git a/main.py b/main.py index 2338517b..8ba07b6a 100644 --- a/main.py +++ b/main.py @@ -171,7 +171,7 @@ if __name__ == "__main__": Console.info("Remote change complete.") import3 = Import3(helper, controller) - tasks_manager = TasksManager(helper, controller) + tasks_manager = TasksManager(helper, controller, file_helper) tasks_manager.start_webserver() def signal_handler(signum, _frame): From f6c61e17643d348f7d3d218f4e35cfd27736a1b4 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Thu, 31 Aug 2023 21:35:50 -0400 Subject: [PATCH 071/210] Add bedrock request for files TODO: Finish zip imports --- .../templates/server/bedrock_wizard.html | 722 ++++++++++-------- 1 file changed, 384 insertions(+), 338 deletions(-) diff --git a/app/frontend/templates/server/bedrock_wizard.html b/app/frontend/templates/server/bedrock_wizard.html index e0100bb5..c10285a1 100644 --- a/app/frontend/templates/server/bedrock_wizard.html +++ b/app/frontend/templates/server/bedrock_wizard.html @@ -27,77 +27,86 @@
    {% if data["server_api"] and data["online"] %}
    - {% else %} -
    - - {% end %} -
    - - -
    + {% else %} +
    + + {% end %} +
    + + +
    + +
    +
    +
    +
    +

    + {{ translate('serverWizard', 'addRole', data['lang']) }} + - {{ translate('serverWizard', 'autoCreate', + data['lang']) }} +

    +
    +
    +
    +
    + {% for r in data['roles'] %} + + {% end %} +
    +
    + + +
    + {% if not data["server_api"] and data["online"] %} +
    - - - -
    - {% if not data["server_api"] and data["online"] %} -
    -

     {{ translate('error', 'bedrockError', data['lang']) }} {{ translate('error', 'craftyStatus', data['lang']) }} -  {{ translate('error', 'serverJars2', data['lang']) }}

    - {% end %} - {% if not data["online"] %} -
    -

     {{ translate('error', 'noInternet', data['lang']) }}

    - {% end %} + {% end %} + {% if not data["online"] %} +
    +

     {{ translate('error', 'noInternet', data['lang']) }}

    +
    + {% end %} @@ -112,31 +121,37 @@
    - +
    - +
    - +

    -

    {{ translate('serverWizard', 'quickSettings', data['lang']) }} - {{ translate('serverWizard', 'quickSettingsDescription', +

    {{ translate('serverWizard', 'quickSettings', data['lang']) }} - {{ translate('serverWizard', 'quickSettingsDescription', data['lang']) }}


    - +
    -

    +

    {{ translate('serverWizard', 'addRole', data['lang']) }} - {{ translate('serverWizard', 'autoCreate', data['lang']) }} @@ -173,13 +188,15 @@

    - +
    - +
    @@ -191,21 +208,26 @@
    - +
    -

    {{ translate('serverWizard', 'quickSettings', data['lang']) }} - {{ translate('serverWizard', 'quickSettingsDescription', data['lang']) }} +

    {{ translate('serverWizard', 'quickSettings', data['lang']) }} - {{ translate('serverWizard', 'quickSettingsDescription', data['lang']) + }}


    - +
    -

    +

    {{ translate('serverWizard', 'addRole', data['lang']) }} - {{ translate('serverWizard', 'autoCreate', data['lang']) }} @@ -229,7 +251,8 @@

    -

  • @@ -96,7 +96,8 @@ $("#notif-count").html(data.length); $("#announcements").html(text); } else { - $("#announcements").html("

    No notifications

    "); + $("#announcements").html(`

    +

    `); } $(".clear-button").on("click", function (event) { console.log("CLEAR BUTTON") @@ -108,7 +109,8 @@ localStorage.setItem("notif-count", notif_count); $("#notif-count").html(notif_count); } else { - $("#announcements").html("

    No notifications

    ") + $("#announcements").html(`

    +

    `) $("#notif-count").html(""); } From d61da42fa9362df423ffe05438fc25075d1fbb5c Mon Sep 17 00:00:00 2001 From: Zedifus Date: Sat, 2 Sep 2023 14:17:44 +0100 Subject: [PATCH 077/210] Bump crypto to resolve #267 & #268 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 98e095f1..4c182563 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ bleach==4.1 cached_property==1.5.2 colorama==0.4 croniter==1.3.5 -cryptography==41.0.1 +cryptography==41.0.3 libgravatar==1.0.0 peewee==3.13 pexpect==4.8 From 5f088c4a1cff41a886a21d1e5385710e8e8d933e Mon Sep 17 00:00:00 2001 From: Zedifus Date: Sat, 2 Sep 2023 14:24:40 +0100 Subject: [PATCH 078/210] Bump tornado to resolve #269 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 98e095f1..79a37409 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,7 @@ pyjwt==2.4.0 PyYAML==6.0.1 requests==2.31 termcolor==1.1 -tornado==6.3.2 +tornado==6.3.3 tzlocal==4.0 jsonschema==4.5.1 orjson==3.8.12 From 34fe9c13267c0757bdb7c4447f79821810da5f76 Mon Sep 17 00:00:00 2001 From: Silversthorn Date: Sat, 2 Sep 2023 21:51:28 +0200 Subject: [PATCH 079/210] Using API Call for Status Page --- app/classes/models/server_stats.py | 2 + app/classes/shared/server.py | 11 +- app/classes/web/routes/api/api_handlers.py | 9 ++ .../web/routes/api/servers/server/status.py | 32 +++++ app/classes/web/tornado_handler.py | 3 +- app/classes/web/websocket_handler.py | 42 ------ app/frontend/templates/panel/dashboard.html | 130 ++++++------------ app/frontend/templates/public/status.html | 47 +++++-- app/frontend/templates/public_base.html | 113 --------------- app/migrations/stats/20230827_add_icon.py | 16 +++ 10 files changed, 137 insertions(+), 268 deletions(-) create mode 100644 app/classes/web/routes/api/servers/server/status.py create mode 100644 app/migrations/stats/20230827_add_icon.py diff --git a/app/classes/models/server_stats.py b/app/classes/models/server_stats.py index d3b8cdf0..5a38ac1e 100644 --- a/app/classes/models/server_stats.py +++ b/app/classes/models/server_stats.py @@ -50,6 +50,7 @@ class ServerStats(Model): max = IntegerField(default=0) players = CharField(default="") desc = CharField(default="Unable to Connect") + icon = CharField(default="") version = CharField(default="") updating = BooleanField(default=False) waiting_start = BooleanField(default=False) @@ -183,6 +184,7 @@ class HelperServerStats: ServerStats.max: server_stats.get("max", False), ServerStats.players: server_stats.get("players", False), ServerStats.desc: server_stats.get("desc", False), + ServerStats.icon: server_stats.get("icon", None), ServerStats.version: server_stats.get("version", False), } ).execute(self.database) diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py index 4682c22a..45675de5 100644 --- a/app/classes/shared/server.py +++ b/app/classes/shared/server.py @@ -1536,14 +1536,6 @@ class ServerInstance: 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: - Console.critical("Can't broadcast server status to websocket") - def get_servers_stats(self): server_stats = {} @@ -1605,6 +1597,7 @@ class ServerInstance: "players": ping_data.get("players", False), "desc": ping_data.get("server_description", False), "version": ping_data.get("server_version", False), + "icon": ping_data.get("server_icon"), } else: server_stats = { @@ -1623,6 +1616,7 @@ class ServerInstance: "players": False, "desc": False, "version": False, + "icon": None, } return server_stats @@ -1671,7 +1665,6 @@ class ServerInstance: } server_stats = {} - server = HelperServers.get_server_obj(server_id) if not server: return {} server_dt = HelperServers.get_server_data_by_id(server_id) diff --git a/app/classes/web/routes/api/api_handlers.py b/app/classes/web/routes/api/api_handlers.py index 36eeb4f0..30e4d0d0 100644 --- a/app/classes/web/routes/api/api_handlers.py +++ b/app/classes/web/routes/api/api_handlers.py @@ -12,6 +12,7 @@ from app.classes.web.routes.api.roles.index import ApiRolesIndexHandler from app.classes.web.routes.api.roles.role.index import ApiRolesRoleIndexHandler from app.classes.web.routes.api.roles.role.servers import ApiRolesRoleServersHandler from app.classes.web.routes.api.roles.role.users import ApiRolesRoleUsersHandler + from app.classes.web.routes.api.servers.index import ApiServersIndexHandler from app.classes.web.routes.api.servers.server.action import ( ApiServersServerActionHandler, @@ -21,6 +22,9 @@ from app.classes.web.routes.api.servers.server.logs import ApiServersServerLogsH from app.classes.web.routes.api.servers.server.public import ( ApiServersServerPublicHandler, ) +from app.classes.web.routes.api.servers.server.status import ( + ApiServersServerStatusHandler, +) from app.classes.web.routes.api.servers.server.stats import ApiServersServerStatsHandler from app.classes.web.routes.api.servers.server.history import ( ApiServersServerHistoryHandler, @@ -110,6 +114,11 @@ def api_handlers(handler_args): ApiServersIndexHandler, handler_args, ), + ( + r"/api/v2/servers/status/?", + ApiServersServerStatusHandler, + handler_args, + ), ( r"/api/v2/servers/([0-9]+)/?", ApiServersServerIndexHandler, diff --git a/app/classes/web/routes/api/servers/server/status.py b/app/classes/web/routes/api/servers/server/status.py new file mode 100644 index 00000000..aab501d8 --- /dev/null +++ b/app/classes/web/routes/api/servers/server/status.py @@ -0,0 +1,32 @@ +import logging +from app.classes.web.base_api_handler import BaseApiHandler + +logger = logging.getLogger(__name__) + + +class ApiServersServerStatusHandler(BaseApiHandler): + def get(self): + servers_status = [] + servers_list = self.controller.servers.get_all_servers_stats() + for server in servers_list: + if server.get("server_data").get("show_status") is True: + servers_status.append( + { + "id": server.get("server_data").get("server_id"), + "world_name": server.get("stats").get("world_name"), + "running": server.get("stats").get("running"), + "online": server.get("stats").get("online"), + "max": server.get("stats").get("max"), + "version": server.get("stats").get("version"), + "desc": server.get("stats").get("desc"), + "icon": server.get("stats").get("icon"), + } + ) + + self.finish_json( + 200, + { + "status": "ok", + "data": servers_status, + }, + ) diff --git a/app/classes/web/tornado_handler.py b/app/classes/web/tornado_handler.py index 0a7f0cb8..643a018a 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 AuthSocketHandler, PublicSocketHandler +from app.classes.web.websocket_handler import AuthSocketHandler 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 @@ -154,7 +154,6 @@ class Webserver: (r"/ajax/(.*)", AjaxHandler, handler_args), (r"/files/(.*)", FileHandler, 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/websocket_handler.py b/app/classes/web/websocket_handler.py index d15d6c7d..ae166e3a 100644 --- a/app/classes/web/websocket_handler.py +++ b/app/classes/web/websocket_handler.py @@ -176,45 +176,3 @@ class AuthSocketHandler(BaseSocketHandler): ) WebSocketManager().add_client(self) logger.debug("Opened 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 - - 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): - 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/panel/dashboard.html b/app/frontend/templates/panel/dashboard.html index 9aa207eb..377a109f 100644 --- a/app/frontend/templates/panel/dashboard.html +++ b/app/frontend/templates/panel/dashboard.html @@ -58,13 +58,11 @@
    -

    +

    {{ translate('dashboard', 'cpuUsage', data['lang']) }}: {{ data.get('hosts_data').get('cpu_usage') }}

    -

    +

    {{ translate('dashboard', 'memUsage', data['lang']) }}: {{ data.get('hosts_data').get('mem_percent') }}%

    @@ -111,12 +109,9 @@ {% for item in data['hosts_data']['disk_json'] %} {% if item["mount"] in data["monitored"] %}
    -

    +

    {{item["mount"]}}

    -
    +
    {{item["used"]}} / + " role="progressbar" style="color: black; height: 100%; width: {{item['percent_used']}}%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">{{item["used"]}} / {{item["total"]}}
    @@ -153,9 +147,7 @@ data['lang']) }} {% if len(data['servers']) > 0 %} {% if data['user_data']['hints'] %} - + {% end %} {% end %}
      {{ @@ -193,8 +185,7 @@ {% if server['alert'] %} - + {{ server['server_data']['server_name'] }}  {% else %} @@ -208,13 +199,11 @@ {% if server['user_command_permission'] %} {% if server['stats']['importing'] and server['stats']['running'] %} -  {{ translate('serverTerm', 'installing', +  {{ translate('serverTerm', 'installing', data['lang']) }} {% elif server['stats']['updating']%} -  {{ translate('serverTerm', 'updating', +  {{ translate('serverTerm', 'updating', data['lang']) }} {% elif server['stats']['waiting_start']%} @@ -226,31 +215,25 @@ {{ translate('serverTerm', 'importing', data['lang']) }} {% elif server['stats']['running'] %} - +   - +   - +   {% else %} - +   - +   - +   {% end %} @@ -258,8 +241,7 @@ -
    +
    + " role="progressbar" style="width: {{server['stats']['cpu']}}%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
    {{server['stats']['cpu']}}% -
    +
    + " role="progressbar" style="width: {{server['stats']['mem_percent']}}%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
    {{server['stats']['mem_percent']}}% - @@ -305,8 +284,7 @@ data['lang']) }}
    {% if server['stats']['desc'] != 'False' %} -
    {{ +
    {{ server['stats']['desc'] }}

    {% end %} @@ -334,16 +312,14 @@

    - + {% end %}
    {% for server in data['failed_servers'] %} -  {{server['server_name']}} +  {{server['server_name']}} @@ -368,28 +344,22 @@
    - + {% if server['stats']['running'] %} {{ translate('dashboard', 'online', data['lang']) }} @@ -410,23 +380,17 @@ {% if server['stats']['running'] %}
    @@ -452,31 +416,24 @@ {% elif server['stats']['importing']%} {% else %} @@ -488,15 +445,13 @@
    -
    +
    {{ translate('dashboard', 'cpuUsage', data['lang']) }}
    -
    +
    + " role="progressbar" style="width: {{server['stats']['cpu']}}%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
    {{server['stats']['cpu']}}%
    @@ -514,8 +468,7 @@
    {{ translate('dashboard', 'memUsage', data['lang']) }}
    -
    +
    + " role="progressbar" style="width: {{server['stats']['mem_percent']}}%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
    {{server['stats']['mem_percent']}}% - @@ -554,8 +506,7 @@ data['lang']) }}
    {% if server['stats']['desc'] != 'False' %} -
    +
    {{ server['stats']['desc'] }}

    {% end %} @@ -796,6 +747,7 @@ /* Update Motd */ let motd = ""; if (server.desc) { + m_motd = `` + server.desc + ``; motd = `` + server.desc + ``; m_server_infos = server_infos + '
    ' + motd + '
    ' + "
    "; server_infos = server_infos + '
    ' + motd + '
    ' + "
    "; diff --git a/app/frontend/templates/public/status.html b/app/frontend/templates/public/status.html index b93c0479..4d99f7a6 100644 --- a/app/frontend/templates/public/status.html +++ b/app/frontend/templates/public/status.html @@ -44,9 +44,14 @@ {% if server['stats']['desc'] != 'False' %} - icon - {{ - server['stats']['desc'] }}
    +
    +
    + icon +
    +
    + {{ server['stats']['desc'] }} +
    +
    {% end %} @@ -183,7 +188,7 @@ m_server_online_status = document.getElementById('m_server_online_status_' + server.id); /* TODO Update each element */ - if (server.int_ping_results) { + if (server.running) { /* Update Players */ if (server.players) { server_players.innerHTML = server.online + ` / ` + server.max + ` {{ translate('dashboard', 'max', data['lang']) }}
    ` @@ -193,16 +198,18 @@ var motd = ""; if (server.desc) { if (server.icon) { - motd = `icon `; + img_motd = `icon `; m_motd = `icon `; } else { - motd = `icon `; + img_motd = `icon `; m_motd = `icon `; } - motd = motd + `` + server.desc + `
    `; + desc_motd = `` + server.desc + `
    `; m_motd = m_motd + `
    ` + server.desc + `
    `; + + motd = `
    ` + img_motd + `
    ` + desc_motd + `
    `; server_motd.innerHTML = motd; m_server_motd.innerHTML = m_motd; } @@ -235,17 +242,31 @@ } function update_servers_status(data) { - console.log(data); - update_one_server_status(data[0]); + console.log("update servers"); + data.forEach(server => { + console.log(server); + update_one_server_status(server); + }); display_motd(); } + function refreshStatus() { + let xmlHttp = new XMLHttpRequest(); + xmlHttp.onreadystatechange = function () { + if (this.readyState == 4 && this.status == 200) { + var myData = JSON.parse(this.responseText); + update_servers_status(myData.data); + } + }; + xmlHttp.open('GET', '/api/v2/servers/status', true); + xmlHttp.send(); + + setTimeout(refreshStatus, 30000); + } + $(document).ready(function () { console.log("ready!"); - - if (webSocket) { - webSocket.on('update_server_status', update_servers_status); - } + refreshStatus(); }()); diff --git a/app/frontend/templates/public_base.html b/app/frontend/templates/public_base.html index 77632762..6513e4e9 100644 --- a/app/frontend/templates/public_base.html +++ b/app/frontend/templates/public_base.html @@ -55,120 +55,7 @@ - {% block js %} diff --git a/app/migrations/stats/20230827_add_icon.py b/app/migrations/stats/20230827_add_icon.py new file mode 100644 index 00000000..2d7970e8 --- /dev/null +++ b/app/migrations/stats/20230827_add_icon.py @@ -0,0 +1,16 @@ +# Generated by database migrator +import peewee + + +def migrate(migrator, database, **kwargs): + migrator.add_columns("server_stats", icon=peewee.CharField(null=True)) + """ + Write your migrations here. + """ + + +def rollback(migrator, database, **kwargs): + migrator.drop_columns("server_stats", ["icon"]) + """ + Write your rollback migrations here. + """ From a63c2dcf003d0b60dd326fc0c991662f4b590f3f Mon Sep 17 00:00:00 2001 From: Silversthorn Date: Sat, 2 Sep 2023 22:19:17 +0200 Subject: [PATCH 080/210] Removing saving data realtime => from 5s to 30s --- app/classes/shared/server.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py index 45675de5..17773833 100644 --- a/app/classes/shared/server.py +++ b/app/classes/shared/server.py @@ -253,6 +253,23 @@ class ServerInstance: seconds=5, id="stats_" + str(self.server_id), ) + logger.info(f"Saving server statistics {self.name} every {30} seconds") + Console.info(f"Saving server statistics {self.name} every {30} seconds") + try: + self.server_scheduler.add_job( + self.record_server_stats, + "interval", + seconds=30, + id="save_stats_" + str(self.server_id), + ) + except: + self.server_scheduler.remove_job("save_" + str(self.server_id)) + self.server_scheduler.add_job( + self.record_server_stats, + "interval", + seconds=30, + id="save_" + str(self.server_id), + ) def setup_server_run_command(self): # configure the server @@ -1526,7 +1543,7 @@ class ServerInstance: total_players += int(raw_ping_result.get("online")) max_players += int(raw_ping_result.get("max")) - self.record_server_stats() + # self.record_server_stats() if (len(servers_ping) > 0) & (len(WebSocketManager().auth_clients) > 0): try: From ff1ba23830c1c585bc23a9770bb6e5d389fcba2d Mon Sep 17 00:00:00 2001 From: Andrew Date: Sat, 2 Sep 2023 19:35:16 -0400 Subject: [PATCH 081/210] Add updates to notifs --- app/classes/shared/helpers.py | 5 +++-- app/classes/shared/tasks.py | 11 +++++++++++ .../web/routes/api/crafty/announcements/index.py | 13 +++---------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/app/classes/shared/helpers.py b/app/classes/shared/helpers.py index 9c74135d..0f36886a 100644 --- a/app/classes/shared/helpers.py +++ b/app/classes/shared/helpers.py @@ -579,8 +579,7 @@ class Helpers: return version_data - @staticmethod - def get_announcements(): + def get_announcements(self): data = [] try: response = requests.get("https://craftycontrol.com/notify.json", timeout=2) @@ -588,6 +587,8 @@ class Helpers: except Exception as e: logger.error(f"Failed to fetch notifications with error: {e}") + if self.update_available: + data.append(self.update_available) return data def get_version_string(self): diff --git a/app/classes/shared/tasks.py b/app/classes/shared/tasks.py index acdc1cac..4d878066 100644 --- a/app/classes/shared/tasks.py +++ b/app/classes/shared/tasks.py @@ -726,12 +726,23 @@ class TasksManager: def check_for_updates(self): logger.info("Checking for Crafty updates...") self.helper.update_available = self.helper.check_remote_version() + remote = self.helper.update_available if self.helper.update_available: logger.info(f"Found new version {self.helper.update_available}") else: logger.info( "No updates found! You are on the most up to date Crafty version." ) + if self.helper.update_available: + self.helper.update_available = { + "id": str(remote), + "title": f"{remote} Update Available", + "date": "", + "desc": "Instructions for updating can be found" + " by clicking this notification.", + "link": "https://docs.craftycontrol.com/pages/" + "getting-started/installation/linux/?h=updating#updating-crafty", + } logger.info("Refreshing Gravatar PFPs...") for user in HelperUsers.get_all_users(): if user.email: diff --git a/app/classes/web/routes/api/crafty/announcements/index.py b/app/classes/web/routes/api/crafty/announcements/index.py index 12430725..ff274256 100644 --- a/app/classes/web/routes/api/crafty/announcements/index.py +++ b/app/classes/web/routes/api/crafty/announcements/index.py @@ -40,6 +40,8 @@ class ApiAnnounceIndexHandler(BaseApiHandler): for item in cleared[:]: if item not in res: cleared.remove(item) + updata = {"cleared_notifs": ",".join(cleared)} + self.controller.users.update_user(auth_data[4]["user_id"], updata) if len(cleared) > 0: for item in data[:]: if item["id"] in cleared: @@ -93,7 +95,7 @@ class ApiAnnounceIndexHandler(BaseApiHandler): for item in cleared_notifs[:]: if item not in res: cleared_notifs.remove(item) - if is_valid_uuid(data["id"]): + if str(data["id"]) in str(res): cleared_notifs.append(data["id"]) else: self.finish_json(200, {"status": "error", "error": "INVALID_DATA"}) @@ -107,12 +109,3 @@ class ApiAnnounceIndexHandler(BaseApiHandler): "data": {}, }, ) - - -def is_valid_uuid(value): - try: - uuid.UUID(str(value)) - - return True - except ValueError: - return False From 5e4560c4610a1b9f50bb2ae55aec59b908638fe2 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sat, 2 Sep 2023 19:35:25 -0400 Subject: [PATCH 082/210] Remove from ajax handler --- app/classes/web/ajax_handler.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/classes/web/ajax_handler.py b/app/classes/web/ajax_handler.py index efe8d2fa..1c703ecc 100644 --- a/app/classes/web/ajax_handler.py +++ b/app/classes/web/ajax_handler.py @@ -83,11 +83,6 @@ class AjaxHandler(BaseHandler): except Exception as e: logger.warning(f"Skipping Log Line due to error: {e}") - elif page == "announcements": - data = Helpers.get_announcements() - page_data["notify_data"] = data - self.render_page("ajax/notify.html", page_data) - elif page == "get_zip_tree": path = self.get_argument("path", None) From a983cc88687c566d63db056182951f2b20805266 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sun, 3 Sep 2023 13:20:02 -0400 Subject: [PATCH 083/210] Squash bugs in java imports/bedrock imports --- app/classes/shared/main_controller.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/classes/shared/main_controller.py b/app/classes/shared/main_controller.py index eb067e7e..52e98138 100644 --- a/app/classes/shared/main_controller.py +++ b/app/classes/shared/main_controller.py @@ -354,7 +354,7 @@ class Controller: server_file = create_data["jarfile"] raise NotImplementedError("Not yet implemented") self.import_helper.import_java_zip_server() - if create_data["minecraft_java"]: + if data["create_type"] == "minecraft_java": _create_server_properties_if_needed( create_data["server_properties_port"], ) @@ -417,6 +417,7 @@ class Controller: else: server_command = f"./{create_data['executable']}" logger.debug("command: " + server_command) + server_file = create_data["executable"] elif root_create_data["create_type"] == "import_zip": # TODO: Copy files from the zip file to the new server directory raise NotImplementedError("Not yet implemented") @@ -520,7 +521,7 @@ class Controller: ServersController.set_import(new_server_id) self.import_helper.import_jar_server( create_data["existing_server_path"], - os.path.join(new_server_path, server_fs_uuid), + new_server_path, monitoring_port, new_server_id, ) @@ -534,6 +535,7 @@ class Controller: new_server_path, new_server_id ) elif root_create_data["create_type"] == "import_server": + ServersController.set_import(new_server_id) full_exe_path = os.path.join(new_server_path, create_data["executable"]) self.import_helper.import_bedrock_server( create_data["existing_server_path"], @@ -543,6 +545,7 @@ class Controller: new_server_id, ) elif root_create_data["create_type"] == "import_zip": + ServersController.set_import(new_server_id) full_exe_path = os.path.join(new_server_path, create_data["executable"]) self.import_helper.import_bedrock_zip_server( create_data["zip_path"], From 1898fd1945101e5cd6849affd1fc1c8158a0802c Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sun, 3 Sep 2023 13:22:17 -0400 Subject: [PATCH 084/210] Squash upload bug created in commit 27f6c4c --- app/classes/web/upload_handler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/classes/web/upload_handler.py b/app/classes/web/upload_handler.py index adce3ab9..88510a80 100644 --- a/app/classes/web/upload_handler.py +++ b/app/classes/web/upload_handler.py @@ -25,11 +25,13 @@ class UploadHandler(BaseHandler): controller: Controller = None, tasks_manager=None, translator=None, + file_helper=None, ): self.helper = helper self.controller = controller self.tasks_manager = tasks_manager self.translator = translator + self.file_helper = file_helper def prepare(self): # Class & Function Defination From 7ff77c598f15093dd693c8928b72257ce68fd1f6 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sun, 3 Sep 2023 13:22:48 -0400 Subject: [PATCH 085/210] Add upload support for apiv2 This should get changed once upload handler is gone. --- app/classes/web/routes/api/crafty/imports/index.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/classes/web/routes/api/crafty/imports/index.py b/app/classes/web/routes/api/crafty/imports/index.py index 3bcae9af..0e88f484 100644 --- a/app/classes/web/routes/api/crafty/imports/index.py +++ b/app/classes/web/routes/api/crafty/imports/index.py @@ -14,6 +14,7 @@ files_get_schema = { "properties": { "page": {"type": "string", "minLength": 1}, "folder": {"type": "string"}, + "upload": {"type": "boolean", "default": "False"}, "unzip": {"type": "boolean", "default": "True"}, }, "additionalProperties": False, @@ -59,6 +60,11 @@ class ApiImportFilesIndexHandler(BaseApiHandler): user_id = auth_data[4]["user_id"] root_path = False if data["unzip"]: + # This is awful. Once uploads go to return + # JSON we need to remove this and just send + # the path. + if data["upload"]: + folder = os.path.join(self.controller.project_root, "imports", folder) if Helpers.check_file_exists(folder): folder = self.file_helper.unzip_server(folder, user_id) root_path = True From 90905a70f5b9a2b2643461b62c603e9a2202df45 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sun, 3 Sep 2023 13:23:18 -0400 Subject: [PATCH 086/210] General bug fixes/upload support --- .../static/assets/js/shared/root-dir.js | 141 ++++++++ .../templates/server/bedrock_wizard.html | 163 +-------- app/frontend/templates/server/wizard.html | 336 +++++------------- 3 files changed, 234 insertions(+), 406 deletions(-) create mode 100644 app/frontend/static/assets/js/shared/root-dir.js diff --git a/app/frontend/static/assets/js/shared/root-dir.js b/app/frontend/static/assets/js/shared/root-dir.js new file mode 100644 index 00000000..9f3f14ac --- /dev/null +++ b/app/frontend/static/assets/js/shared/root-dir.js @@ -0,0 +1,141 @@ + +function show_file_tree() { + $("#dir_select").modal(); +} +function getDirView(event = false) { + if (event) { + try { + path = event.target.parentElement.getAttribute('data-path'); + if (event.target.parentElement.classList.contains('clicked')) { + + if ($(`#${path}span`).hasClass('files-tree-title')) { + $(`#${path}ul`).toggleClass("d-block"); + $(`#${path}span`).toggleClass("tree-caret-down"); + } + return; + } else { + getTreeView(path); + } + } catch { + console.log("Well that failed"); + } + } else { + if ($("#root_files_button").hasClass("clicked")) { + path = $("#zip_server_path").val(); + getTreeView(path, true); + } else { + path = $("#file-uploaded").val(); + getTreeView(path, true, true); + } + } + +} + +async function getTreeView(path, unzip = false, upload = false) { + var token = getCookie("_xsrf"); + console.log("IN TREE VIEW") + console.log({ "page": "import", "folder": path, "upload": upload, "unzip": unzip }); + let res = await fetch(`/api/v2/import/file/unzip/`, { + method: 'POST', + headers: { + 'X-XSRFToken': token + }, + body: JSON.stringify({ "page": "import", "folder": path, "upload": upload, "unzip": unzip }), + }); + let responseData = await res.json(); + if (responseData.status === "ok") { + console.log(responseData); + process_tree_response(responseData); + var x = document.querySelector('.bootbox'); + if (x) { + x.remove() + } + var x = document.querySelector('.modal-backdrop'); + if (x) { + x.remove() + } + show_file_tree(); + + } else { + + bootbox.alert({ + title: responseData.status, + message: responseData.error + }); + } +} + +function process_tree_response(response) { + const styles = window.getComputedStyle(document.getElementById("lower_half")); + //If this value is still hidden we know the user is executing a zip import and not an upload + if (styles.visibility === "hidden") { + document.getElementById('zip_submit').disabled = false; + } else { + document.getElementById('upload_submit').disabled = false; + } + let path = response.data.root_path.path; + $(".root-input").val(response.data.root_path.path); + text = `
      `; + Object.entries(response.data).forEach(([key, value]) => { + if (key === "root_path" || key === "db_stats") { + //continue is not valid in for each. Return acts as a continue. + return; + } + + let dpath = value.path; + let filename = key; + if (value.dir) { + text += `
    • +
      + + + + + ${filename} + +
    • ` + } else { + text += `
    • + ${filename}
    • + ` + } + }); + text += `
    `; + + if (response.data.root_path.top) { + try { + document.getElementById('main-tree-div').innerHTML += text; + document.getElementById('main-tree').parentElement.classList.add("clicked"); + } catch { + document.getElementById('files-tree').innerHTML = text; + } + } else { + try { + document.getElementById(path + "span").classList.add('tree-caret-down'); + document.getElementById(path).innerHTML += text; + document.getElementById(path).classList.add("clicked"); + } catch { + console.log("Bad") + } + + var toggler = document.getElementById(path + "span"); + + if (toggler.classList.contains('files-tree-title')) { + document.getElementById(path + "span").addEventListener("click", function caretListener() { + document.getElementById(path + "ul").classList.toggle("d-block"); + document.getElementById(path + "span").classList.toggle("tree-caret-down"); + }); + } + } +} + +function getToggleMain(event) { + path = event.target.parentElement.getAttribute('data-path'); + document.getElementById("files-tree").classList.toggle("d-block"); + document.getElementById(path + "span").classList.toggle("tree-caret-down"); + document.getElementById(path + "span").classList.toggle("tree-caret"); +} \ No newline at end of file diff --git a/app/frontend/templates/server/bedrock_wizard.html b/app/frontend/templates/server/bedrock_wizard.html index c10285a1..d91a1eb5 100644 --- a/app/frontend/templates/server/bedrock_wizard.html +++ b/app/frontend/templates/server/bedrock_wizard.html @@ -184,7 +184,7 @@

    {{ translate('serverWizard', 'importZip', data['lang']) }}


    - \ +
    @@ -265,7 +265,7 @@