From 438b7cc6ab9a1325d38fd6cab8cb9bebcb9807d3 Mon Sep 17 00:00:00 2001 From: Zedifus Date: Fri, 19 Apr 2024 01:57:48 +0100 Subject: [PATCH 01/26] Add jinja2 @latest Sort requirements alphabetically --- requirements.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index ed0f7698..ae7b7891 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,10 +5,14 @@ cached_property==1.5.2 colorama==0.4.6 croniter==1.4.1 cryptography==42.0.4 +jinja2==3.1.3 +jsonschema==4.19.1 libgravatar==1.0.4 nh3==0.2.14 +orjson==3.9.15 packaging==23.2 peewee==3.13 +prometheus-client==0.17.1 psutil==5.9.5 pyOpenSSL==24.0.0 pyjwt==2.8.0 @@ -17,6 +21,3 @@ requests==2.31.0 termcolor==1.1 tornado==6.3.3 tzlocal==5.1 -jsonschema==4.19.1 -orjson==3.9.15 -prometheus-client==0.17.1 From ae4f806bac533aa5b57ab4993e6bfdb4769c6491 Mon Sep 17 00:00:00 2001 From: Zedifus Date: Sun, 21 Apr 2024 17:40:26 +0100 Subject: [PATCH 02/26] Instantiate Jinja environment and create renderer in base webhook --- app/classes/web/webhooks/base_webhook.py | 25 +++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/app/classes/web/webhooks/base_webhook.py b/app/classes/web/webhooks/base_webhook.py index 75e485fc..4fe0144f 100644 --- a/app/classes/web/webhooks/base_webhook.py +++ b/app/classes/web/webhooks/base_webhook.py @@ -1,6 +1,7 @@ from abc import ABC, abstractmethod import logging import requests +from jinja2 import Environment, BaseLoader from app.classes.shared.helpers import Helpers @@ -16,6 +17,9 @@ class WebhookProvider(ABC): ensuring that each provider will have a send method. """ + def __init__(self): + self.jinja_env = Environment(loader=BaseLoader()) + WEBHOOK_USERNAME = "Crafty Webhooks" WEBHOOK_PFP_URL = ( "https://gitlab.com/crafty-controller/crafty-4/-" @@ -34,6 +38,25 @@ class WebhookProvider(ABC): logger.error(error) raise RuntimeError(f"Failed to dispatch notification: {error}") from error + def render_template(self, template_str, context): + """ + Renders a Jinja2 template with the provided context. + + Args: + template_str (str): The Jinja2 template string. + context (dict): A dictionary containing all the variables needed for + rendering the template. + + Returns: + str: The rendered message. + """ + try: + template = self.jinja_env.from_string(template_str) + return template.render(context) + except Exception as error: + logger.error(f"Error rendering Jinja2 template: {error}") + raise + @abstractmethod - def send(self, server_name, title, url, message, **kwargs): + def send(self, server_name, title, url, message_template, event_data, **kwargs): """Abstract method that derived classes will implement for sending webhooks.""" From 915245bbff006ee548cc7b33a4a0e3297bcda6bb Mon Sep 17 00:00:00 2001 From: Zedifus Date: Sun, 21 Apr 2024 17:53:27 +0100 Subject: [PATCH 03/26] Refactor get_monitored_events to dict to incl supported variables Update panel_handler to account for type change to get_monitored_events --- app/classes/web/panel_handler.py | 8 ++-- app/classes/web/webhooks/webhook_factory.py | 42 ++++++++++++--------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index 716671c8..0db13ecb 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -731,7 +731,9 @@ class PanelHandler(BaseHandler): server_id, model=True ) ) - page_data["triggers"] = WebhookFactory.get_monitored_events() + page_data["triggers"] = list( + WebhookFactory.get_monitored_events().keys() + ) def get_banned_players_html(): banned_players = self.controller.servers.get_banned_players(server_id) @@ -1041,7 +1043,7 @@ class PanelHandler(BaseHandler): page_data["webhook"]["enabled"] = True page_data["providers"] = WebhookFactory.get_supported_providers() - page_data["triggers"] = WebhookFactory.get_monitored_events() + page_data["triggers"] = list(WebhookFactory.get_monitored_events().keys()) if not EnumPermissionsServer.CONFIG in page_data["user_permissions"]: if not superuser: @@ -1092,7 +1094,7 @@ class PanelHandler(BaseHandler): ).split(",") page_data["providers"] = WebhookFactory.get_supported_providers() - page_data["triggers"] = WebhookFactory.get_monitored_events() + page_data["triggers"] = list(WebhookFactory.get_monitored_events().keys()) if not EnumPermissionsServer.CONFIG in page_data["user_permissions"]: if not superuser: diff --git a/app/classes/web/webhooks/webhook_factory.py b/app/classes/web/webhooks/webhook_factory.py index 9fe2c752..c0fbbede 100644 --- a/app/classes/web/webhooks/webhook_factory.py +++ b/app/classes/web/webhooks/webhook_factory.py @@ -13,7 +13,7 @@ class WebhookFactory: to manage the available providers. Attributes: - - _registry (dict): A dictionary mapping provider names to their classes. + - _registry (dict): A dictionary mapping provider names to their classes. """ _registry = { @@ -32,18 +32,18 @@ class WebhookFactory: provided arguments. If the provider is not recognized, a ValueError is raised. Arguments: - - provider_name (str): The name of the desired webhook provider. + - provider_name (str): The name of the desired webhook provider. Additional arguments supported that we may use for if a provider requires initialization: - - *args: Positional arguments to pass to the provider's constructor. - - **kwargs: Keyword arguments to pass to the provider's constructor. + - *args: Positional arguments to pass to the provider's constructor. + - **kwargs: Keyword arguments to pass to the provider's constructor. Returns: - WebhookProvider: An instance of the desired webhook provider. + WebhookProvider: An instance of the desired webhook provider. Raises: - ValueError: If the specified provider name is not recognized. + ValueError: If the specified provider name is not recognized. """ if provider_name not in cls._registry: raise ValueError(f"Provider {provider_name} is not supported.") @@ -58,7 +58,7 @@ class WebhookFactory: currently registered in the factory's registry. Returns: - List[str]: A list of supported provider names. + List[str]: A list of supported provider names. """ return list(cls._registry.keys()) @@ -68,17 +68,23 @@ class WebhookFactory: Retrieves the list of supported events for monitoring. This method provides a list of common server events that the webhook system can - monitor and notify about. + monitor and notify about. Along with the available `event_data` vars for use + on the frontend. Returns: - List[str]: A list of supported monitored actions. + dict: A dictionary where each key is an event name and the value is a + dictionary containing a list of `variables` for that event. + These variables are intended for use in the frontend to show whats + available. """ - return [ - "start_server", - "stop_server", - "crash_detected", - "backup_server", - "jar_update", - "send_command", - "kill", - ] + return { + "start_server": {"variables": ["server_name", "user", "timestamp"]}, + "stop_server": {"variables": ["server_name", "user", "timestamp"]}, + "crash_detected": {"variables": ["server_name", "user", "timestamp"]}, + "backup_server": {"variables": ["server_name", "user", "timestamp"]}, + "jar_update": {"variables": ["server_name", "user", "timestamp"]}, + "send_command": { + "variables": ["server_name", "user", "command", "timestamp"] + }, + "kill": {"variables": ["server_name", "user", "timestamp"]}, + } From d5ae2c606b925e1d49c52c050677e8b6e3d97e2a Mon Sep 17 00:00:00 2001 From: Zedifus Date: Sun, 21 Apr 2024 18:04:32 +0100 Subject: [PATCH 04/26] Implement Jinja into callback function and discord provider Will add to the other providers later after testing --- app/classes/shared/server.py | 29 ++++++++++++++++----- app/classes/web/webhooks/discord_webhook.py | 3 ++- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py index a31cc891..313f64a1 100644 --- a/app/classes/shared/server.py +++ b/app/classes/shared/server.py @@ -56,29 +56,46 @@ def callback(called_func): try: res = called_func(*args, **kwargs) finally: - events = WebhookFactory.get_monitored_events() - if called_func.__name__ in events: + event_type = called_func.__name__ + + # For send_command, Retrieve command from args or kwargs + # TODO Test Properly + command = args[1] if len(args) > 1 else kwargs.get("command", "") + + if event_type in WebhookFactory.get_monitored_events(): server_webhooks = HelpersWebhooks.get_webhooks_by_server( args[0].server_id, True ) for swebhook in server_webhooks: - if called_func.__name__ in str(swebhook.trigger).split(","): + if event_type in str(swebhook.trigger).split(","): logger.info( - f"Found callback for event {called_func.__name__}" + f"Found callback for event {event_type}" f" for server {args[0].server_id}" ) webhook = HelpersWebhooks.get_webhook_by_id(swebhook.id) webhook_provider = WebhookFactory.create_provider( webhook["webhook_type"] ) + + event_data = { + "server_name": args[0].name, + "server_id": args[0].server_id, + "user": "", + "user_id" "command": command, + "timestamp": datetime.datetime.utcnow().strftime( + "%Y-%m-%d %H:%M:%S" + ), + } + if res is not False and swebhook.enabled: webhook_provider.send( - bot_name=webhook["bot_name"], server_name=args[0].name, title=webhook["name"], url=webhook["url"], - message=webhook["body"], + message_template=webhook["body"], + event_data=event_data, color=webhook["color"], + bot_name=webhook["bot_name"], ) return res diff --git a/app/classes/web/webhooks/discord_webhook.py b/app/classes/web/webhooks/discord_webhook.py index eebe38aa..607e9bc3 100644 --- a/app/classes/web/webhooks/discord_webhook.py +++ b/app/classes/web/webhooks/discord_webhook.py @@ -51,7 +51,7 @@ class DiscordWebhook(WebhookProvider): return payload, headers - def send(self, server_name, title, url, message, **kwargs): + def send(self, server_name, title, url, message_template, event_data, **kwargs): """ Sends a Discord webhook notification using the given details. @@ -74,6 +74,7 @@ class DiscordWebhook(WebhookProvider): Raises: Exception: If there's an error in dispatching the webhook. """ + message = self.render_template(message_template, event_data) color = kwargs.get("color", "#005cd1") # Default to a color if not provided. bot_name = kwargs.get("bot_name", self.WEBHOOK_USERNAME) payload, headers = self._construct_discord_payload( From 5774744e495e1aacf55fe58ca0c8ba28faffe9ca Mon Sep 17 00:00:00 2001 From: Zedifus Date: Thu, 9 May 2024 21:43:25 +0100 Subject: [PATCH 05/26] Fix temporary event_data object Or dictionary, whatever you want to call it ;) --- app/classes/shared/server.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py index 313f64a1..9bedd134 100644 --- a/app/classes/shared/server.py +++ b/app/classes/shared/server.py @@ -81,7 +81,8 @@ def callback(called_func): "server_name": args[0].name, "server_id": args[0].server_id, "user": "", - "user_id" "command": command, + "user_id": "", + "command": command, "timestamp": datetime.datetime.utcnow().strftime( "%Y-%m-%d %H:%M:%S" ), From 08f7218a640137206c88de4d21f9a0cd75dcca70 Mon Sep 17 00:00:00 2001 From: Zedifus Date: Thu, 9 May 2024 21:59:29 +0100 Subject: [PATCH 06/26] Bump Jinja2 to resolve CVE-2024-34064 https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-34064 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ae7b7891..7470c519 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ cached_property==1.5.2 colorama==0.4.6 croniter==1.4.1 cryptography==42.0.4 -jinja2==3.1.3 +jinja2==3.1.4 jsonschema==4.19.1 libgravatar==1.0.4 nh3==0.2.14 From 92bc8971e168a25ff5a600013b213b63d7d849b2 Mon Sep 17 00:00:00 2001 From: Zedifus Date: Sun, 16 Nov 2025 16:58:44 +0000 Subject: [PATCH 07/26] Replicate jinja2 rendering over all providers --- app/classes/web/webhooks/mattermost_webhook.py | 6 ++++-- app/classes/web/webhooks/slack_webhook.py | 6 ++++-- app/classes/web/webhooks/teams_adaptive_webhook.py | 9 +++++---- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/app/classes/web/webhooks/mattermost_webhook.py b/app/classes/web/webhooks/mattermost_webhook.py index 3dc97c05..8fb6e796 100644 --- a/app/classes/web/webhooks/mattermost_webhook.py +++ b/app/classes/web/webhooks/mattermost_webhook.py @@ -41,7 +41,7 @@ class MattermostWebhook(WebhookProvider): return payload, headers - def send(self, server_name, title, url, message, **kwargs): + def send(self, server_name, title, url, message_template, event_data, **kwargs): """ Sends a Mattermost webhook notification using the given details. @@ -52,7 +52,8 @@ class MattermostWebhook(WebhookProvider): server_name (str): The name of the server triggering the notification. title (str): The title for the notification message. url (str): The webhook URL to send the notification to. - message (str): The main content or body of the notification message. + message_template (str): The Jinja2 template for the message body. + event_data (dict): A dictionary containing variables for template rendering. bot_name (str): Override for the Webhook's name set on creation, see note! Returns: @@ -67,6 +68,7 @@ class MattermostWebhook(WebhookProvider): - Mattermost's `config.json` setting is `"EnablePostUsernameOverride": true` - Mattermost's `config.json` setting is `"EnablePostIconOverride": true` """ + message = self.render_template(message_template, event_data) bot_name = kwargs.get("bot_name", self.WEBHOOK_USERNAME) payload, headers = self._construct_mattermost_payload( server_name, title, message, bot_name diff --git a/app/classes/web/webhooks/slack_webhook.py b/app/classes/web/webhooks/slack_webhook.py index cd7c71bf..960a25e1 100644 --- a/app/classes/web/webhooks/slack_webhook.py +++ b/app/classes/web/webhooks/slack_webhook.py @@ -67,7 +67,7 @@ class SlackWebhook(WebhookProvider): return payload, headers - def send(self, server_name, title, url, message, **kwargs): + def send(self, server_name, title, url, message_template, event_data, **kwargs): """ Sends a Slack webhook notification using the given details. @@ -78,7 +78,8 @@ class SlackWebhook(WebhookProvider): server_name (str): The name of the server triggering the notification. title (str): The title for the notification message. url (str): The webhook URL to send the notification to. - message (str): The main content or body of the notification message. + message_template (str): The Jinja2 template for the message body. + event_data (dict): A dictionary containing variables for template rendering. color (str, optional): The color code for the blocks's colour accent. Defaults to a pretty blue if not provided. bot_name (str): Override for the Webhook's name set on creation, (not working). @@ -90,6 +91,7 @@ class SlackWebhook(WebhookProvider): Raises: Exception: If there's an error in dispatching the webhook. """ + message = self.render_template(message_template, event_data) color = kwargs.get("color", "#005cd1") # Default to a color if not provided. bot_name = kwargs.get("bot_name", self.WEBHOOK_USERNAME) payload, headers = self._construct_slack_payload( diff --git a/app/classes/web/webhooks/teams_adaptive_webhook.py b/app/classes/web/webhooks/teams_adaptive_webhook.py index 6342de65..37a4dc04 100644 --- a/app/classes/web/webhooks/teams_adaptive_webhook.py +++ b/app/classes/web/webhooks/teams_adaptive_webhook.py @@ -101,19 +101,19 @@ class TeamsWebhook(WebhookProvider): return payload, headers - def send(self, server_name, title, url, message, **kwargs): + def send(self, server_name, title, url, message_template, event_data, **kwargs): """ Sends a Teams Adaptive card notification using the given details. The method constructs and dispatches a payload suitable for - Discords's webhook system. + Teams's webhook system. Parameters: server_name (str): The name of the server triggering the notification. title (str): The title for the notification message. url (str): The webhook URL to send the notification to. - message (str): The main content or body of the notification message. - Defaults to a pretty blue if not provided. + message_template (str): The Jinja2 template for the message body. + event_data (dict): A dictionary containing variables for template rendering. Returns: str: "Dispatch successful!" if the message is sent successfully, otherwise an @@ -122,5 +122,6 @@ class TeamsWebhook(WebhookProvider): Raises: Exception: If there's an error in dispatching the webhook. """ + message = self.render_template(message_template, event_data) payload, headers = self._construct_teams_payload(server_name, title, message) return self._send_request(url, payload, headers) From 20c04a824b64a3d67372076d5f2b08a74267c224 Mon Sep 17 00:00:00 2001 From: Zedifus Date: Sun, 16 Nov 2025 18:05:28 +0000 Subject: [PATCH 08/26] Add formatted to time variables to available event_data --- app/classes/shared/server.py | 3 +++ app/classes/web/webhooks/base_webhook.py | 32 ++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py index 2a6d8219..e3c532b5 100644 --- a/app/classes/shared/server.py +++ b/app/classes/shared/server.py @@ -88,6 +88,9 @@ def callback(called_func): ), } + # Add time variables to event_data + event_data = webhook_provider.add_time_variables(event_data) + if res is not False and swebhook.enabled: webhook_provider.send( server_name=args[0].name, diff --git a/app/classes/web/webhooks/base_webhook.py b/app/classes/web/webhooks/base_webhook.py index afd42292..24393a4c 100644 --- a/app/classes/web/webhooks/base_webhook.py +++ b/app/classes/web/webhooks/base_webhook.py @@ -1,6 +1,8 @@ from abc import ABC, abstractmethod import logging import requests +import datetime +import time from jinja2 import Environment, BaseLoader from app.classes.helpers.helpers import Helpers @@ -57,6 +59,36 @@ class WebhookProvider(ABC): logger.error(f"Error rendering Jinja2 template: {error}") raise + def add_time_variables(self, event_data): + """ + Adds various time format variables to the event_data dictionary. + + Adds the following time-related variables to event_data: + - time_iso: ISO 8601 formatted datetime (UTC) + - time_unix: UNIX timestamp (seconds since epoch) + - time_day: Day of month (1-31) + - time_month: Month (1-12) + - time_year: Full year (e.g., 2025) + - time_formatted: Human-readable format (YYYY-MM-DD HH:MM:SS UTC) + + Args: + event_data (dict): A dictionary containing event information. + + Returns: + dict: The event_data dictionary with time variables added. + """ + now_utc = datetime.datetime.now(datetime.timezone.utc) + unix_timestamp = int(time.time()) + + event_data["time_iso"] = now_utc.isoformat().replace("+00:00", "Z") + event_data["time_unix"] = unix_timestamp + event_data["time_day"] = now_utc.day + event_data["time_month"] = now_utc.month + event_data["time_year"] = now_utc.year + event_data["time_formatted"] = now_utc.strftime("%Y-%m-%d %H:%M:%S UTC") + + return event_data + @abstractmethod def send(self, server_name, title, url, message_template, event_data, **kwargs): """Abstract method that derived classes will implement for sending webhooks.""" From d137cbf4ef66d0ededcd2f45927e5f75a4c5dfec Mon Sep 17 00:00:00 2001 From: Zedifus Date: Sun, 16 Nov 2025 18:30:14 +0000 Subject: [PATCH 09/26] Remove legacy timestamp and tidy up supported monitored events List --- app/classes/shared/server.py | 3 --- app/classes/web/webhooks/webhook_factory.py | 21 ++++++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py index e3c532b5..d790c9d8 100644 --- a/app/classes/shared/server.py +++ b/app/classes/shared/server.py @@ -83,9 +83,6 @@ def callback(called_func): "user": "", "user_id": "", "command": command, - "timestamp": datetime.datetime.utcnow().strftime( - "%Y-%m-%d %H:%M:%S" - ), } # Add time variables to event_data diff --git a/app/classes/web/webhooks/webhook_factory.py b/app/classes/web/webhooks/webhook_factory.py index c0fbbede..12299053 100644 --- a/app/classes/web/webhooks/webhook_factory.py +++ b/app/classes/web/webhooks/webhook_factory.py @@ -77,14 +77,17 @@ class WebhookFactory: These variables are intended for use in the frontend to show whats available. """ + # Common variables for all events + common_vars = [ + "server_name", "server_id", "user", "user_id", + "time_iso", "time_unix", "time_day", "time_month", "time_year", "time_formatted" + ] return { - "start_server": {"variables": ["server_name", "user", "timestamp"]}, - "stop_server": {"variables": ["server_name", "user", "timestamp"]}, - "crash_detected": {"variables": ["server_name", "user", "timestamp"]}, - "backup_server": {"variables": ["server_name", "user", "timestamp"]}, - "jar_update": {"variables": ["server_name", "user", "timestamp"]}, - "send_command": { - "variables": ["server_name", "user", "command", "timestamp"] - }, - "kill": {"variables": ["server_name", "user", "timestamp"]}, + "start_server": {"variables": common_vars}, + "stop_server": {"variables": common_vars}, + "crash_detected": {"variables": common_vars}, + "backup_server": {"variables": common_vars + ["file_name", "download_link", "file_size"]}, + "jar_update": {"variables": common_vars}, + "send_command": {"variables": common_vars + ["command"]}, + "kill": {"variables": common_vars + ["reason"]}, } From 2f7b340a4cbf946ac50dc9e3b2eee45e46dc5135 Mon Sep 17 00:00:00 2001 From: Zedifus Date: Sun, 16 Nov 2025 19:23:40 +0000 Subject: [PATCH 10/26] Add `event_type` to available `event_data` --- app/classes/shared/server.py | 1 + app/classes/web/webhooks/webhook_factory.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py index d790c9d8..f6f38608 100644 --- a/app/classes/shared/server.py +++ b/app/classes/shared/server.py @@ -83,6 +83,7 @@ def callback(called_func): "user": "", "user_id": "", "command": command, + "event_type": event_type, } # Add time variables to event_data diff --git a/app/classes/web/webhooks/webhook_factory.py b/app/classes/web/webhooks/webhook_factory.py index 12299053..e734632b 100644 --- a/app/classes/web/webhooks/webhook_factory.py +++ b/app/classes/web/webhooks/webhook_factory.py @@ -79,7 +79,7 @@ class WebhookFactory: """ # Common variables for all events common_vars = [ - "server_name", "server_id", "user", "user_id", + "server_name", "server_id", "user", "user_id", "event_type", "time_iso", "time_unix", "time_day", "time_month", "time_year", "time_formatted" ] return { From 931f42ec376b6125eb94a069902db3b6cf84de48 Mon Sep 17 00:00:00 2001 From: Zedifus Date: Sun, 16 Nov 2025 19:41:31 +0000 Subject: [PATCH 11/26] Add baseline for sources to available `event_data` --- app/classes/shared/server.py | 11 +++++++++-- app/classes/web/webhooks/webhook_factory.py | 3 ++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py index f6f38608..e648d66b 100644 --- a/app/classes/shared/server.py +++ b/app/classes/shared/server.py @@ -77,13 +77,20 @@ def callback(called_func): webhook["webhook_type"] ) + + # Extract source context from kwargs if present + source_type = kwargs.get("source_type", "unknown") + source_id = kwargs.get("source_id", "") + source_name = kwargs.get("source_name", "") + event_data = { "server_name": args[0].name, "server_id": args[0].server_id, - "user": "", - "user_id": "", "command": command, "event_type": event_type, + "source_type": source_type, + "source_id": source_id, + "source_name": source_name, } # Add time variables to event_data diff --git a/app/classes/web/webhooks/webhook_factory.py b/app/classes/web/webhooks/webhook_factory.py index e734632b..95c97041 100644 --- a/app/classes/web/webhooks/webhook_factory.py +++ b/app/classes/web/webhooks/webhook_factory.py @@ -79,7 +79,8 @@ class WebhookFactory: """ # Common variables for all events common_vars = [ - "server_name", "server_id", "user", "user_id", "event_type", + "server_name", "server_id", "event_type", + "source_type", "source_id", "source_name", "time_iso", "time_unix", "time_day", "time_month", "time_year", "time_formatted" ] return { From 1333ba04c0938c34976de26cbfcb090b35717276 Mon Sep 17 00:00:00 2001 From: Zedifus Date: Wed, 19 Nov 2025 20:05:19 +0000 Subject: [PATCH 12/26] Fix black formatting --- app/classes/shared/server.py | 1 - app/classes/web/webhooks/webhook_factory.py | 19 +++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py index e648d66b..3e6490e9 100644 --- a/app/classes/shared/server.py +++ b/app/classes/shared/server.py @@ -77,7 +77,6 @@ def callback(called_func): webhook["webhook_type"] ) - # Extract source context from kwargs if present source_type = kwargs.get("source_type", "unknown") source_id = kwargs.get("source_id", "") diff --git a/app/classes/web/webhooks/webhook_factory.py b/app/classes/web/webhooks/webhook_factory.py index 95c97041..6358de32 100644 --- a/app/classes/web/webhooks/webhook_factory.py +++ b/app/classes/web/webhooks/webhook_factory.py @@ -79,15 +79,26 @@ class WebhookFactory: """ # Common variables for all events common_vars = [ - "server_name", "server_id", "event_type", - "source_type", "source_id", "source_name", - "time_iso", "time_unix", "time_day", "time_month", "time_year", "time_formatted" + "server_name", + "server_id", + "event_type", + "source_type", + "source_id", + "source_name", + "time_iso", + "time_unix", + "time_day", + "time_month", + "time_year", + "time_formatted", ] return { "start_server": {"variables": common_vars}, "stop_server": {"variables": common_vars}, "crash_detected": {"variables": common_vars}, - "backup_server": {"variables": common_vars + ["file_name", "download_link", "file_size"]}, + "backup_server": { + "variables": common_vars + ["file_name", "download_link", "file_size"] + }, "jar_update": {"variables": common_vars}, "send_command": {"variables": common_vars + ["command"]}, "kill": {"variables": common_vars + ["reason"]}, From 167ab4555f56e211953094bdda3d5ba9e8794ae5 Mon Sep 17 00:00:00 2001 From: Zedifus Date: Wed, 19 Nov 2025 20:48:20 +0000 Subject: [PATCH 13/26] Normalise backup related jinja var names --- app/classes/web/webhooks/webhook_factory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/classes/web/webhooks/webhook_factory.py b/app/classes/web/webhooks/webhook_factory.py index 6358de32..61358988 100644 --- a/app/classes/web/webhooks/webhook_factory.py +++ b/app/classes/web/webhooks/webhook_factory.py @@ -97,7 +97,7 @@ class WebhookFactory: "stop_server": {"variables": common_vars}, "crash_detected": {"variables": common_vars}, "backup_server": { - "variables": common_vars + ["file_name", "download_link", "file_size"] + "variables": common_vars + ["backup_name", "backup_link", "backup_size"] }, "jar_update": {"variables": common_vars}, "send_command": {"variables": common_vars + ["command"]}, From 7ab8159fda199cadaac227d258003aa01dcc85fa Mon Sep 17 00:00:00 2001 From: = Date: Wed, 19 Nov 2025 17:34:47 -0500 Subject: [PATCH 14/26] Return backup status, size, etc for webhooks --- app/classes/shared/backup_mgr.py | 48 ++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/app/classes/shared/backup_mgr.py b/app/classes/shared/backup_mgr.py index 876d7111..652e7ba9 100644 --- a/app/classes/shared/backup_mgr.py +++ b/app/classes/shared/backup_mgr.py @@ -63,7 +63,7 @@ class BackupManager: self.file_helper.del_file(os.path.join(server_path, item)) self.file_helper.restore_archive(backup_location, server_path) - def backup_starter(self, backup_config, server): + def backup_starter(self, backup_config, server) -> tuple: """Notify users of backup starting, and start the backup. Args: @@ -83,14 +83,35 @@ class BackupManager: ).format(server.name), ) time.sleep(3) - + size = False # Start the backup if backup_config.get("backup_type", "zip_vault") == "zip_vault": - self.zip_vault(backup_config, server) + backup_file_name = self.zip_vault(backup_config, server) + if backup_file_name: + if Path(backup_file_name).suffix != ".zip": + backup_file_name += ".zip" + size = ( + Path( + backup_config["backup_location"], + backup_config["backup_id"], + backup_file_name, + ) + .stat() + .st_size + ) else: - self.snapshot_backup(backup_config, server) + backup_file_name = self.snapshot_backup(backup_config, server) + if backup_file_name: + if Path(backup_file_name).suffix != ".manifest": + backup_file_name += ".manifest" + if backup_file_name: + return ( + backup_file_name, + size, + ) + return (False, "error") - def zip_vault(self, backup_config, server): + def zip_vault(self, backup_config, server) -> str | bool: # Adjust the location to include the backup ID for destination. backup_location = os.path.join( @@ -100,7 +121,7 @@ class BackupManager: # Check if the backup location even exists. if not backup_location: Console.critical("No backup path found. Canceling") - return None + return False self.helper.ensure_dir_exists(backup_location) @@ -164,8 +185,10 @@ class BackupManager: {"status": json.dumps({"status": "Standby", "message": ""})}, ) time.sleep(5) + return Path(backup_filename).name except Exception as e: self.fail_backup(e, backup_config, server) + return False @staticmethod def fail_backup(why: Exception, backup_config: dict, server) -> None: @@ -266,7 +289,7 @@ class BackupManager: logger.info(f"Removing old backup '{oldfile['path']}'") os.remove(Helpers.get_os_understandable_path(oldfile_path)) - def snapshot_backup(self, backup_config, server) -> None: + def snapshot_backup(self, backup_config, server) -> str | bool: """ Creates snapshot style backup of server. No file will be saved more than once over all backups. Designed to enable encryption of files and s3 compatability in @@ -308,7 +331,7 @@ class BackupManager: manifest_file: io.TextIOWrapper = backup_manifest_path.open("w+") except OSError as why: self.fail_backup(why, backup_config, server) - return + return False # Write manifest file version. manifest_file.write("00\n") @@ -328,7 +351,7 @@ class BackupManager: manifest_file.close() backup_manifest_path.unlink(missing_ok=True) self.fail_backup(why, backup_config, server) - return + return False # Write saved file into manifest. manifest_file.write( @@ -342,6 +365,13 @@ class BackupManager: backup_config["max_backups"], backup_repository_path ) + HelpersManagement.update_backup_config( + backup_config["backup_id"], + {"status": json.dumps({"status": "Standby", "message": ""})}, + ) + + return Path(backup_manifest_path).name + def snapshot_restore( self, backup_config: {str}, backup_manifest_filename: str, server ) -> None: From 662248a587ea8e98c3201621a5d4b1500688545e Mon Sep 17 00:00:00 2001 From: = Date: Wed, 19 Nov 2025 17:35:16 -0500 Subject: [PATCH 15/26] Set baseurl for Crafty --- app/classes/helpers/helpers.py | 2 ++ app/classes/web/routes/api/crafty/config/index.py | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/app/classes/helpers/helpers.py b/app/classes/helpers/helpers.py index 78ef3a3a..e90e8468 100644 --- a/app/classes/helpers/helpers.py +++ b/app/classes/helpers/helpers.py @@ -69,6 +69,7 @@ MASTER_CONFIG = { "max_login_attempts": 3, "superMFA": False, "general_user_log_access": False, + "base_url": "127.0.0.1:8443", } CONFIG_CATEGORIES = { @@ -80,6 +81,7 @@ CONFIG_CATEGORIES = { "disabled_language_files", "big_bucket_repo", "enable_user_self_delete", + "base_url", ], "security": [ "allow_nsfw_profile_pictures", diff --git a/app/classes/web/routes/api/crafty/config/index.py b/app/classes/web/routes/api/crafty/config/index.py index d6ed25cb..c82e8998 100644 --- a/app/classes/web/routes/api/crafty/config/index.py +++ b/app/classes/web/routes/api/crafty/config/index.py @@ -119,6 +119,13 @@ config_json_schema = { "error": "typeBool", "fill": True, }, + "base_url": { + "type": "string", + "pattern": ( + r"^(?:(?:\d{1,3}\.){3}\d{1,3}" + r"|(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,})(?::\d{1,5})?$" + ), + }, "max_login_attempts": {"type": "integer", "error": "typeInt", "fill": True}, "superMFA": {"type": "boolean", "error": "typeBool", "fill": True}, "general_user_log_access": { From 878e315ac6ebdf18f5f7c015370dae4fb024da44 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 19 Nov 2025 17:35:32 -0500 Subject: [PATCH 16/26] Add backup attributes to webhooks --- app/classes/shared/server.py | 84 +++++++++++++++++++++++++++++++++--- 1 file changed, 79 insertions(+), 5 deletions(-) diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py index 3e6490e9..cbdfcf16 100644 --- a/app/classes/shared/server.py +++ b/app/classes/shared/server.py @@ -54,7 +54,7 @@ def callback(called_func): res = None logger.debug("Checking for callbacks") try: - res = called_func(*args, **kwargs) + res = called_func(*args, **kwargs) # Calls and runs the function finally: event_type = called_func.__name__ @@ -81,6 +81,18 @@ def callback(called_func): source_type = kwargs.get("source_type", "unknown") source_id = kwargs.get("source_id", "") source_name = kwargs.get("source_name", "") + backup_name = "" + backup_size = "" + backup_link = "" + backup_status = "" + backup_error = "" + + if isinstance(res, dict): + backup_name = res.get("backup_name") + backup_size = str(res.get("backup_size")) + backup_link = res.get("backup_link") + backup_status = res.get("backup_status") + backup_error = res.get("backup_error") event_data = { "server_name": args[0].name, @@ -90,6 +102,11 @@ def callback(called_func): "source_type": source_type, "source_id": source_id, "source_name": source_name, + "backup_name": backup_name, + "backup_size": backup_size, + "backup_link": backup_link, + "backup_status": backup_status, + "backup_error": backup_error, } # Add time variables to event_data @@ -1230,7 +1247,7 @@ class ServerInstance: logger.info(f"Backup Thread started for server {self.settings['server_name']}.") @callback - def backup_server(self, backup_id): + def backup_server(self, backup_id) -> dict | bool: logger.info(f"Starting server {self.name} (ID {self.server_id}) backup") server_users = PermissionsServers.get_server_user_list(self.server_id) # Alert the start of the backup to the authorized users. @@ -1246,7 +1263,15 @@ class ServerInstance: # Get the backup config if not backup_id: - return logger.error("No backup ID provided. Exiting backup") + logger.error("No backup ID provided. Exiting backup") + last_failed = self.last_backup_status() + if last_failed: + last_backup_status = "❌" + reason = "No backup ID provided" + return { + "backup_status": last_backup_status, + "backup_error": reason, + } conf = HelpersManagement.get_backup_config(backup_id) # Adjust the location to include the backup ID for destination. backup_location = os.path.join(conf["backup_location"], conf["backup_id"]) @@ -1254,7 +1279,16 @@ class ServerInstance: # Check if the backup location even exists. if not backup_location: Console.critical("No backup path found. Canceling") - return None + backup_status = json.loads( + HelpersManagement.get_backup_config(backup_id)["status"] + ) + if backup_status["status"] == "Failed": + last_backup_status = "❌" + reason = backup_status["message"] + return { + "backup_status": last_backup_status, + "backup_error": reason, + } if conf["before"]: logger.debug( "Found running server and send command option. Sending command" @@ -1262,7 +1296,7 @@ class ServerInstance: self.send_command(conf["before"]) # Pause to let command run time.sleep(5) - self.backup_mgr.backup_starter(conf, self) + backup_name, backup_size = self.backup_mgr.backup_starter(conf, self) if conf["after"]: self.send_command(conf["after"]) if conf["shutdown"] and self.was_running: @@ -1272,6 +1306,46 @@ class ServerInstance: self.run_threaded_server(HelperUsers.get_user_id_by_name("system")) self.set_backup_status() + # Return data for webhooks callback + base_url = f"{self.helper.get_setting('base_url')}" + size = backup_size + backup_status = json.loads( + HelpersManagement.get_backup_config(backup_id)["status"] + ) + reason = backup_status["message"] + if not backup_name: + return { + "backup_status": "❌", + "backup_error": reason, + } + if backup_size: + size = self.helper.human_readable_file_size(backup_size) + url = ( + f"https://{base_url}/api/v2/servers/{self.server_id}" + f"/backups/backup/{backup_id}/download/{html.escape(backup_name)}" + ) + if conf["backup_type"] == "snapshot": + size = 0 + url = ( + f"https://{base_url}/panel/edit_backup?" + f"id={self.server_id}&backup_id={backup_id}" + ) + backup_status = json.loads( + HelpersManagement.get_backup_config(backup_id)["status"] + ) + last_backup_status = "✅" + reason = "" + if backup_status["status"] == "Failed": + last_backup_status = "❌" + reason = backup_status["message"] + return { + "backup_name": backup_name, + "backup_size": size, + "backup_link": url, + "backup_status": last_backup_status, + "backup_error": reason, + } + def set_backup_status(self): backups = HelpersManagement.get_backups_by_server(self.server_id, True) alert = False From c0fbd60cdf781dcabf289f72d1f8548808ac8bfb Mon Sep 17 00:00:00 2001 From: = Date: Wed, 19 Nov 2025 17:35:44 -0500 Subject: [PATCH 17/26] Add backup error and backup status to webhooks --- app/classes/web/webhooks/webhook_factory.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/classes/web/webhooks/webhook_factory.py b/app/classes/web/webhooks/webhook_factory.py index 61358988..6e873323 100644 --- a/app/classes/web/webhooks/webhook_factory.py +++ b/app/classes/web/webhooks/webhook_factory.py @@ -97,7 +97,14 @@ class WebhookFactory: "stop_server": {"variables": common_vars}, "crash_detected": {"variables": common_vars}, "backup_server": { - "variables": common_vars + ["backup_name", "backup_link", "backup_size"] + "variables": common_vars + + [ + "backup_name", + "backup_link", + "backup_size", + "backup_status", + "backup_error", + ] }, "jar_update": {"variables": common_vars}, "send_command": {"variables": common_vars + ["command"]}, From 751afc1b2008247110435011121deabf3ccaf4aa Mon Sep 17 00:00:00 2001 From: Zedifus Date: Sat, 22 Nov 2025 17:38:17 +0000 Subject: [PATCH 18/26] Fix C0411 --- app/classes/web/webhooks/base_webhook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/classes/web/webhooks/base_webhook.py b/app/classes/web/webhooks/base_webhook.py index 24393a4c..7cd11cc1 100644 --- a/app/classes/web/webhooks/base_webhook.py +++ b/app/classes/web/webhooks/base_webhook.py @@ -1,8 +1,8 @@ from abc import ABC, abstractmethod import logging -import requests import datetime import time +import requests from jinja2 import Environment, BaseLoader from app.classes.helpers.helpers import Helpers From b9c88c0db4d74510d5dea9b720f418d8a62c1e76 Mon Sep 17 00:00:00 2001 From: Zedifus Date: Sat, 22 Nov 2025 17:48:16 +0000 Subject: [PATCH 19/26] Update pylint analysis version to min supported (3.10) Tweak readme compose example --- .pylintrc | 2 +- README.md | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.pylintrc b/.pylintrc index e4e3f119..c0aae8e6 100644 --- a/.pylintrc +++ b/.pylintrc @@ -52,7 +52,7 @@ persistent=yes # Minimum Python version to use for version dependent checks. Will default to # the version used to run pylint. -py-version=3.9 +py-version=3.10 # When enabled, pylint would attempt to guess common misconfiguration and emit # user-friendly hints instead of false-positive error messages. diff --git a/README.md b/README.md index 636fa6df..1e47b9ee 100644 --- a/README.md +++ b/README.md @@ -66,8 +66,6 @@ The image is located at: `registry.gitlab.com/crafty-controller/crafty-4:latest` $ vim docker-compose.yml ``` ```yml -version: '3' - services: crafty: container_name: crafty_container From 823830710225b337e9c514077a44e10eedbdb4e2 Mon Sep 17 00:00:00 2001 From: Zedifus Date: Sat, 22 Nov 2025 18:04:15 +0000 Subject: [PATCH 20/26] Set autoescape on jinja_env, fixes SQ:- python:S5247 Even though the rendered string is not being dispatched to any web page/HTML/JavaScript processing, Auto escaping is probably best as we may make a provider in the future that could be affected by this. --- app/classes/web/webhooks/base_webhook.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/classes/web/webhooks/base_webhook.py b/app/classes/web/webhooks/base_webhook.py index 7cd11cc1..e031903e 100644 --- a/app/classes/web/webhooks/base_webhook.py +++ b/app/classes/web/webhooks/base_webhook.py @@ -20,7 +20,10 @@ class WebhookProvider(ABC): """ def __init__(self): - self.jinja_env = Environment(loader=BaseLoader()) + self.jinja_env = Environment( + loader=BaseLoader(), + autoescape=True, + ) WEBHOOK_USERNAME = "Crafty Webhooks" WEBHOOK_PFP_URL = ( From e2d53939abddc9bb9622fd4827654c5417dfe892 Mon Sep 17 00:00:00 2001 From: Zedifus Date: Sat, 22 Nov 2025 18:08:54 +0000 Subject: [PATCH 21/26] Remove TODO and fix erroneous import TODO has been tested and is fine, `logging.config` was causing the import to appear as unused, where `import logging` is the correct use case in this instance --- app/classes/shared/server.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py index cbdfcf16..51dac292 100644 --- a/app/classes/shared/server.py +++ b/app/classes/shared/server.py @@ -7,7 +7,7 @@ import time import datetime import base64 import threading -import logging.config +import logging import subprocess import html import glob @@ -59,7 +59,6 @@ def callback(called_func): event_type = called_func.__name__ # For send_command, Retrieve command from args or kwargs - # TODO Test Properly command = args[1] if len(args) > 1 else kwargs.get("command", "") if event_type in WebhookFactory.get_monitored_events(): From afd5c5861d3b2b5ec73baf6a0f27a51afa0d1b96 Mon Sep 17 00:00:00 2001 From: Zedifus Date: Sat, 22 Nov 2025 18:29:10 +0000 Subject: [PATCH 22/26] Make snapshot/archive suffix definitions class constants, fixes SQ- python:S1192 --- app/classes/shared/backup_mgr.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/app/classes/shared/backup_mgr.py b/app/classes/shared/backup_mgr.py index 652e7ba9..e3293875 100644 --- a/app/classes/shared/backup_mgr.py +++ b/app/classes/shared/backup_mgr.py @@ -27,6 +27,8 @@ logger = logging.getLogger(__name__) class BackupManager: SNAPSHOT_BACKUP_DATE_FORMAT_STRING = "%Y-%m-%d-%H-%M-%S" + SNAPSHOT_SUFFIX = ".manifest" + ARCHIVE_SUFFIX = ".zip" def __init__(self, helper, file_helper, management_helper): self.helper = helper @@ -70,6 +72,7 @@ class BackupManager: backup_config (_type_): _description_ server (_type_): Server object to backup """ + # Notify users of backup starting logger.info(f"Starting server {server.name} (ID {server.server_id}) backup") server_users = PermissionsServers.get_server_user_list(server.server_id) @@ -88,8 +91,8 @@ class BackupManager: if backup_config.get("backup_type", "zip_vault") == "zip_vault": backup_file_name = self.zip_vault(backup_config, server) if backup_file_name: - if Path(backup_file_name).suffix != ".zip": - backup_file_name += ".zip" + if Path(backup_file_name).suffix != self.ARCHIVE_SUFFIX: + backup_file_name += self.ARCHIVE_SUFFIX size = ( Path( backup_config["backup_location"], @@ -102,8 +105,8 @@ class BackupManager: else: backup_file_name = self.snapshot_backup(backup_config, server) if backup_file_name: - if Path(backup_file_name).suffix != ".manifest": - backup_file_name += ".manifest" + if Path(backup_file_name).suffix != self.SNAPSHOT_SUFFIX: + backup_file_name += self.SNAPSHOT_SUFFIX if backup_file_name: return ( backup_file_name, @@ -226,8 +229,7 @@ class BackupManager: {"status": json.dumps({"status": "Failed", "message": f"{why}"})}, ) - @staticmethod - def list_backups(backup_config: dict, server_id) -> list: + def list_backups(self, backup_config: dict, server_id) -> list: if not backup_config: logger.info( f"Error putting backup file list for server with ID: {server_id}" @@ -260,7 +262,7 @@ class BackupManager: "size": "", } for f in files - if f["path"].endswith(".manifest") + if f["path"].endswith(self.SNAPSHOT_SUFFIX) ] return [ { @@ -271,7 +273,7 @@ class BackupManager: "size": f["size"], } for f in files - if f["path"].endswith(".zip") + if f["path"].endswith(self.ARCHIVE_SUFFIX) ] def remove_old_backups(self, backup_config, server): From 2cd5179abe8c9780219e74c1091e8b05d451070d Mon Sep 17 00:00:00 2001 From: Zedifus Date: Sat, 22 Nov 2025 18:45:07 +0000 Subject: [PATCH 23/26] Merge combinable if statements well preserving original logic for SQ- python:S1066 --- app/classes/shared/backup_mgr.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/app/classes/shared/backup_mgr.py b/app/classes/shared/backup_mgr.py index e3293875..b3534507 100644 --- a/app/classes/shared/backup_mgr.py +++ b/app/classes/shared/backup_mgr.py @@ -90,9 +90,12 @@ class BackupManager: # Start the backup if backup_config.get("backup_type", "zip_vault") == "zip_vault": backup_file_name = self.zip_vault(backup_config, server) + if ( + backup_file_name + and Path(backup_file_name).suffix != self.ARCHIVE_SUFFIX + ): + backup_file_name += self.ARCHIVE_SUFFIX if backup_file_name: - if Path(backup_file_name).suffix != self.ARCHIVE_SUFFIX: - backup_file_name += self.ARCHIVE_SUFFIX size = ( Path( backup_config["backup_location"], @@ -104,14 +107,13 @@ class BackupManager: ) else: backup_file_name = self.snapshot_backup(backup_config, server) - if backup_file_name: - if Path(backup_file_name).suffix != self.SNAPSHOT_SUFFIX: - backup_file_name += self.SNAPSHOT_SUFFIX + if ( + backup_file_name + and Path(backup_file_name).suffix != self.SNAPSHOT_SUFFIX + ): + backup_file_name += self.SNAPSHOT_SUFFIX if backup_file_name: - return ( - backup_file_name, - size, - ) + return (backup_file_name, size) return (False, "error") def zip_vault(self, backup_config, server) -> str | bool: From 8825eb87b0a480c3d6fcf04f00eb0e30d5ae34ed Mon Sep 17 00:00:00 2001 From: Zedifus Date: Sat, 22 Nov 2025 21:04:24 +0000 Subject: [PATCH 24/26] Update changelog !757 Drop py 3.9 Bump release minor (Retag) --- CHANGELOG.md | 4 ++-- README.md | 2 +- app/config/version.json | 4 ++-- sonar-project.properties | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f0a6d6f..eb9bc75b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Changelog -## --- [4.5.6] - 2025/TBD +## --- [4.6.1] - 2025/TBD ### New features -TBD +- Jinja2 Dynamic Variables for Webhook Notifications ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/757)) ### Bug fixes - Change hour and minute intervals in APScheudler to fix incorrect triggers ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/910)) - Use asyncio locks to limit upload handler race condition ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/907)) diff --git a/README.md b/README.md index 1e47b9ee..349150b2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![Crafty Logo](app/frontend/static/assets/images/logo_long.svg)](https://craftycontrol.com) -# Crafty Controller 4.5.6 +# Crafty Controller 4.6.1 > Python based Control Panel for your Minecraft Server ## What is Crafty Controller? diff --git a/app/config/version.json b/app/config/version.json index bb1da134..4c45077b 100644 --- a/app/config/version.json +++ b/app/config/version.json @@ -1,5 +1,5 @@ { "major": 4, - "minor": 5, - "sub": 6 + "minor": 6, + "sub": 1 } diff --git a/sonar-project.properties b/sonar-project.properties index 06f48266..7c73fc67 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -3,8 +3,8 @@ sonar.organization=crafty-controller # This is the name and version displayed in the SonarCloud UI. sonar.projectName=Crafty 4 -sonar.projectVersion=4.5.6 -sonar.python.version=3.9, 3.10, 3.11 +sonar.projectVersion=4.6.1 +sonar.python.version=3.10, 3.11, 3.12 sonar.exclusions=app/migrations/**, app/frontend/static/assets/vendors/** # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. From 45af9ac26c34b401ec358d4f45e0747a5a6bbcaa Mon Sep 17 00:00:00 2001 From: Andrew Date: Sat, 22 Nov 2025 19:37:55 -0600 Subject: [PATCH 25/26] Add jinja 2 doc link to body --- .../static/assets/css/partial/crafty-webhooks.css | 9 +++++++++ app/frontend/templates/panel/server_webhook_edit.html | 10 ++++++---- app/translations/en_EN.json | 2 ++ 3 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 app/frontend/static/assets/css/partial/crafty-webhooks.css diff --git a/app/frontend/static/assets/css/partial/crafty-webhooks.css b/app/frontend/static/assets/css/partial/crafty-webhooks.css new file mode 100644 index 00000000..c21cfd7f --- /dev/null +++ b/app/frontend/static/assets/css/partial/crafty-webhooks.css @@ -0,0 +1,9 @@ +.jinja2 { + background-color: var(--card-banner-bg); + border-top-left-radius: 10px; + border-top-right-radius: 10px; + outline: 1px solid var(--outline); + padding: 10px; + margin-top: 10px; + margin-bottom: -10px; +} \ No newline at end of file diff --git a/app/frontend/templates/panel/server_webhook_edit.html b/app/frontend/templates/panel/server_webhook_edit.html index c0e351cf..a82eefcd 100644 --- a/app/frontend/templates/panel/server_webhook_edit.html +++ b/app/frontend/templates/panel/server_webhook_edit.html @@ -7,6 +7,7 @@ {% block content %} +
@@ -83,10 +84,11 @@
- - + +
diff --git a/app/translations/en_EN.json b/app/translations/en_EN.json index 6c29f479..03501feb 100644 --- a/app/translations/en_EN.json +++ b/app/translations/en_EN.json @@ -790,9 +790,11 @@ "bot_name": "Bot Name", "color": "Select Color Accent", "crash_detected": "Server Crashed", + "documentation": "Please utilize this documentation to take advantage of dynamic variables.", "edit": "Edit", "enabled": "Enabled", "jar_update": "Server Executable Updated", + "jinja2": "Crafty's webhook engine takes advantage of Jinja2 for dynamic message rendering.", "kill": "Server Killed", "name": "Name", "new": "New Webhook", From 9228b955579f832fd656dee6df72864898ac1557 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sat, 22 Nov 2025 19:42:38 -0600 Subject: [PATCH 26/26] Make translation more clear --- app/translations/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/translations/en_EN.json b/app/translations/en_EN.json index 03501feb..7cd22112 100644 --- a/app/translations/en_EN.json +++ b/app/translations/en_EN.json @@ -790,7 +790,7 @@ "bot_name": "Bot Name", "color": "Select Color Accent", "crash_detected": "Server Crashed", - "documentation": "Please utilize this documentation to take advantage of dynamic variables.", + "documentation": "Please reference this documentation for information regarding dynamic variables.", "edit": "Edit", "enabled": "Enabled", "jar_update": "Server Executable Updated",