diff --git a/.gitlab/lint.yml b/.gitlab/lint.yml index bc797808..37649e1a 100644 --- a/.gitlab/lint.yml +++ b/.gitlab/lint.yml @@ -5,7 +5,7 @@ yamllint: stage: lint image: registry.gitlab.com/pipeline-components/yamllint:latest tags: - - docker + - saas-linux-medium-amd64 rules: - if: "$CODE_QUALITY_DISABLED" when: never @@ -18,7 +18,7 @@ jsonlint: stage: lint image: registry.gitlab.com/pipeline-components/jsonlint:latest tags: - - docker + - saas-linux-medium-amd64 rules: - if: "$CODE_QUALITY_DISABLED" when: never @@ -33,7 +33,7 @@ black: stage: lint image: registry.gitlab.com/pipeline-components/black:latest tags: - - docker + - saas-linux-medium-amd64 rules: - if: "$CODE_QUALITY_DISABLED" when: never @@ -46,7 +46,7 @@ pylint: stage: lint image: registry.gitlab.com/pipeline-components/pylint:latest tags: - - docker + - saas-linux-medium-amd64 rules: - if: "$CODE_QUALITY_DISABLED" when: never @@ -69,7 +69,7 @@ sonarcloud-check: name: sonarsource/sonar-scanner-cli:latest entrypoint: [""] tags: - - docker + - saas-linux-medium-amd64 rules: - if: "$SONAR_TOKEN == null" when: never @@ -91,7 +91,7 @@ lang-check: stage: lint image: alpine:latest tags: - - docker + - saas-linux-medium-amd64 rules: - if: "$CODE_QUALITY_DISABLED" when: never diff --git a/CHANGELOG.md b/CHANGELOG.md index 3be9e1a1..994a0217 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ # Changelog -## --- [4.3.3] - 2024/TBD +## --- [4.4.1] - 2024/TBD ### New features TBD ### Bug fixes @@ -10,6 +10,20 @@ TBD TBD

+## --- [4.4.0] - 2024/05/11 +### Refactor +- Refactor API keys "super user" to "full access" ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/731) | [Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/759)) +- Refactor SBuilder to use Big Bucket Svc ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/755)) +### Bug fixes +- Reset query arguments on login if `?next` is not available ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/750)) +- Fix child schedule failing to load after del parent ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/753)) +### Tweaks +- Add link to go back to dashboard on error page ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/743)) +- Set audit logging to logfile instead of DB ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/751)) +### Lang +- Changes of phrase in `cs_CS` translation ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/749)) +

+ ## --- [4.3.2] - 2024/04/07 ### Refactor - Refactor ServerJars caching and move to api.serverjars.com ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/744) | [Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/746)) diff --git a/README.md b/README.md index 64e26224..2b382faf 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.3.3 +# Crafty Controller 4.4.1 > Python based Control Panel for your Minecraft Server ## What is Crafty Controller? diff --git a/app/classes/controllers/management_controller.py b/app/classes/controllers/management_controller.py index 7fea8a34..fc661e84 100644 --- a/app/classes/controllers/management_controller.py +++ b/app/classes/controllers/management_controller.py @@ -95,9 +95,6 @@ class ManagementController: # ********************************************************************************** # Audit_Log Methods # ********************************************************************************** - @staticmethod - def get_activity_log(): - return HelpersManagement.get_activity_log() def add_to_audit_log(self, user_id, log_msg, server_id=None, source_ip=None): return self.management_helper.add_to_audit_log( diff --git a/app/classes/controllers/server_perms_controller.py b/app/classes/controllers/server_perms_controller.py index 37893e9e..4586b4aa 100644 --- a/app/classes/controllers/server_perms_controller.py +++ b/app/classes/controllers/server_perms_controller.py @@ -17,6 +17,10 @@ class ServerPermsController: def get_server_user_list(server_id): return PermissionsServers.get_server_user_list(server_id) + @staticmethod + def get_permissions(permissions_mask): + return PermissionsServers.get_permissions(permissions_mask) + @staticmethod def list_defined_permissions(): permissions_list = PermissionsServers.get_permissions_list() @@ -61,6 +65,22 @@ class ServerPermsController: def get_permissions_mask(role_id, server_id): return PermissionsServers.get_permissions_mask(role_id, server_id) + @staticmethod + def get_lowest_api_perm_mask(user_server_permissions_mask, api_key_permssions_mask): + mask = "" + # If this isn't an API key we'll know the request came from basic + # authentication and ignore the API key permissions mask. + if not api_key_permssions_mask: + return user_server_permissions_mask + for _index, (user_perm, api_perm) in enumerate( + zip(user_server_permissions_mask, api_key_permssions_mask) + ): + if user_perm == "1" and api_perm == "1": + mask += "1" + else: + mask += "0" + return mask + @staticmethod def set_permission( permission_mask, permission_tested: EnumPermissionsServer, value @@ -82,6 +102,11 @@ class ServerPermsController: def get_api_key_permissions_list(key: ApiKeys, server_id: str): return PermissionsServers.get_api_key_permissions_list(key, server_id) + @staticmethod + def get_user_permissions_mask(user_id: str, server_id: str): + user = HelperUsers.get_user_model(user_id) + return PermissionsServers.get_user_permissions_mask(user, server_id) + @staticmethod def get_authorized_servers_stats_from_roles(user_id): user_roles = HelperUsers.get_user_roles_id(user_id) diff --git a/app/classes/logging/log_formatter.py b/app/classes/logging/log_formatter.py new file mode 100644 index 00000000..e3f2b4f7 --- /dev/null +++ b/app/classes/logging/log_formatter.py @@ -0,0 +1,53 @@ +import logging +import logging.config +import json +from datetime import datetime + + +class JsonEncoderStrFallback(json.JSONEncoder): + def default(self, o): + try: + return super().default(o) + except TypeError as exc: + if "not JSON serializable" in str(exc): + return str(o) + raise + + +class JsonEncoderDatetime(JsonEncoderStrFallback): + def default(self, o): + if isinstance(o, datetime): + return o.strftime("%Y-%m-%dT%H:%M:%S%z") + + return super().default(o) + + +class JsonFormatter(logging.Formatter): + def formatTime(self, record, datefmt=None): + """ + Override formatTime to customize the time format. + """ + timestamp = datetime.fromtimestamp(record.created) + if datefmt: + # Use the specified date format + return timestamp.strftime(datefmt) + # Default date format: YYYY-MM-DD HH:MM:SS,mmm + secs = int(record.msecs) + return f"{timestamp.strftime('%Y-%m-%d %H:%M:%S')},{secs:03d}" + + def format(self, record): + log_data = { + "level": record.levelname, + "time": self.formatTime(record), + "log_msg": record.getMessage(), + } + + # Filter out standard log record attributes and include only custom ones + custom_attrs = ["user_name", "user_id", "server_id", "source_ip"] + extra_attrs = { + key: value for key, value in record.__dict__.items() if key in custom_attrs + } + + # Merge extra attributes with log data + log_data.update(extra_attrs) + return json.dumps(log_data) diff --git a/app/classes/minecraft/bigbucket.py b/app/classes/minecraft/bigbucket.py new file mode 100644 index 00000000..591aa6d8 --- /dev/null +++ b/app/classes/minecraft/bigbucket.py @@ -0,0 +1,236 @@ +import os +import json +import threading +import time +import logging +from datetime import datetime +import requests + +from app.classes.controllers.servers_controller import ServersController +from app.classes.models.server_permissions import PermissionsServers +from app.classes.shared.file_helpers import FileHelpers +from app.classes.shared.websocket_manager import WebSocketManager + +logger = logging.getLogger(__name__) +# Temp type var until sjars restores generic fetchTypes0 + + +class BigBucket: + def __init__(self, helper): + self.helper = helper + # remove any trailing slash from config.json + # url since we add it on all the calls + self.base_url = str( + self.helper.get_setting("big_bucket_repo", "https://jars.arcadiatech.org") + ).rstrip("/") + + def _read_cache(self) -> dict: + cache_file = self.helper.big_bucket_cache + cache = {} + try: + with open(cache_file, "r", encoding="utf-8") as f: + cache = json.load(f) + + except Exception as e: + logger.error(f"Unable to read big_bucket cache file: {e}") + + return cache + + def get_bucket_data(self): + data = self._read_cache() + return data.get("categories") + + def _check_bucket_alive(self) -> bool: + logger.info("Checking Big Bucket status") + + check_url = f"{self.base_url}/healthcheck" + try: + response = requests.get(check_url, timeout=2) + response_json = response.json() + if ( + response.status_code in [200, 201] + and response_json.get("status") == "ok" + ): + logger.info("Big bucket is alive and responding as expected") + return True + except Exception as e: + logger.error(f"Unable to connect to big bucket due to error: {e}") + return False + + logger.error( + "Big bucket manifest is not available as expected or unable to contact" + ) + return False + + def _get_big_bucket(self) -> dict: + logger.debug("Calling for big bucket manifest.") + try: + response = requests.get(f"{self.base_url}/manifest.json", timeout=5) + if response.status_code in [200, 201]: + data = response.json() + del data["manifest_version"] + return data + return {} + except TimeoutError as e: + logger.error(f"Unable to get jars from remote with error {e}") + return {} + + def _refresh_cache(self): + """ + Contains the shared logic for refreshing the cache. + This method is called by both manual_refresh_cache and refresh_cache methods. + """ + if not self._check_bucket_alive(): + logger.error("big bucket API is not available.") + return False + + cache_data = { + "last_refreshed": datetime.now().strftime("%m/%d/%Y, %H:%M:%S"), + "categories": self._get_big_bucket(), + } + try: + with open( + self.helper.big_bucket_cache, "w", encoding="utf-8" + ) as cache_file: + json.dump(cache_data, cache_file, indent=4) + logger.info("Cache file successfully refreshed manually.") + except Exception as e: + logger.error(f"Failed to update cache file manually: {e}") + + def manual_refresh_cache(self): + """ + Manually triggers the cache refresh process. + """ + logger.info("Manual bucket cache refresh initiated.") + self._refresh_cache() + logger.info("Manual refresh completed.") + + def refresh_cache(self): + """ + Automatically trigger cache refresh process based age. + + This method checks if the cache file is older than a specified number of days + before deciding to refresh. + """ + cache_file_path = self.helper.big_bucket_cache + + # Determine if the cache is old and needs refreshing + cache_old = self.helper.is_file_older_than_x_days(cache_file_path) + + # debug override + # cache_old = True + + if not self._check_bucket_alive(): + logger.error("big bucket API is not available.") + return False + + if not cache_old: + logger.info("Cache file is not old enough to require automatic refresh.") + return False + + logger.info("Automatic cache refresh initiated due to old cache.") + self._refresh_cache() + + def get_fetch_url(self, jar, server, version) -> str: + """ + Constructs the URL for downloading a server JAR file based on the server type. + Parameters: + jar (str): The category of the JAR file to download. + server (str): Server software name (e.g., "paper"). + version (str): Server version. + + Returns: + str or None: URL for downloading the JAR file, or None if URL cannot be + constructed or an error occurs. + """ + try: + # Read cache file for URL that is in a list of one item + return self.get_bucket_data()[jar]["types"][server]["versions"][version][ + "url" + ][0] + except Exception as e: + logger.error(f"An error occurred while constructing fetch URL: {e}") + return None + + def download_jar(self, jar, server, version, path, server_id): + update_thread = threading.Thread( + name=f"server_download-{server_id}-{server}-{version}", + target=self.a_download_jar, + daemon=True, + args=(jar, server, version, path, server_id), + ) + update_thread.start() + + def a_download_jar(self, jar, server, version, path, server_id): + """ + Downloads a server JAR file and performs post-download actions including + notifying users and setting import status. + + This method waits for the server registration to complete, retrieves the + download URL for the specified server JAR file. + + Upon successful download, it either runs the installer for + Forge servers or simply finishes the import process for other types. It + notifies server users about the completion of the download. + + Parameters: + - jar (str): The category of the JAR file to download. + - server (str): The type of server software (e.g., 'forge', 'paper'). + - version (str): The version of the server software. + - path (str): The local filesystem path where the JAR file will be saved. + - server_id (str): The unique identifier for the server being updated or + imported, used for notifying users and setting the import status. + + Returns: + - bool: True if the JAR file was successfully downloaded and saved; + False otherwise. + + The method ensures that the server is properly registered before proceeding + with the download and handles exceptions by logging errors and reverting + the import status if necessary. + """ + # delaying download for server register to finish + time.sleep(3) + + fetch_url = self.get_fetch_url(jar, server, version) + if not fetch_url: + return False + + server_users = PermissionsServers.get_server_user_list(server_id) + + # Make sure the server is registered before updating its stats + while True: + try: + ServersController.set_import(server_id) + for user in server_users: + WebSocketManager().broadcast_user(user, "send_start_reload", {}) + break + except Exception as ex: + logger.debug(f"Server not registered yet. Delaying download - {ex}") + + # Initiate Download + jar_dir = os.path.dirname(path) + jar_name = os.path.basename(path) + logger.info(fetch_url) + success = FileHelpers.ssl_get_file(fetch_url, jar_dir, jar_name) + + # Post-download actions + if success: + if server == "forge-installer": + # If this is the newer Forge version, run the installer + ServersController.finish_import(server_id, True) + else: + ServersController.finish_import(server_id) + + # Notify users + for user in server_users: + WebSocketManager().broadcast_user( + user, "notification", "Executable download finished" + ) + time.sleep(3) # Delay for user notification + WebSocketManager().broadcast_user(user, "send_start_reload", {}) + else: + logger.error(f"Unable to save jar to {path} due to download failure.") + ServersController.finish_import(server_id) + + return success diff --git a/app/classes/minecraft/serverjars.py b/app/classes/minecraft/serverjars.py deleted file mode 100644 index 944ec382..00000000 --- a/app/classes/minecraft/serverjars.py +++ /dev/null @@ -1,395 +0,0 @@ -import os -import json -import threading -import time -import logging -from datetime import datetime -import requests - -from app.classes.controllers.servers_controller import ServersController -from app.classes.models.server_permissions import PermissionsServers -from app.classes.shared.file_helpers import FileHelpers -from app.classes.shared.websocket_manager import WebSocketManager - -logger = logging.getLogger(__name__) -# Temp type var until sjars restores generic fetchTypes0 -SERVERJARS_TYPES = ["modded", "proxies", "servers", "vanilla"] -PAPERJARS = ["paper", "folia"] - - -class ServerJars: - def __init__(self, helper): - self.helper = helper - self.base_url = "https://api.serverjars.com" - self.paper_base = "https://api.papermc.io" - - @staticmethod - def get_paper_jars(): - return PAPERJARS - - def get_paper_versions(self, project): - """ - Retrieves a list of versions for a specified project from the PaperMC API. - - Parameters: - project (str): The project name to query for available versions. - - Returns: - list: A list of version strings available for the project. Returns an empty - list if the API call fails or if no versions are found. - - This function makes a GET request to the PaperMC API to fetch available project - versions, The versions are returned in reverse order, with the most recent - version first. - """ - try: - response = requests.get( - f"{self.paper_base}/v2/projects/{project}/", timeout=2 - ) - response.raise_for_status() - api_data = response.json() - except Exception as e: - logger.error(f"Error loading project versions for {project}: {e}") - return [] - - versions = api_data.get("versions", []) - versions.reverse() # Ensure the most recent version comes first - return versions - - def get_paper_build(self, project, version): - """ - Fetches the latest build for a specified project and version from PaperMC API. - - Parameters: - project (str): Project name, typically a server software like 'paper'. - version (str): Project version to fetch the build number for. - - Returns: - int or None: Latest build number if successful, None if not or on error. - - This method attempts to query the PaperMC API for the latest build and - handles exceptions by logging errors and returning None. - """ - try: - response = requests.get( - f"{self.paper_base}/v2/projects/{project}/versions/{version}/builds/", - timeout=2, - ) - response.raise_for_status() - api_data = response.json() - except Exception as e: - logger.error(f"Error fetching build for {project} {version}: {e}") - return None - - builds = api_data.get("builds", []) - return builds[-1] if builds else None - - def _read_cache(self): - cache_file = self.helper.serverjar_cache - cache = {} - try: - with open(cache_file, "r", encoding="utf-8") as f: - cache = json.load(f) - - except Exception as e: - logger.error(f"Unable to read serverjars.com cache file: {e}") - - return cache - - def get_serverjar_data(self): - data = self._read_cache() - return data.get("types") - - def _check_sjars_api_alive(self): - logger.info("Checking serverjars.com API status") - - check_url = f"{self.base_url}" - try: - response = requests.get(check_url, timeout=2) - response_json = response.json() - - if ( - response.status_code in [200, 201] - and response_json.get("status") == "success" - and response_json.get("response", {}).get("status") == "ok" - ): - logger.info("Serverjars.com API is alive and responding as expected") - return True - except Exception as e: - logger.error(f"Unable to connect to serverjar.com API due to error: {e}") - return False - - logger.error( - "Serverjars.com API is not responding as expected or unable to contact" - ) - return False - - def _fetch_projects_for_type(self, server_type): - """ - Fetches projects for a given server type from the ServerJars API. - """ - try: - response = requests.get( - f"{self.base_url}/api/fetchTypes/{server_type}", timeout=5 - ) - response.raise_for_status() # Ensure HTTP errors are caught - data = response.json() - if data.get("status") == "success": - return data["response"].get("servers", []) - except requests.RequestException as e: - print(f"Error fetching projects for type {server_type}: {e}") - return [] - - def _get_server_type_list(self): - """ - Builds the type structure with projects fetched for each type. - """ - type_structure = {} - for server_type in SERVERJARS_TYPES: - projects = self._fetch_projects_for_type(server_type) - type_structure[server_type] = {project: [] for project in projects} - return type_structure - - def _get_jar_versions(self, server_type, project_name, max_ver=50): - """ - Grabs available versions for specified project - - Args: - server_type (str): Server Type Category (modded, servers, etc) - project_name (str): Target project (paper, forge, magma, etc) - max (int, optional): Max versions returned. Defaults to 50. - - Returns: - list: An array of versions - """ - url = f"{self.base_url}/api/fetchAll/{server_type}/{project_name}?max={max_ver}" - try: - response = requests.get(url, timeout=5) - response.raise_for_status() # Ensure HTTP errors are caught - data = response.json() - logger.debug(f"Received data for {server_type}/{project_name}: {data}") - - if data.get("status") == "success": - versions = [ - item.get("version") - for item in data.get("response", []) - if "version" in item - ] - versions.reverse() # Reverse so versions are newest -> oldest - logger.debug(f"Versions extracted: {versions}") - return versions - except requests.RequestException as e: - logger.error( - f"Error fetching jar versions for {server_type}/{project_name}: {e}" - ) - - return [] - - def _refresh_cache(self): - """ - Contains the shared logic for refreshing the cache. - This method is called by both manual_refresh_cache and refresh_cache methods. - """ - now = datetime.now() - cache_data = { - "last_refreshed": now.strftime("%m/%d/%Y, %H:%M:%S"), - "types": self._get_server_type_list(), - } - - for server_type, projects in cache_data["types"].items(): - for project_name in projects: - versions = self._get_jar_versions(server_type, project_name) - cache_data["types"][server_type][project_name] = versions - - for paper_project in PAPERJARS: - cache_data["types"]["servers"][paper_project] = self.get_paper_versions( - paper_project - ) - - return cache_data - - def manual_refresh_cache(self): - """ - Manually triggers the cache refresh process. - """ - if not self._check_sjars_api_alive(): - logger.error("ServerJars API is not available.") - return False - - logger.info("Manual cache refresh requested.") - cache_data = self._refresh_cache() - - # Save the updated cache data - try: - with open(self.helper.serverjar_cache, "w", encoding="utf-8") as cache_file: - json.dump(cache_data, cache_file, indent=4) - logger.info("Cache file successfully refreshed manually.") - except Exception as e: - logger.error(f"Failed to update cache file manually: {e}") - - def refresh_cache(self): - """ - Automatically trigger cache refresh process based age. - - This method checks if the cache file is older than a specified number of days - before deciding to refresh. - """ - cache_file_path = self.helper.serverjar_cache - - # Determine if the cache is old and needs refreshing - cache_old = self.helper.is_file_older_than_x_days(cache_file_path) - - # debug override - # cache_old = True - - if not self._check_sjars_api_alive(): - logger.error("ServerJars API is not available.") - return False - - if not cache_old: - logger.info("Cache file is not old enough to require automatic refresh.") - return False - - logger.info("Automatic cache refresh initiated due to old cache.") - cache_data = self._refresh_cache() - - # Save the updated cache data - try: - with open(cache_file_path, "w", encoding="utf-8") as cache_file: - json.dump(cache_data, cache_file, indent=4) - logger.info("Cache file successfully refreshed automatically.") - except Exception as e: - logger.error(f"Failed to update cache file automatically: {e}") - - def get_fetch_url(self, jar, server, version): - """ - Constructs the URL for downloading a server JAR file based on the server type. - - Supports two main types of server JAR sources: - - ServerJars API for servers not in PAPERJARS. - - Paper API for servers available through the Paper project. - - Parameters: - jar (str): Name of the JAR file. - server (str): Server software name (e.g., "paper"). - version (str): Server version. - - Returns: - str or None: URL for downloading the JAR file, or None if URL cannot be - constructed or an error occurs. - """ - try: - # Check if the server type is not specifically handled by Paper. - if server not in PAPERJARS: - return f"{self.base_url}/api/fetchJar/{jar}/{server}/{version}" - - # For Paper servers, attempt to get the build for the specified version. - paper_build_info = self.get_paper_build(server, version) - if paper_build_info is None: - # Log an error or handle the case where paper_build_info is None - logger.error( - "Error: Unable to get build information for server:" - f" {server}, version: {version}" - ) - return None - - build = paper_build_info.get("build") - if not build: - # Log an error or handle the case where build is None or not found - logger.error( - f"Error: Build number not found for server:" - f" {server}, version: {version}" - ) - return None - - # Construct and return the URL for downloading the Paper server JAR. - return ( - f"{self.paper_base}/v2/projects/{server}/versions/{version}/" - f"builds/{build}/downloads/{server}-{version}-{build}.jar" - ) - except Exception as e: - logger.error(f"An error occurred while constructing fetch URL: {e}") - return None - - def download_jar(self, jar, server, version, path, server_id): - update_thread = threading.Thread( - name=f"server_download-{server_id}-{server}-{version}", - target=self.a_download_jar, - daemon=True, - args=(jar, server, version, path, server_id), - ) - update_thread.start() - - def a_download_jar(self, jar, server, version, path, server_id): - """ - Downloads a server JAR file and performs post-download actions including - notifying users and setting import status. - - This method waits for the server registration to complete, retrieves the - download URL for the specified server JAR file. - - Upon successful download, it either runs the installer for - Forge servers or simply finishes the import process for other types. It - notifies server users about the completion of the download. - - Parameters: - - jar (str): The name of the JAR file to download. - - server (str): The type of server software (e.g., 'forge', 'paper'). - - version (str): The version of the server software. - - path (str): The local filesystem path where the JAR file will be saved. - - server_id (str): The unique identifier for the server being updated or - imported, used for notifying users and setting the import status. - - Returns: - - bool: True if the JAR file was successfully downloaded and saved; - False otherwise. - - The method ensures that the server is properly registered before proceeding - with the download and handles exceptions by logging errors and reverting - the import status if necessary. - """ - # delaying download for server register to finish - time.sleep(3) - - fetch_url = self.get_fetch_url(jar, server, version) - if not fetch_url: - return False - - server_users = PermissionsServers.get_server_user_list(server_id) - - # Make sure the server is registered before updating its stats - while True: - try: - ServersController.set_import(server_id) - for user in server_users: - WebSocketManager().broadcast_user(user, "send_start_reload", {}) - break - except Exception as ex: - logger.debug(f"Server not registered yet. Delaying download - {ex}") - - # Initiate Download - jar_dir = os.path.dirname(path) - jar_name = os.path.basename(path) - logger.info(fetch_url) - success = FileHelpers.ssl_get_file(fetch_url, jar_dir, jar_name) - - # Post-download actions - if success: - if server == "forge": - # If this is the newer Forge version, run the installer - ServersController.finish_import(server_id, True) - else: - ServersController.finish_import(server_id) - - # Notify users - for user in server_users: - WebSocketManager().broadcast_user( - user, "notification", "Executable download finished" - ) - time.sleep(3) # Delay for user notification - WebSocketManager().broadcast_user(user, "send_start_reload", {}) - else: - logger.error(f"Unable to save jar to {path} due to download failure.") - ServersController.finish_import(server_id) - - return success diff --git a/app/classes/models/crafty_permissions.py b/app/classes/models/crafty_permissions.py index 7430f332..e7a159d9 100644 --- a/app/classes/models/crafty_permissions.py +++ b/app/classes/models/crafty_permissions.py @@ -187,7 +187,7 @@ class PermissionsCrafty: @staticmethod def get_api_key_permissions_list(key: ApiKeys): user = HelperUsers.get_user(key.user_id) - if user["superuser"] and key.superuser: + if user["superuser"] and key.full_access: return PermissionsCrafty.get_permissions_list() if user["superuser"]: # User is superuser but API key isn't diff --git a/app/classes/models/management.py b/app/classes/models/management.py index dc754c72..86606249 100644 --- a/app/classes/models/management.py +++ b/app/classes/models/management.py @@ -18,28 +18,10 @@ from app.classes.models.base_model import BaseModel from app.classes.models.users import HelperUsers from app.classes.models.servers import Servers from app.classes.models.server_permissions import PermissionsServers -from app.classes.shared.main_models import DatabaseShortcuts from app.classes.shared.websocket_manager import WebSocketManager logger = logging.getLogger(__name__) - - -# ********************************************************************************** -# Audit_Log Class -# ********************************************************************************** -class AuditLog(BaseModel): - audit_id = AutoField() - created = DateTimeField(default=datetime.datetime.now) - user_name = CharField(default="") - user_id = IntegerField(default=0, index=True) - source_ip = CharField(default="127.0.0.1") - server_id = ForeignKeyField( - Servers, backref="audit_server", null=True - ) # When auditing global events, use server ID null - log_msg = TextField(default="") - - class Meta: - table_name = "audit_log" +auth_logger = logging.getLogger("audit_log") # ********************************************************************************** @@ -155,10 +137,6 @@ class HelpersManagement: # ********************************************************************************** # Audit_Log Methods # ********************************************************************************** - @staticmethod - def get_activity_log(): - query = AuditLog.select() - return DatabaseShortcuts.return_db_rows(query) def add_to_audit_log(self, user_id, log_msg, server_id=None, source_ip=None): logger.debug(f"Adding to audit log User:{user_id} - Message: {log_msg} ") @@ -172,50 +150,28 @@ class HelpersManagement: WebSocketManager().broadcast_user(user, "notification", audit_msg) except Exception as e: logger.error(f"Error broadcasting to user {user} - {e}") - - AuditLog.insert( - { - AuditLog.user_name: user_data["username"], - AuditLog.user_id: user_id, - AuditLog.server_id: server_id, - AuditLog.log_msg: audit_msg, - AuditLog.source_ip: source_ip, - } - ).execute() - # deletes records when there's more than 300 - ordered = AuditLog.select().order_by(+AuditLog.created) - for item in ordered: - if not self.helper.get_setting("max_audit_entries"): - max_entries = 300 - else: - max_entries = self.helper.get_setting("max_audit_entries") - if AuditLog.select().count() > max_entries: - AuditLog.delete().where(AuditLog.audit_id == item.audit_id).execute() - else: - return + auth_logger.info( + str(log_msg), + extra={ + "user_name": user_data["username"], + "user_id": user_id, + "server_id": server_id, + "source_ip": source_ip, + }, + ) def add_to_audit_log_raw(self, user_name, user_id, server_id, log_msg, source_ip): - AuditLog.insert( - { - AuditLog.user_name: user_name, - AuditLog.user_id: user_id, - AuditLog.server_id: server_id, - AuditLog.log_msg: log_msg, - AuditLog.source_ip: source_ip, - } - ).execute() - # deletes records when there's more than 300 - ordered = AuditLog.select().order_by(+AuditLog.created) - for item in ordered: - # configurable through app/config/config.json - if not self.helper.get_setting("max_audit_entries"): - max_entries = 300 - else: - max_entries = self.helper.get_setting("max_audit_entries") - if AuditLog.select().count() > max_entries: - AuditLog.delete().where(AuditLog.audit_id == item.audit_id).execute() - else: - return + if isinstance(server_id, Servers) and server_id is not None: + server_id = server_id.server_id + auth_logger.info( + str(log_msg), + extra={ + "user_name": user_name, + "user_id": user_id, + "server_id": server_id, + "source_ip": source_ip, + }, + ) @staticmethod def create_crafty_row(): diff --git a/app/classes/models/server_permissions.py b/app/classes/models/server_permissions.py index 56f9d8ac..12301e30 100644 --- a/app/classes/models/server_permissions.py +++ b/app/classes/models/server_permissions.py @@ -264,7 +264,7 @@ class PermissionsServers: @staticmethod def get_api_key_permissions_list(key: ApiKeys, server_id: str): user = HelperUsers.get_user(key.user_id) - if user["superuser"] and key.superuser: + if user["superuser"] and key.full_access: return PermissionsServers.get_permissions_list() roles_list = HelperUsers.get_user_roles_id(user["user_id"]) role_server = ( diff --git a/app/classes/models/users.py b/app/classes/models/users.py index 1963bf3b..f35943ea 100644 --- a/app/classes/models/users.py +++ b/app/classes/models/users.py @@ -71,7 +71,7 @@ class ApiKeys(BaseModel): user_id = ForeignKeyField(Users, backref="api_token", index=True) server_permissions = CharField(default="00000000") crafty_permissions = CharField(default="000") - superuser = BooleanField(default=False) + full_access = BooleanField(default=False) class Meta: table_name = "api_keys" @@ -407,7 +407,7 @@ class HelperUsers: def add_user_api_key( name: str, user_id: str, - superuser: bool = False, + full_access: bool = False, server_permissions_mask: t.Optional[str] = None, crafty_permissions_mask: t.Optional[str] = None, ): @@ -425,7 +425,7 @@ class HelperUsers: if crafty_permissions_mask is not None else {} ), - ApiKeys.superuser: superuser, + ApiKeys.full_access: full_access, } ).execute() diff --git a/app/classes/shared/helpers.py b/app/classes/shared/helpers.py index 0427da11..55a588fc 100644 --- a/app/classes/shared/helpers.py +++ b/app/classes/shared/helpers.py @@ -72,7 +72,7 @@ class Helpers: self.db_path = os.path.join( self.root_dir, "app", "config", "db", "crafty.sqlite" ) - self.serverjar_cache = os.path.join(self.config_dir, "serverjars.json") + self.big_bucket_cache = os.path.join(self.config_dir, "bigbucket.json") self.credits_cache = os.path.join(self.config_dir, "credits.json") self.passhasher = PasswordHasher() self.exiting = False @@ -516,6 +516,7 @@ class Helpers: "monitored_mounts": mounts, "dir_size_poll_freq_minutes": 5, "crafty_logs_delete_after_days": 0, + "big_bucket_repo": "https://jars.arcadiatech.org", } def get_all_settings(self): diff --git a/app/classes/shared/main_controller.py b/app/classes/shared/main_controller.py index 9d99e4f8..d693d96b 100644 --- a/app/classes/shared/main_controller.py +++ b/app/classes/shared/main_controller.py @@ -32,7 +32,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.import_helper import ImportHelpers -from app.classes.minecraft.serverjars import ServerJars +from app.classes.minecraft.bigbucket import BigBucket from app.classes.shared.websocket_manager import WebSocketManager logger = logging.getLogger(__name__) @@ -43,7 +43,7 @@ class Controller: self.helper: Helpers = helper self.file_helper: FileHelpers = file_helper self.import_helper: ImportHelpers = import_helper - self.server_jars: ServerJars = ServerJars(helper) + self.big_bucket: BigBucket = BigBucket(helper) self.users_helper: HelperUsers = HelperUsers(database, self.helper) self.roles_helper: HelperRoles = HelperRoles(database) self.servers_helper: HelperServers = HelperServers(database) @@ -436,7 +436,7 @@ class Controller: if root_create_data["create_type"] == "download_jar": if Helpers.is_os_windows(): # Let's check for and setup for install server commands - if create_data["type"] == "forge": + if create_data["type"] == "forge-installer": server_command = ( f"java -Xms{Helpers.float_to_string(min_mem)}M " f"-Xmx{Helpers.float_to_string(max_mem)}M " @@ -449,7 +449,7 @@ class Controller: f'-jar "{server_file}" nogui' ) else: - if create_data["type"] == "forge": + if create_data["type"] == "forge-installer": server_command = ( f"java -Xms{Helpers.float_to_string(min_mem)}M " f"-Xmx{Helpers.float_to_string(max_mem)}M " @@ -568,19 +568,16 @@ class Controller: if data["create_type"] == "minecraft_java": if root_create_data["create_type"] == "download_jar": # modded update urls from server jars will only update the installer - if ( - create_data["category"] != "modded" - and create_data["type"] not in ServerJars.get_paper_jars() - ): + if create_data["type"] != "forge-installer": server_obj = self.servers.get_server_obj(new_server_id) - url = ( - "https://api.serverjars.com/api/fetchJar/" - f"{create_data['category']}" - f"/{create_data['type']}/{create_data['version']}" + url = self.big_bucket.get_fetch_url( + create_data["category"], + create_data["type"], + create_data["version"], ) server_obj.executable_update_url = url self.servers.update_server(server_obj) - self.server_jars.download_jar( + self.big_bucket.download_jar( create_data["category"], create_data["type"], create_data["version"], diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py index a31cc891..a6c98b89 100644 --- a/app/classes/shared/server.py +++ b/app/classes/shared/server.py @@ -690,7 +690,8 @@ class ServerInstance: try: # Getting the forge version from the executable command version = re.findall( - r"forge-([0-9\.]+)((?:)|(?:-([0-9\.]+)-[a-zA-Z]+)).jar", + r"forge-installer-([0-9\.]+)((?:)|" + r"(?:-([0-9\.]+)-[a-zA-Z]+)).jar", server_obj.execution_command, ) version_param = version[0][0].split(".") diff --git a/app/classes/shared/tasks.py b/app/classes/shared/tasks.py index d1b786b9..b9513441 100644 --- a/app/classes/shared/tasks.py +++ b/app/classes/shared/tasks.py @@ -685,16 +685,16 @@ class TasksManager: id="stats", ) - def serverjar_cache_refresher(self): - logger.info("Refreshing serverjars.com cache on start") - self.controller.server_jars.refresh_cache() + def big_bucket_cache_refresher(self): + logger.info("Refreshing big bucket cache on start") + self.controller.big_bucket.refresh_cache() - logger.info("Scheduling Serverjars.com cache refresh service every 12 hours") + logger.info("Scheduling big bucket cache refresh service every 12 hours") self.scheduler.add_job( - self.controller.server_jars.refresh_cache, + self.controller.big_bucket.refresh_cache, "interval", hours=12, - id="serverjars", + id="big_bucket", ) def realtime(self): diff --git a/app/classes/web/base_handler.py b/app/classes/web/base_handler.py index ced6cb97..7cca08e8 100644 --- a/app/classes/web/base_handler.py +++ b/app/classes/web/base_handler.py @@ -182,6 +182,7 @@ class BaseHandler(tornado.web.RequestHandler): t.List[str], bool, t.Dict[str, t.Any], + str, ] ]: try: @@ -190,9 +191,10 @@ class BaseHandler(tornado.web.RequestHandler): ) superuser = user["superuser"] + server_permissions_api_mask = "" if api_key is not None: - superuser = superuser and api_key.superuser - + superuser = superuser and api_key.full_access + server_permissions_api_mask = api_key.server_permissions exec_user_role = set() if superuser: authorized_servers = self.controller.servers.get_all_defined_servers() @@ -214,6 +216,7 @@ class BaseHandler(tornado.web.RequestHandler): user["user_id"] ) ) + logger.debug(user["roles"]) for r in user["roles"]: role = self.controller.roles.get_role(r) @@ -234,6 +237,7 @@ class BaseHandler(tornado.web.RequestHandler): exec_user_role, superuser, user, + server_permissions_api_mask, ) logging.debug("Auth unsuccessful") auth_log.error( diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index ac5a8b41..962a5abb 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -168,7 +168,7 @@ class PanelHandler(BaseHandler): # Commented out because there is no server access control for API keys, # they just inherit from the host user # if api_key is not None: - # superuser = superuser and api_key.superuser + # superuser = superuser and api_key.full_access if server_id is None: self.redirect("/panel/error?error=Invalid Server ID") @@ -242,7 +242,7 @@ class PanelHandler(BaseHandler): api_key, _token_data, exec_user = self.current_user superuser = exec_user["superuser"] if api_key is not None: - superuser = superuser and api_key.superuser + superuser = superuser and api_key.full_access if superuser: # TODO: Figure out a better solution defined_servers = self.controller.servers.list_defined_servers() @@ -351,7 +351,7 @@ class PanelHandler(BaseHandler): "created": api_key.created, "server_permissions": api_key.server_permissions, "crafty_permissions": api_key.crafty_permissions, - "superuser": api_key.superuser, + "full_access": api_key.full_access, } if api_key is not None else None @@ -1224,9 +1224,11 @@ class PanelHandler(BaseHandler): page_data["schedule"]["interval_type"] = schedule.interval_type if schedule.interval_type == "reaction": difficulty = "reaction" - page_data["parent"] = self.controller.management.get_scheduled_task( - schedule.parent - ) + page_data["parent"] = None + if schedule.parent: + page_data["parent"] = self.controller.management.get_scheduled_task( + schedule.parent + ) elif schedule.cron_string == "": difficulty = "basic" page_data["parent"] = None @@ -1417,6 +1419,9 @@ class PanelHandler(BaseHandler): page_data["crafty_permissions_all"] = ( self.controller.crafty_perms.list_defined_crafty_permissions() ) + page_data["user_crafty_permissions"] = ( + self.controller.crafty_perms.get_crafty_permissions_list(user_id) + ) if user_id is None: self.redirect("/panel/error?error=Invalid User ID") @@ -1564,8 +1569,6 @@ class PanelHandler(BaseHandler): template = "panel/panel_edit_role.html" elif page == "activity_logs": - page_data["audit_logs"] = self.controller.management.get_activity_log() - template = "panel/activity_logs.html" elif page == "download_file": diff --git a/app/classes/web/public_handler.py b/app/classes/web/public_handler.py index 21e2d495..a3d89d25 100644 --- a/app/classes/web/public_handler.py +++ b/app/classes/web/public_handler.py @@ -48,7 +48,10 @@ class PublicHandler(BaseHandler): } if self.request.query: - page_data["query"] = self.request.query_arguments.get("next")[0].decode() + request_query = self.request.query_arguments.get("next") + if not request_query: + self.redirect("/login") + page_data["query"] = request_query[0].decode() # sensible defaults template = "public/404.html" diff --git a/app/classes/web/routes/api/crafty/announcements/index.py b/app/classes/web/routes/api/crafty/announcements/index.py index 75f00f16..d66c4473 100644 --- a/app/classes/web/routes/api/crafty/announcements/index.py +++ b/app/classes/web/routes/api/crafty/announcements/index.py @@ -26,6 +26,7 @@ class ApiAnnounceIndexHandler(BaseApiHandler): _, _, _user, + _, ) = auth_data data = self.helper.get_announcements() @@ -72,6 +73,7 @@ class ApiAnnounceIndexHandler(BaseApiHandler): _, _, _user, + _, ) = auth_data try: data = json.loads(self.request.body) diff --git a/app/classes/web/routes/api/crafty/clogs/index.py b/app/classes/web/routes/api/crafty/clogs/index.py index 97a24a34..35f48a7f 100644 --- a/app/classes/web/routes/api/crafty/clogs/index.py +++ b/app/classes/web/routes/api/crafty/clogs/index.py @@ -1,3 +1,5 @@ +import os +import json from app.classes.web.base_api_handler import BaseApiHandler @@ -12,6 +14,7 @@ class ApiCraftyLogIndexHandler(BaseApiHandler): _, superuser, _, + _, ) = auth_data if not superuser: @@ -22,9 +25,17 @@ class ApiCraftyLogIndexHandler(BaseApiHandler): raise NotImplementedError if log_type == "audit": + with open( + os.path.join(self.controller.project_root, "logs", "audit.log"), + "r", + encoding="utf-8", + ) as f: + log_lines = [json.loads(line) for line in f] + rev_log_lines = log_lines[::-1] + return self.finish_json( 200, - {"status": "ok", "data": self.controller.management.get_activity_log()}, + {"status": "ok", "data": rev_log_lines}, ) if log_type == "session": diff --git a/app/classes/web/routes/api/crafty/config/index.py b/app/classes/web/routes/api/crafty/config/index.py index 40504d76..d625d339 100644 --- a/app/classes/web/routes/api/crafty/config/index.py +++ b/app/classes/web/routes/api/crafty/config/index.py @@ -31,6 +31,7 @@ config_json_schema = { "monitored_mounts": {"type": "array"}, "dir_size_poll_freq_minutes": {"type": "integer"}, "crafty_logs_delete_after_days": {"type": "integer"}, + "big_bucket_repo": {"type": "string"}, }, "additionalProperties": False, "minProperties": 1, @@ -67,6 +68,7 @@ class ApiCraftyConfigIndexHandler(BaseApiHandler): _, superuser, _, + _, ) = auth_data # GET /api/v2/roles?ids=true @@ -93,13 +95,7 @@ class ApiCraftyConfigIndexHandler(BaseApiHandler): auth_data = self.authenticate_user() if not auth_data: return - ( - _, - _, - _, - superuser, - user, - ) = auth_data + (_, _, _, superuser, user, _) = auth_data if not superuser: return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) @@ -149,6 +145,7 @@ class ApiCraftyCustomizeIndexHandler(BaseApiHandler): _, superuser, _, + _, ) = auth_data # GET /api/v2/roles?ids=true @@ -181,6 +178,7 @@ class ApiCraftyCustomizeIndexHandler(BaseApiHandler): _, superuser, user, + _, ) = auth_data if not superuser: return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) diff --git a/app/classes/web/routes/api/crafty/config/server_dir.py b/app/classes/web/routes/api/crafty/config/server_dir.py index 07cf7c26..bc88cba9 100644 --- a/app/classes/web/routes/api/crafty/config/server_dir.py +++ b/app/classes/web/routes/api/crafty/config/server_dir.py @@ -24,6 +24,7 @@ class ApiCraftyConfigServerDirHandler(BaseApiHandler): _, superuser, _, + _, ) = auth_data # GET /api/v2/roles?ids=true @@ -56,6 +57,7 @@ class ApiCraftyConfigServerDirHandler(BaseApiHandler): _, _, _, + _, ) = auth_data if not auth_data: diff --git a/app/classes/web/routes/api/crafty/exe_cache.py b/app/classes/web/routes/api/crafty/exe_cache.py index 8836aef8..7fa9743a 100644 --- a/app/classes/web/routes/api/crafty/exe_cache.py +++ b/app/classes/web/routes/api/crafty/exe_cache.py @@ -12,16 +12,17 @@ class ApiCraftyJarCacheIndexHandler(BaseApiHandler): _, _, _, + _, ) = auth_data if not auth_data[4]["superuser"]: return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) - self.controller.server_jars.manual_refresh_cache() + self.controller.big_bucket.manual_refresh_cache() self.finish_json( 200, { "status": "ok", - "data": self.controller.server_jars.get_serverjar_data(), + "data": self.controller.big_bucket.get_bucket_data(), }, ) diff --git a/app/classes/web/routes/api/roles/index.py b/app/classes/web/routes/api/roles/index.py index 0f656dbb..a8612c75 100644 --- a/app/classes/web/routes/api/roles/index.py +++ b/app/classes/web/routes/api/roles/index.py @@ -75,6 +75,7 @@ class ApiRolesIndexHandler(BaseApiHandler): _, superuser, _, + _, ) = auth_data # GET /api/v2/roles?ids=true @@ -107,6 +108,7 @@ class ApiRolesIndexHandler(BaseApiHandler): _, superuser, user, + _, ) = auth_data if not superuser: diff --git a/app/classes/web/routes/api/roles/role/index.py b/app/classes/web/routes/api/roles/role/index.py index 97362f5b..73fd9ff3 100644 --- a/app/classes/web/routes/api/roles/role/index.py +++ b/app/classes/web/routes/api/roles/role/index.py @@ -74,6 +74,7 @@ class ApiRolesRoleIndexHandler(BaseApiHandler): _, superuser, _, + _, ) = auth_data if not superuser: @@ -97,6 +98,7 @@ class ApiRolesRoleIndexHandler(BaseApiHandler): _, superuser, user, + _, ) = auth_data if not superuser: @@ -126,10 +128,19 @@ class ApiRolesRoleIndexHandler(BaseApiHandler): _, superuser, user, + _, ) = auth_data - if not superuser: - return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) + role = self.controller.roles.get_role(role_id) + if not superuser and user["user_id"] != role["manager"]: + return self.finish_json( + 400, + { + "status": "error", + "error": "NOT_AUTHORIZED", + "error_data": "Not Authorized", + }, + ) try: data = orjson.loads(self.request.body) diff --git a/app/classes/web/routes/api/roles/role/servers.py b/app/classes/web/routes/api/roles/role/servers.py index 0a0eff6f..8f41f6c6 100644 --- a/app/classes/web/routes/api/roles/role/servers.py +++ b/app/classes/web/routes/api/roles/role/servers.py @@ -13,6 +13,7 @@ class ApiRolesRoleServersHandler(BaseApiHandler): _, superuser, _, + _, ) = auth_data # GET /api/v2/roles/role/servers?ids=true diff --git a/app/classes/web/routes/api/roles/role/users.py b/app/classes/web/routes/api/roles/role/users.py index ac2227ac..48444ead 100644 --- a/app/classes/web/routes/api/roles/role/users.py +++ b/app/classes/web/routes/api/roles/role/users.py @@ -12,6 +12,7 @@ class ApiRolesRoleUsersHandler(BaseApiHandler): _, superuser, _, + _, ) = auth_data if not superuser: diff --git a/app/classes/web/routes/api/servers/index.py b/app/classes/web/routes/api/servers/index.py index 3c14f604..43cf01e2 100644 --- a/app/classes/web/routes/api/servers/index.py +++ b/app/classes/web/routes/api/servers/index.py @@ -139,7 +139,7 @@ new_server_schema = { "category": { "title": "Jar Category", "type": "string", - "examples": ["modded", "vanilla"], + "examples": ["Mc_java_servers", "Mc_java_proxies"], }, "properties": { "type": { @@ -685,6 +685,7 @@ class ApiServersIndexHandler(BaseApiHandler): _, _superuser, user, + _, ) = auth_data if EnumPermissionsCrafty.SERVER_CREATION not in exec_user_crafty_permissions: diff --git a/app/classes/web/routes/api/servers/server/action.py b/app/classes/web/routes/api/servers/server/action.py index 526899b5..aba06da3 100644 --- a/app/classes/web/routes/api/servers/server/action.py +++ b/app/classes/web/routes/api/servers/server/action.py @@ -18,13 +18,14 @@ class ApiServersServerActionHandler(BaseApiHandler): 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.COMMANDS - not in self.controller.server_perms.get_user_id_permissions_list( + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( auth_data[4]["user_id"], server_id - ) - ): + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) + if EnumPermissionsServer.COMMANDS not in server_permissions: # if the user doesn't have Commands permission, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) 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 index 5dc301bb..cfe8f4b1 100644 --- a/app/classes/web/routes/api/servers/server/backups/backup/index.py +++ b/app/classes/web/routes/api/servers/server/backups/backup/index.py @@ -26,12 +26,14 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler): auth_data = self.authenticate_user() if not auth_data: return - if ( - EnumPermissionsServer.BACKUP - not in self.controller.server_perms.get_user_id_permissions_list( + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( auth_data[4]["user_id"], server_id - ) - ): + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) + if EnumPermissionsServer.BACKUP not in server_permissions: # 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)) @@ -41,12 +43,14 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler): 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( + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( auth_data[4]["user_id"], server_id - ) - ): + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) + if EnumPermissionsServer.BACKUP not in server_permissions: # if the user doesn't have Schedule permission, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) @@ -89,12 +93,14 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler): auth_data = self.authenticate_user() if not auth_data: return - if ( - EnumPermissionsServer.BACKUP - not in self.controller.server_perms.get_user_id_permissions_list( + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( auth_data[4]["user_id"], server_id - ) - ): + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) + if EnumPermissionsServer.BACKUP not in server_permissions: # if the user doesn't have Schedule permission, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) diff --git a/app/classes/web/routes/api/servers/server/backups/index.py b/app/classes/web/routes/api/servers/server/backups/index.py index 0a95bff0..55744ea1 100644 --- a/app/classes/web/routes/api/servers/server/backups/index.py +++ b/app/classes/web/routes/api/servers/server/backups/index.py @@ -42,12 +42,14 @@ class ApiServersServerBackupsIndexHandler(BaseApiHandler): auth_data = self.authenticate_user() if not auth_data: return - if ( - EnumPermissionsServer.BACKUP - not in self.controller.server_perms.get_user_id_permissions_list( + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( auth_data[4]["user_id"], server_id - ) - ): + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) + if EnumPermissionsServer.BACKUP not in server_permissions: # 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)) @@ -83,13 +85,14 @@ class ApiServersServerBackupsIndexHandler(BaseApiHandler): 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( + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( auth_data[4]["user_id"], server_id - ) - ): + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) + if EnumPermissionsServer.BACKUP not in server_permissions: # if the user doesn't have Schedule permission, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) diff --git a/app/classes/web/routes/api/servers/server/files.py b/app/classes/web/routes/api/servers/server/files.py index 8e70d4fe..2951ff25 100644 --- a/app/classes/web/routes/api/servers/server/files.py +++ b/app/classes/web/routes/api/servers/server/files.py @@ -80,16 +80,16 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler): 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"}) - + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( + auth_data[4]["user_id"], server_id + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) if ( - EnumPermissionsServer.FILES - not in self.controller.server_perms.get_user_id_permissions_list( - auth_data[4]["user_id"], server_id - ) - and EnumPermissionsServer.BACKUP - not in self.controller.server_perms.get_user_id_permissions_list( - auth_data[4]["user_id"], server_id - ) + EnumPermissionsServer.FILES not in server_permissions + and EnumPermissionsServer.BACKUP not in server_permissions ): # if the user doesn't have Files or Backup permission, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) @@ -197,13 +197,14 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler): 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.FILES - not in self.controller.server_perms.get_user_id_permissions_list( + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( auth_data[4]["user_id"], server_id - ) - ): + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) + if EnumPermissionsServer.FILES not in server_permissions: # if the user doesn't have Files permission, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) try: @@ -254,13 +255,14 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler): 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.FILES - not in self.controller.server_perms.get_user_id_permissions_list( + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( auth_data[4]["user_id"], server_id - ) - ): + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) + if EnumPermissionsServer.FILES not in server_permissions: # if the user doesn't have Files permission, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) try: @@ -307,13 +309,14 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler): 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.FILES - not in self.controller.server_perms.get_user_id_permissions_list( + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( auth_data[4]["user_id"], server_id - ) - ): + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) + if EnumPermissionsServer.FILES not in server_permissions: # if the user doesn't have Files permission, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) try: @@ -373,13 +376,14 @@ class ApiServersServerFilesCreateHandler(BaseApiHandler): 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.FILES - not in self.controller.server_perms.get_user_id_permissions_list( + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( auth_data[4]["user_id"], server_id - ) - ): + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) + if EnumPermissionsServer.FILES not in server_permissions: # if the user doesn't have Files permission, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) try: @@ -438,13 +442,14 @@ class ApiServersServerFilesCreateHandler(BaseApiHandler): 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.FILES - not in self.controller.server_perms.get_user_id_permissions_list( + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( auth_data[4]["user_id"], server_id - ) - ): + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) + if EnumPermissionsServer.FILES not in server_permissions: # if the user doesn't have Files permission, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) try: @@ -504,13 +509,14 @@ class ApiServersServerFilesZipHandler(BaseApiHandler): 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.FILES - not in self.controller.server_perms.get_user_id_permissions_list( + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( auth_data[4]["user_id"], server_id - ) - ): + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) + if EnumPermissionsServer.FILES not in server_permissions: # if the user doesn't have Files permission, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) try: diff --git a/app/classes/web/routes/api/servers/server/index.py b/app/classes/web/routes/api/servers/server/index.py index 81035bd0..9bfc3a9a 100644 --- a/app/classes/web/routes/api/servers/server/index.py +++ b/app/classes/web/routes/api/servers/server/index.py @@ -102,13 +102,14 @@ class ApiServersServerIndexHandler(BaseApiHandler): 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.CONFIG - not in self.controller.server_perms.get_user_id_permissions_list( + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( auth_data[4]["user_id"], server_id - ) - ): + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) + if EnumPermissionsServer.CONFIG not in server_permissions: # if the user doesn't have Config permission, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) @@ -154,13 +155,14 @@ class ApiServersServerIndexHandler(BaseApiHandler): 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.CONFIG - not in self.controller.server_perms.get_user_id_permissions_list( + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( auth_data[4]["user_id"], server_id - ) - ): + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) + if EnumPermissionsServer.CONFIG not in server_permissions: # if the user doesn't have Config permission, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) diff --git a/app/classes/web/routes/api/servers/server/logs.py b/app/classes/web/routes/api/servers/server/logs.py index 94a8a71b..eb6ede00 100644 --- a/app/classes/web/routes/api/servers/server/logs.py +++ b/app/classes/web/routes/api/servers/server/logs.py @@ -30,13 +30,14 @@ class ApiServersServerLogsHandler(BaseApiHandler): 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.LOGS - not in self.controller.server_perms.get_user_id_permissions_list( + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( auth_data[4]["user_id"], server_id - ) - ): + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) + if EnumPermissionsServer.LOGS not in server_permissions: # if the user doesn't have Logs permission, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) diff --git a/app/classes/web/routes/api/servers/server/stdin.py b/app/classes/web/routes/api/servers/server/stdin.py index ba8400b7..ca2cd7d9 100644 --- a/app/classes/web/routes/api/servers/server/stdin.py +++ b/app/classes/web/routes/api/servers/server/stdin.py @@ -16,13 +16,14 @@ class ApiServersServerStdinHandler(BaseApiHandler): 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.COMMANDS - not in self.controller.server_perms.get_user_id_permissions_list( + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( auth_data[4]["user_id"], server_id - ) - ): + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) + if EnumPermissionsServer.COMMANDS not in server_permissions: # if the user doesn't have Commands permission, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) diff --git a/app/classes/web/routes/api/servers/server/tasks/index.py b/app/classes/web/routes/api/servers/server/tasks/index.py index 8e98bbbe..0c03319c 100644 --- a/app/classes/web/routes/api/servers/server/tasks/index.py +++ b/app/classes/web/routes/api/servers/server/tasks/index.py @@ -78,13 +78,14 @@ class ApiServersServerTasksIndexHandler(BaseApiHandler): 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.SCHEDULE - not in self.controller.server_perms.get_user_id_permissions_list( + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( auth_data[4]["user_id"], server_id - ) - ): + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) + if EnumPermissionsServer.SCHEDULE not in server_permissions: # if the user doesn't have Schedule permission, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) data["server_id"] = server_id diff --git a/app/classes/web/routes/api/servers/server/tasks/task/index.py b/app/classes/web/routes/api/servers/server/tasks/task/index.py index 742312a6..dac60762 100644 --- a/app/classes/web/routes/api/servers/server/tasks/task/index.py +++ b/app/classes/web/routes/api/servers/server/tasks/task/index.py @@ -54,12 +54,14 @@ class ApiServersServerTasksTaskIndexHandler(BaseApiHandler): auth_data = self.authenticate_user() if not auth_data: return - if ( - EnumPermissionsServer.SCHEDULE - not in self.controller.server_perms.get_user_id_permissions_list( + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( auth_data[4]["user_id"], server_id - ) - ): + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) + if EnumPermissionsServer.SCHEDULE not in server_permissions: # 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_scheduled_task(task_id)) @@ -68,12 +70,14 @@ class ApiServersServerTasksTaskIndexHandler(BaseApiHandler): auth_data = self.authenticate_user() if not auth_data: return - if ( - EnumPermissionsServer.SCHEDULE - not in self.controller.server_perms.get_user_id_permissions_list( + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( auth_data[4]["user_id"], server_id - ) - ): + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) + if EnumPermissionsServer.SCHEDULE not in server_permissions: # if the user doesn't have Schedule permission, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) @@ -120,13 +124,14 @@ class ApiServersServerTasksTaskIndexHandler(BaseApiHandler): 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.SCHEDULE - not in self.controller.server_perms.get_user_id_permissions_list( + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( auth_data[4]["user_id"], server_id - ) - ): + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) + if EnumPermissionsServer.SCHEDULE not in server_permissions: # if the user doesn't have Schedule permission, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) diff --git a/app/classes/web/routes/api/servers/server/webhooks/index.py b/app/classes/web/routes/api/servers/server/webhooks/index.py index 223171c8..2557c309 100644 --- a/app/classes/web/routes/api/servers/server/webhooks/index.py +++ b/app/classes/web/routes/api/servers/server/webhooks/index.py @@ -38,12 +38,14 @@ class ApiServersServerWebhooksIndexHandler(BaseApiHandler): auth_data = self.authenticate_user() if not auth_data: return - if ( - EnumPermissionsServer.CONFIG - not in self.controller.server_perms.get_user_id_permissions_list( + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( auth_data[4]["user_id"], server_id - ) - ): + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) + if EnumPermissionsServer.CONFIG not in server_permissions: # if the user doesn't have Schedule permission, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) self.finish_json( @@ -81,13 +83,14 @@ class ApiServersServerWebhooksIndexHandler(BaseApiHandler): 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.CONFIG - not in self.controller.server_perms.get_user_id_permissions_list( + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( auth_data[4]["user_id"], server_id - ) - ): + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) + if EnumPermissionsServer.CONFIG not in server_permissions: # if the user doesn't have Schedule permission, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) data["server_id"] = server_id diff --git a/app/classes/web/routes/api/servers/server/webhooks/webhook/index.py b/app/classes/web/routes/api/servers/server/webhooks/webhook/index.py index 4b58011e..c94aa975 100644 --- a/app/classes/web/routes/api/servers/server/webhooks/webhook/index.py +++ b/app/classes/web/routes/api/servers/server/webhooks/webhook/index.py @@ -39,12 +39,14 @@ class ApiServersServerWebhooksManagementIndexHandler(BaseApiHandler): auth_data = self.authenticate_user() if not auth_data: return - if ( - EnumPermissionsServer.CONFIG - not in self.controller.server_perms.get_user_id_permissions_list( + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( auth_data[4]["user_id"], server_id - ) - ): + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) + if EnumPermissionsServer.CONFIG not in server_permissions: # if the user doesn't have Schedule permission, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) if ( @@ -66,12 +68,14 @@ class ApiServersServerWebhooksManagementIndexHandler(BaseApiHandler): auth_data = self.authenticate_user() if not auth_data: return - if ( - EnumPermissionsServer.CONFIG - not in self.controller.server_perms.get_user_id_permissions_list( + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( auth_data[4]["user_id"], server_id - ) - ): + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) + if EnumPermissionsServer.CONFIG not in server_permissions: # if the user doesn't have Schedule permission, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) @@ -117,13 +121,14 @@ class ApiServersServerWebhooksManagementIndexHandler(BaseApiHandler): 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.CONFIG - not in self.controller.server_perms.get_user_id_permissions_list( + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( auth_data[4]["user_id"], server_id - ) - ): + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) + if EnumPermissionsServer.CONFIG not in server_permissions: # if the user doesn't have Schedule permission, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) @@ -159,13 +164,14 @@ class ApiServersServerWebhooksManagementIndexHandler(BaseApiHandler): 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.CONFIG - not in self.controller.server_perms.get_user_id_permissions_list( + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( auth_data[4]["user_id"], server_id - ) - ): + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) + if EnumPermissionsServer.CONFIG not in server_permissions: # if the user doesn't have Schedule permission, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) webhook = self.controller.management.get_webhook_by_id(webhook_id) diff --git a/app/classes/web/routes/api/users/index.py b/app/classes/web/routes/api/users/index.py index fef154a0..dbdb1ac0 100644 --- a/app/classes/web/routes/api/users/index.py +++ b/app/classes/web/routes/api/users/index.py @@ -21,6 +21,7 @@ class ApiUsersIndexHandler(BaseApiHandler): _, _, user, + _, ) = auth_data # GET /api/v2/users?ids=true @@ -70,6 +71,7 @@ class ApiUsersIndexHandler(BaseApiHandler): _, superuser, user, + _, ) = auth_data if EnumPermissionsCrafty.USER_CONFIG not in exec_user_crafty_permissions: @@ -149,11 +151,12 @@ class ApiUsersIndexHandler(BaseApiHandler): 400, {"status": "error", "error": "INVALID_SUPERUSER_CREATE"} ) - if len(roles) != 0 and not superuser: - # HACK: This should check if the user has the roles or something - return self.finish_json( - 400, {"status": "error", "error": "INVALID_ROLES_CREATE"} - ) + for role in roles: + role = self.controller.roles.get_role(role) + if int(role["manager"]) != int(auth_data[4]["user_id"]) and not superuser: + return self.finish_json( + 400, {"status": "error", "error": "INVALID_ROLES_CREATE"} + ) # TODO: do this in the most efficient way user_id = self.controller.users.add_user( diff --git a/app/classes/web/routes/api/users/user/api.py b/app/classes/web/routes/api/users/user/api.py index 9bdafadf..3891ef83 100644 --- a/app/classes/web/routes/api/users/user/api.py +++ b/app/classes/web/routes/api/users/user/api.py @@ -75,7 +75,7 @@ class ApiUsersUserKeyHandler(BaseApiHandler): "name": key.name, "server_permissions": key.server_permissions, "crafty_permissions": key.crafty_permissions, - "superuser": key.superuser, + "full_access": key.full_access, } ) self.finish_json( @@ -99,7 +99,7 @@ class ApiUsersUserKeyHandler(BaseApiHandler): "type": "string", "pattern": "^[01]{3}$", # 8 bits, see EnumPermissionsCrafty }, - "superuser": {"type": "boolean"}, + "full_access": {"type": "boolean"}, }, "additionalProperties": False, "minProperties": 1, @@ -113,6 +113,7 @@ class ApiUsersUserKeyHandler(BaseApiHandler): _, _superuser, user, + _, ) = auth_data try: @@ -163,7 +164,7 @@ class ApiUsersUserKeyHandler(BaseApiHandler): key_id = self.controller.users.add_user_api_key( data["name"], user_id, - data["superuser"], + data["full_access"], data["server_permissions_mask"], data["crafty_permissions_mask"], ) @@ -188,6 +189,7 @@ class ApiUsersUserKeyHandler(BaseApiHandler): _, _, _user, + _, ) = auth_data if key_id: key = self.controller.users.get_user_api_key(key_id) diff --git a/app/classes/web/routes/api/users/user/index.py b/app/classes/web/routes/api/users/user/index.py index 6efee93e..9fa46200 100644 --- a/app/classes/web/routes/api/users/user/index.py +++ b/app/classes/web/routes/api/users/user/index.py @@ -24,6 +24,7 @@ class ApiUsersUserIndexHandler(BaseApiHandler): _, _, user, + _, ) = auth_data if user_id in ["@me", user["user_id"]]: @@ -72,6 +73,7 @@ class ApiUsersUserIndexHandler(BaseApiHandler): _, _, user, + _, ) = auth_data if (user_id in ["@me", user["user_id"]]) and self.helper.get_setting( @@ -121,6 +123,7 @@ class ApiUsersUserIndexHandler(BaseApiHandler): _, superuser, user, + _, ) = auth_data try: diff --git a/app/classes/web/routes/api/users/user/permissions.py b/app/classes/web/routes/api/users/user/permissions.py index 5981eaf4..d0f496f2 100644 --- a/app/classes/web/routes/api/users/user/permissions.py +++ b/app/classes/web/routes/api/users/user/permissions.py @@ -27,6 +27,7 @@ class ApiUsersUserPermissionsHandler(BaseApiHandler): _, _, user, + _, ) = auth_data if user_id in ["@me", user["user_id"]]: diff --git a/app/classes/web/routes/api/users/user/public.py b/app/classes/web/routes/api/users/user/public.py index b67ab61e..e016babc 100644 --- a/app/classes/web/routes/api/users/user/public.py +++ b/app/classes/web/routes/api/users/user/public.py @@ -17,6 +17,7 @@ class ApiUsersUserPublicHandler(BaseApiHandler): _, _, user, + _, ) = auth_data if user_id == "@me": diff --git a/app/classes/web/server_handler.py b/app/classes/web/server_handler.py index 62b76f3c..a8e278b2 100644 --- a/app/classes/web/server_handler.py +++ b/app/classes/web/server_handler.py @@ -30,7 +30,7 @@ class ServerHandler(BaseHandler): ) = self.current_user superuser = exec_user["superuser"] if api_key is not None: - superuser = superuser and api_key.superuser + superuser = superuser and api_key.full_access if superuser: defined_servers = self.controller.servers.list_defined_servers() @@ -124,7 +124,7 @@ class ServerHandler(BaseHandler): "created": api_key.created, "server_permissions": api_key.server_permissions, "crafty_permissions": api_key.crafty_permissions, - "superuser": api_key.superuser, + "full_access": api_key.full_access, } if api_key is not None else None @@ -146,12 +146,12 @@ class ServerHandler(BaseHandler): return page_data["server_api"] = False if page_data["online"]: - page_data["server_api"] = self.helper.check_address_status( - "https://api.serverjars.com" + page_data["server_api"] = ( + self.controller.big_bucket._check_bucket_alive() ) - page_data["server_types"] = self.controller.server_jars.get_serverjar_data() + page_data["server_types"] = self.controller.big_bucket.get_bucket_data() page_data["js_server_types"] = json.dumps( - self.controller.server_jars.get_serverjar_data() + self.controller.big_bucket.get_bucket_data() ) if page_data["server_types"] is None: page_data["server_types"] = [] diff --git a/app/classes/web/upload_handler.py b/app/classes/web/upload_handler.py index 0667dd12..747fa63b 100644 --- a/app/classes/web/upload_handler.py +++ b/app/classes/web/upload_handler.py @@ -42,7 +42,7 @@ class UploadHandler(BaseHandler): if self.upload_type == "server_import": superuser = exec_user["superuser"] if api_key is not None: - superuser = superuser and api_key.superuser + superuser = superuser and api_key.full_access user_id = exec_user["user_id"] stream_size_value = self.helper.get_setting("stream_size_GB") @@ -133,7 +133,7 @@ class UploadHandler(BaseHandler): elif self.upload_type == "background": superuser = exec_user["superuser"] if api_key is not None: - superuser = superuser and api_key.superuser + superuser = superuser and api_key.full_access user_id = exec_user["user_id"] stream_size_value = self.helper.get_setting("stream_size_GB") @@ -212,7 +212,7 @@ class UploadHandler(BaseHandler): server_id = self.get_argument("server_id", None) superuser = exec_user["superuser"] if api_key is not None: - superuser = superuser and api_key.superuser + superuser = superuser and api_key.full_access user_id = exec_user["user_id"] stream_size_value = self.helper.get_setting("stream_size_GB") diff --git a/app/config/logging.json b/app/config/logging.json index fd1173eb..d0a20cdf 100644 --- a/app/config/logging.json +++ b/app/config/logging.json @@ -14,6 +14,9 @@ "auth": { "format": "%(asctime)s - [AUTH] - %(levelname)s - %(message)s" }, + "audit": { + "()": "app.classes.logging.log_formatter.JsonFormatter" + }, "cmd_queue": { "format": "%(asctime)s - [CMD_QUEUE] - %(levelname)s - %(message)s" } @@ -70,6 +73,14 @@ "maxBytes": 10485760, "backupCount": 20, "encoding": "utf8" + }, + "audit_log_handler": { + "class": "logging.handlers.RotatingFileHandler", + "formatter": "audit", + "filename": "logs/audit.log", + "maxBytes": 10485760, + "backupCount": 20, + "encoding": "utf8" } }, "loggers": { @@ -108,6 +119,12 @@ "cmd_queue_file_handler" ], "propagate": false + }, + "audit_log": { + "level": "INFO", + "handlers": [ + "audit_log_handler" + ] } } } \ No newline at end of file diff --git a/app/config/version.json b/app/config/version.json index 22de834f..c8db4444 100644 --- a/app/config/version.json +++ b/app/config/version.json @@ -1,5 +1,5 @@ { "major": 4, - "minor": 3, - "sub": 3 + "minor": 4, + "sub": 1 } diff --git a/app/frontend/static/assets/images/serverjars/FULL-WHITE.svg b/app/frontend/static/assets/images/serverjars/FULL-WHITE.svg deleted file mode 100644 index d5036723..00000000 --- a/app/frontend/static/assets/images/serverjars/FULL-WHITE.svg +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/frontend/static/assets/images/serverjars/ICON.svg b/app/frontend/static/assets/images/serverjars/ICON.svg deleted file mode 100644 index 2adc4cff..00000000 --- a/app/frontend/static/assets/images/serverjars/ICON.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/frontend/templates/panel/activity_logs.html b/app/frontend/templates/panel/activity_logs.html index 389edaf5..e053e50e 100644 --- a/app/frontend/templates/panel/activity_logs.html +++ b/app/frontend/templates/panel/activity_logs.html @@ -36,25 +36,21 @@ - - - - - + + + + + - {% for row in data['audit_logs'] %} - - - - - - {% end %}
UsernameTimeActionServer IDIPTimeUsernameActionServer IDIP
{{ row['user_name'] }} - {{ row['created'].strftime('%Y-%m-%d %H:%M:%S') }} + + Crafty Logo, Crafty is loading

{{ translate('datatables', + 'loadingRecords', data['lang'])}}
{{ row['log_msg'] }}{{ row['server_id'] }}{{ row['source_ip'] }}
@@ -79,17 +75,6 @@ {% end %} {% block js %} - {% end %} \ No newline at end of file diff --git a/app/frontend/templates/panel/loading.html b/app/frontend/templates/panel/loading.html index a56a75ee..15396071 100644 --- a/app/frontend/templates/panel/loading.html +++ b/app/frontend/templates/panel/loading.html @@ -20,7 +20,8 @@ data-internet="{{ translate('startup', 'internet', data['lang']) }}" data-tasks="{{ translate('startup', 'tasks', data['lang']) }}" data-internals="{{ translate('startup', 'internals', data['lang']) }}" - data-almost="{{ translate('startup', 'almost', data['lang']) }}"> + data-almost="{{ translate('startup', 'almost', data['lang']) }}" + data-cache="{{ translate('startup', 'cache', data['lang'])}}"> {{ translate('startup', 'starting', data['lang']) }} @@ -70,4 +71,4 @@ }); -{% end %} +{% end %} \ No newline at end of file diff --git a/app/frontend/templates/panel/panel_edit_user_apikeys.html b/app/frontend/templates/panel/panel_edit_user_apikeys.html index 084db0c3..76fa780b 100644 --- a/app/frontend/templates/panel/panel_edit_user_apikeys.html +++ b/app/frontend/templates/panel/panel_edit_user_apikeys.html @@ -58,7 +58,7 @@ {{ translate('apiKeys', 'name', data['lang']) }} {{ translate('apiKeys', 'created', data['lang']) }} - {{ translate('apiKeys', 'superUser', data['lang']) }} + {{ translate('apiKeys', 'fullAccess', data['lang']) }} {{ translate('apiKeys', 'perms', data['lang']) }} {{ translate('apiKeys', 'buttons', data['lang']) }} @@ -70,7 +70,7 @@ {{ apikey.name }} {{ apikey.created.strftime('%d/%m/%Y %H:%M:%S') }} - {% if apikey.superuser %} + {% if apikey.full_access %} {{ translate('apiKeys', 'yes', data['lang']) }} @@ -148,9 +148,15 @@ }} + {% if permission in data['user_crafty_permissions'] %} + {% else %} + + {% end %} {% end %} @@ -158,8 +164,8 @@ - - + +
@@ -240,7 +246,7 @@ "name": formDataObject.name, "server_permissions_mask": server_permissions, "crafty_permissions_mask": crafty_permissions, - "superuser": $("#superuser").prop('checked'), + "full_access": $("#full_access").prop('checked'), }); console.log(formDataJsonString); diff --git a/app/frontend/templates/public/error.html b/app/frontend/templates/public/error.html index f51d1dc9..535c1205 100644 --- a/app/frontend/templates/public/error.html +++ b/app/frontend/templates/public/error.html @@ -1,5 +1,6 @@ - + @@ -60,6 +61,11 @@ {{ translate('error', 'hereIsTheError', data['lang']) }}: {{data['error']}}

That's all the help I can give you - Godspeed

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

diff --git a/app/frontend/templates/public/login.html b/app/frontend/templates/public/login.html index 275d2000..9c7d3f91 100644 --- a/app/frontend/templates/public/login.html +++ b/app/frontend/templates/public/login.html @@ -171,7 +171,6 @@ //Create an object from the form data entries let formDataObject = Object.fromEntries(formData.entries()); - console.log(formDataObject) let res = await fetch(`/login`, { method: 'POST', headers: { diff --git a/app/frontend/templates/server/wizard.html b/app/frontend/templates/server/wizard.html index d6d64da3..fd079343 100644 --- a/app/frontend/templates/server/wizard.html +++ b/app/frontend/templates/server/wizard.html @@ -20,11 +20,6 @@
- {% if data["server_api"] and data["online"] %} - - {% end %}

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


@@ -67,16 +62,19 @@ {% end %} {% raw xsrf_form_html() %}
+
- +
- + {% for s in data['server_types'] %} - + {% if data['server_types'][s].get("enabled", False) %} + {% end %} {% if data['super_user'] %} @@ -108,7 +106,8 @@
+ data-content="⚠️ {{ translate('serverWizard', 'unsupported', data['lang']) }} ⚠️" , + data-placement="right">
@@ -191,10 +190,12 @@ {% if not data["server_api"] and data["online"] %}

 {{ - translate('error', 'serverJars1', data['lang']) }} {{ translate('error', + translate('error', 'bigBucket1', data['lang']) }} {{ + translate('error', 'craftyStatus', data['lang']) }} -  {{ translate('error', 'serverJars2', data['lang']) }}

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

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

{% end %} {% if not data["online"] %} @@ -827,7 +828,7 @@ message: '

Please wait while we gather your files...

', closeButton: false }); - setTimeout(function(){ + setTimeout(function () { getDirView(); }, 2000); } else { @@ -845,9 +846,9 @@ message: '

Please wait while we gather your files...

', closeButton: false }); - setTimeout(function(){ - getDirView(); - }, 2000); + setTimeout(function () { + getDirView(); + }, 2000); }); var upload = false; var file; @@ -1137,7 +1138,7 @@ function wait_msg(importing) { bootbox.alert({ title: importing ? '{% raw translate("serverWizard", "importing", data["lang"]) %}' : '{% raw translate("serverWizard", "downloading", data["lang"]) %}', - message: importing ? ' {% raw translate("serverWizard", "bePatient", data["lang"]) %}' : ' {% raw translate("serverWizard", "bePatient", data["lang"]) %}

Powered by serverjars.com', + message: importing ? ' {% raw translate("serverWizard", "bePatient", data["lang"]) %}' : ' {% raw translate("serverWizard", "bePatient", data["lang"]) %}', }); } @@ -1197,33 +1198,28 @@ */ function serverTypeChange(selectObj) { // get the index of the selected option - let idx = document.getElementById('server_type').selectedIndex; + let idx = document.getElementById('server_type').value; + let idx_list = idx.split("|"); // get the value of the selected option let cSelect = document.getElementById("server"); let which = {}; try { - which = document.getElementById('server_type').options[idx].value; + versions = Object.keys(serverTypesLists[idx_list[0]]["types"][idx_list[1]]["versions"]); } catch { while (cSelect.options.length > 0) { cSelect.remove(0); } return; } - let server_type = which.split('|')[0]; - let server = which.split('|')[1]; - // use the selected option value to retrieve the list of items from the serverTypesLists array - let cList = serverTypesLists[server_type]; - // get the country select element via its known id - cSelect = document.getElementById("server"); - // remove the current options from the country select - while (cSelect.options.length > 0) { - cSelect.remove(0); - } - let newOption; + + $("#server option").each(function () { + $(this).remove() + }) + // create new options ordered by ascending - cList[server].forEach(type => { + versions.forEach(type => { newOption = document.createElement("option"); - newOption.value = which + "|" + type; // assumes option string and value are the same + newOption.value = idx + "|" + type; // assumes option string and value are the same newOption.text = type; // add the new option try { @@ -1234,17 +1230,18 @@ } }) } - $("#server").change(function (){ + + $("#server").change(function () { let selected_version = $("#server :selected").text().split("."); - if(parseInt(selected_version[0]) === 1 && parseInt(selected_version[1]) < 8 ){ + if (parseInt(selected_version[0]) === 1 && parseInt(selected_version[1]) < 8) { $('[data-toggle="popover"]').popover(); - if ($(window).width() < 1000) { - $('.version-hint').attr("data-placement", "top") + if ($(window).width() < 1000) { + $('.version-hint').attr("data-placement", "top") + } else { + $('.version-hint').attr("data-placement", "right") + } + $('.version-hint').popover("show"); } else { - $('.version-hint').attr("data-placement", "right") - } - $('.version-hint').popover("show"); - }else{ $('.version-hint').popover("hide"); } }); @@ -1253,6 +1250,15 @@ const type_select = document.getElementById('server_jar') const tidx = type_select.selectedIndex; const val = type_select.options[tidx].value; + if (!val) { + $("#server_type option").each(function () { + $(this).remove() + }) + $("#server option").each(function () { + $(this).remove() + }) + return; + } let jcSelect = {}; if (val == 'None') { jcSelect = document.getElementById("server_type"); @@ -1267,7 +1273,7 @@ // get the value of the selected option let jwhich = selectObj.options[jidx].value; // use the selected option value to retrieve the list of items from the serverTypesLists array - let jcList = Object.keys(serverTypesLists[jwhich]); + let jcList = Object.keys(serverTypesLists[jwhich]["types"]); // get the country select element via its known id jcSelect = document.getElementById("server_type"); // remove the current options from the country select diff --git a/app/migrations/20240216_rework_servers_uuid.py b/app/migrations/20240216_rework_servers_uuid.py index facd0e42..7850346d 100644 --- a/app/migrations/20240216_rework_servers_uuid.py +++ b/app/migrations/20240216_rework_servers_uuid.py @@ -6,7 +6,6 @@ import logging from app.classes.shared.console import Console from app.classes.shared.migration import Migrator, MigrateHistory from app.classes.models.management import ( - AuditLog, Webhooks, Schedules, Backups, @@ -61,17 +60,6 @@ def migrate(migrator: Migrator, database, **kwargs): peewee.CharField(primary_key=True, default=str(uuid.uuid4())), ) - # Changes on Audit Log Table - migrator.alter_column_type( - AuditLog, - "server_id", - peewee.ForeignKeyField( - Servers, - backref="audit_server", - null=True, - field=peewee.CharField(primary_key=True, default=str(uuid.uuid4())), - ), - ) # Changes on Webhook Table migrator.alter_column_type( Webhooks, @@ -109,13 +97,6 @@ def rollback(migrator: Migrator, database, **kwargs): peewee.AutoField(), ) - # Changes on Audit Log Table - migrator.alter_column_type( - AuditLog, - "server_id", - peewee.IntegerField(default=None, index=True), - ) - # Changes on Webhook Table migrator.alter_column_type( Webhooks, diff --git a/app/migrations/20240217_rework_servers_uuid_part2.py b/app/migrations/20240217_rework_servers_uuid_part2.py index eab04c28..a4b01c45 100644 --- a/app/migrations/20240217_rework_servers_uuid_part2.py +++ b/app/migrations/20240217_rework_servers_uuid_part2.py @@ -6,7 +6,6 @@ import logging from app.classes.shared.console import Console from app.classes.shared.migration import Migrator, MigrateHistory from app.classes.models.management import ( - AuditLog, Webhooks, Schedules, Backups, @@ -73,20 +72,6 @@ def migrate(migrator: Migrator, database, **kwargs): try: logger.info("Migrating Data from Int to UUID (Foreign Keys)") Console.info("Migrating Data from Int to UUID (Foreign Keys)") - # Changes on Audit Log Table - for audit_log in AuditLog.select(): - old_server_id = audit_log.server_id_id - if old_server_id == "0" or old_server_id is None: - server_uuid = None - else: - try: - server = Servers.get_by_id(old_server_id) - server_uuid = server.server_uuid - except: - server_uuid = old_server_id - AuditLog.update(server_id=server_uuid).where( - AuditLog.audit_id == audit_log.audit_id - ).execute() # Changes on Webhooks Log Table for webhook in Webhooks.select(): @@ -247,21 +232,6 @@ def rollback(migrator: Migrator, database, **kwargs): try: logger.info("Migrating Data from UUID to Int (Foreign Keys)") Console.info("Migrating Data from UUID to Int (Foreign Keys)") - # Changes on Audit Log Table - for audit_log in AuditLog.select(): - old_server_id = audit_log.server_id_id - if old_server_id is None: - new_server_id = 0 - else: - try: - server = Servers.get_or_none(Servers.server_uuid == old_server_id) - new_server_id = server.server_id - except: - new_server_id = old_server_id - AuditLog.update(server_id=new_server_id).where( - AuditLog.audit_id == audit_log.audit_id - ).execute() - # Changes on Webhooks Log Table for webhook in Webhooks.select(): old_server_id = webhook.server_id_id diff --git a/app/migrations/20240317_apikey_full_access.py b/app/migrations/20240317_apikey_full_access.py new file mode 100644 index 00000000..6ae223e1 --- /dev/null +++ b/app/migrations/20240317_apikey_full_access.py @@ -0,0 +1,17 @@ +# Generated by database migrator +import peewee + + +def migrate(migrator, database, **kwargs): + migrator.rename_column("api_keys", "superuser", "full_access") + + """ + Write your migrations here. + """ + + +def rollback(migrator, database, **kwargs): + migrator.rename_column("api_keys", "full_access", "superuser") + """ + Write your rollback migrations here. + """ diff --git a/app/migrations/20240420_audit_log_drop.py b/app/migrations/20240420_audit_log_drop.py new file mode 100644 index 00000000..be153f11 --- /dev/null +++ b/app/migrations/20240420_audit_log_drop.py @@ -0,0 +1,34 @@ +import peewee +import datetime +from peewee import ( + AutoField, + DateTimeField, + CharField, + IntegerField, + ForeignKeyField, + TextField, +) + +from app.classes.shared.server import Servers + + +def migrate(migrator, db): + migrator.drop_table("audit_log") + + +def rollback(migrator, db): + class AuditLog(peewee.Model): + audit_id = AutoField() + created = DateTimeField(default=datetime.datetime.now) + user_name = CharField(default="") + user_id = IntegerField(default=0, index=True) + source_ip = CharField(default="127.0.0.1") + server_id = ForeignKeyField( + Servers, backref="audit_server", null=True + ) # When auditing global events, use server ID null + log_msg = TextField(default="") + + class Meta: + table_name = "audit_log" + + migrator.create_table(AuditLog) diff --git a/app/translations/cs_CS.json b/app/translations/cs_CS.json index 2229d5cb..4d496e2a 100644 --- a/app/translations/cs_CS.json +++ b/app/translations/cs_CS.json @@ -20,6 +20,7 @@ "created": "Vytvořen", "deleteKeyConfirmation": "Chcete tento API klíč odstranit? Tuto akci nelze vrátit zpět.", "deleteKeyConfirmationTitle": "Odstranit klíč API ${keyId}?", + "fullAccess": "všechno", "getToken": "Získat token", "name": "Jméno", "nameDesc": "Jak chcete nazvat tento token API? ", @@ -192,11 +193,14 @@ }, "thousands": " ", "zeroRecords": "Nebyly nalezeny žádné odpovídající záznamy" - } + }, + "loadingRecords": "Načítání..." }, "error": { "agree": "Souhlasím", "bedrockError": "Stažení Bedrocku není dostupné. Prosím zkontrolujte", + "bigBucket1": "Big Bucket Kontrola stavu selhala. Prosím zkontrolujte jej", + "bigBucket2": "pro nejnovější informace.", "cancel": "Zrušit", "contact": "Kontaktujte podporu Crafty přes Discord", "craftyStatus": "Crafty stav systémů", @@ -218,6 +222,8 @@ "not-downloaded": "Zdá se, že nemůžeme najít váš spustitelný soubor. Bylo jeho stahování dokončeno? Jsou oprávnění nastavena na spustitelný soubor?", "portReminder": "Zjistili jsme, že server {} byl spuštěn poprvé. Ujistěte se, že jste přesměrovali port {} přes váš směrovač/firewall, aby byl tento port vzdáleně přístupný z internetu.", "privMsg": "a ", + "return": "vrátit se na hlavní stránku", + "selfHost": "Pokud Hostujete sami toto uložiště prosím zkontrolujte adresu nebo si přečtěte náš průvodce odstraňováním problémů.", "serverJars1": "Server JAR api je nepřístupná. Prosím zkontrolujte", "serverJars2": "pro aktualní informace.", "start-error": "Server {} se nepodařilo spustit s kódem chyby: {}", @@ -612,13 +618,14 @@ "credits": "Zásluhy", "dashboard": "Ovládací panel", "documentation": "Dokumentace", - "inApp": "V app dokumentaci", + "inApp": "V lokalní dokumentaci", "navigation": "Navigace", "newServer": "Vytvořit nový server", "servers": "Servery" }, "startup": { "almost": "Dokončuji. Držte se...", + "cache": "Znovu načítam mezipaměť Big Bucket", "internals": "Nastavuji a startuji Crafty interní komponenty", "internet": "Kontroluju připojení k internetu", "server": "Konfigurace ", diff --git a/app/translations/de_DE.json b/app/translations/de_DE.json index c9ffea15..adeb7f4c 100644 --- a/app/translations/de_DE.json +++ b/app/translations/de_DE.json @@ -20,6 +20,7 @@ "created": "Erstellt", "deleteKeyConfirmation": "Möchten Sie diesen API Schlüssel löschen? Diese Aktion kann nicht rückgängig gemacht werden.", "deleteKeyConfirmationTitle": "Folgenden API Schlüssel löschen: ${keyId}?", + "fullAccess": "Vollzugriff", "getToken": "Schlüssel erhalten", "name": "Name", "nameDesc": "Wie soll der API Schlüssel genannt werden? ", @@ -177,11 +178,14 @@ }, "thousands": ".", "zeroRecords": "Keine passenden Einträge gefunden" - } + }, + "loadingRecords": "Laden..." }, "error": { "agree": "Zustimmen", "bedrockError": "Bedrock-Downloads sind nicht verfügbar. Bitte überprüfen Sie", + "bigBucket1": "Big Bucket Zustandsprüfung fehlgeschlagen. Bitte Überprüfen", + "bigBucket2": "für die aktuellsten Informationen.", "cancel": "Abbrechen", "contact": "Kontaktieren Sie den Crafty Control Support über Discord", "craftyStatus": "Crafty-Statusseite", @@ -203,6 +207,8 @@ "not-downloaded": "Crafty kann die auszuführende Datei nicht finden. Ist der Download abgeschlossen? Sind die Berechtigungen für Crafty korrekt?", "portReminder": "Wir haben festgestellt, dass dies das erste Mal ist, dass {} ausgeführt wurde. Stellen Sie sicher, dass Sie Port {} durch Ihren Router/Firewall weiterleiten, um den Fernzugriff aus dem Internet zu ermöglichen.", "privMsg": "und der/die/das ", + "return": "Zurück zum Dashboard", + "selfHost": "Wenn Sie dieses Repo selbst hosten, überprüfen Sie bitte Ihre Adresse oder konsultieren Sie unsere Anleitung zur Fehlerbehebung.", "serverJars1": "Server-JAR-API nicht erreichbar. Bitte überprüfen Sie ", "serverJars2": "um die aktuellsten Informationen zu erhalten.", "start-error": "Der Server {} konnte wegen dem Fehlercode: {} nicht gestartet werden", @@ -600,6 +606,7 @@ }, "startup": { "almost": "Nur noch einen Moment, fast geschafft", + "cache": "Aktualisieren der Big Bucket-Cache-Datei", "internals": "Crafty's interne Komponneten initialisieren und starten", "internet": "Verbindung zum Internet überprüfen", "server": "initialisieren ", diff --git a/app/translations/en_EN.json b/app/translations/en_EN.json index 7425208f..47553b0f 100644 --- a/app/translations/en_EN.json +++ b/app/translations/en_EN.json @@ -20,6 +20,7 @@ "created": "Created", "deleteKeyConfirmation": "Do you want to delete this API key? This cannot be undone.", "deleteKeyConfirmationTitle": "Remove API key ${keyId}?", + "fullAccess": "Full Access", "getToken": "Get A Token", "name": "Name", "nameDesc": "What would you like to call this API token? ", @@ -28,7 +29,6 @@ "permName": "Permission Name", "perms": "Permissions", "server": "Server: ", - "superUser": "Super User", "yes": "Yes" }, "base": { @@ -177,11 +177,14 @@ }, "thousands": ",", "zeroRecords": "No matching records found" - } + }, + "loadingRecords": "Loading..." }, "error": { "agree": "Agree", "bedrockError": "Bedrock downloads unavailable. Please check", + "bigBucket1": "Big Bucket Health Check Failed. Please check", + "bigBucket2": "for the most up to date information.", "cancel": "Cancel", "contact": "Contact Crafty Control Support via Discord", "craftyStatus": "Crafty's status page", @@ -203,8 +206,8 @@ "not-downloaded": "We can't seem to find your executable file. Has it finished downloading? Are the permissions set to executable?", "portReminder": "We have detected this is the first time {} has been run. Make sure to forward port {} through your router/firewall to make this remotely accessible from the internet.", "privMsg": "and the ", - "serverJars1": "Server JARs API unreachable. Please check", - "serverJars2": "for the most up to date information.", + "return": "Return to Dashboard", + "selfHost": "If you are self-hosting this repo please check your address or consult our troubleshooting guide.", "start-error": "Server {} failed to start with error code: {}", "superError": "You must be a super user to complete this action.", "terribleFailure": "What a Terrible Failure!" @@ -607,6 +610,7 @@ }, "startup": { "almost": "Finishing up. Hang on tight...", + "cache": "Refreshing Big Bucket cache file", "internals": "Configuring and starting Crafty's internal components", "internet": "Checking for internet connection", "server": "Initializing ", diff --git a/app/translations/es_ES.json b/app/translations/es_ES.json index 5f750fc7..bc9f7adb 100644 --- a/app/translations/es_ES.json +++ b/app/translations/es_ES.json @@ -20,6 +20,7 @@ "created": "Creado", "deleteKeyConfirmation": "¿Quieres eliminar esta clave de API? Esto no se puede deshacer.", "deleteKeyConfirmationTitle": "¿Eliminar la clave API ${keyId}?", + "fullAccess": "Acceso completo", "getToken": "Conseguir un Token", "name": "Nombre", "nameDesc": "¿Como te gustaría llamar a este Token de API? ", @@ -177,11 +178,14 @@ }, "thousands": ",", "zeroRecords": "No se encontraron registros que coincidan" - } + }, + "loadingRecords": "Cargando..." }, "error": { "agree": "Aceptar", "bedrockError": "Descargas de Bedrock no disponibles. por favor, compruebe", + "bigBucket1": "La verificación de estado de Big Bucket ha fallado. Por favor, verifica", + "bigBucket2": "para obtener la información más actualizada.", "cancel": "Cancelar", "contact": "Contacta el soporte de Crafty Control desde Discord", "craftyStatus": "Página de estados de Crafty", @@ -203,6 +207,8 @@ "not-downloaded": "No podemos encontrar el archivo ejecutable. ¿Ha terminado de descargarse? ¿Están los permisos puestos como ejecutable?", "portReminder": "Detectamos que es la primera vez que se inicia {}. Asegúrese de configurar el puerto {} a través de su router/firewall para hacer el servidor accesible por Internet.", "privMsg": "y el ", + "return": "Volver al panel de control", + "selfHost": "Si estás autoalojando este repositorio, revisa tu dirección o consulta nuestra guía de solución de problemas.", "serverJars1": "API de Servidor JAR no disponible. por favor, compruebe", "serverJars2": "para la información más actualizada.", "start-error": "Servidor {} fallo al iniciar con código de error: {}", @@ -600,6 +606,7 @@ }, "startup": { "almost": "Terminando. Espera un momento...", + "cache": "Actualizando el archivo de caché de Big Bucket", "internals": "Configurando e inicializando los componentes internos de Crafty", "internet": "Verificando conexion a internet", "server": "Inicializando ", diff --git a/app/translations/fi_FI_incomplete.json b/app/translations/fi_FI_incomplete.json index 55cd990c..8d6168a6 100644 --- a/app/translations/fi_FI_incomplete.json +++ b/app/translations/fi_FI_incomplete.json @@ -100,6 +100,7 @@ "welcome": "Tervetuloa Crafty Controller" }, "datatables": { + "loadingRecords": "Ladataan...", "i18n": { "aria": { "sortAscending": ": lajittele sarake nousevasti", @@ -560,4 +561,4 @@ "userSettings": "Käyttäjäasetukset", "uses": "Sallittujen käyttäkertojen määtä (-1 == Ei rajaa)" } -} +} \ No newline at end of file diff --git a/app/translations/fr_FR.json b/app/translations/fr_FR.json index be429c83..6e1e4ad5 100644 --- a/app/translations/fr_FR.json +++ b/app/translations/fr_FR.json @@ -20,6 +20,7 @@ "created": "Crée", "deleteKeyConfirmation": "Es-tu sûr de vouloir supprimer cette clé API? Tu ne pourras plus revenir en arrière.", "deleteKeyConfirmationTitle": "Supprimer la clé API ${keyId}?", + "fullAccess": "Accès Complet", "getToken": "Obtenir un Jeton", "name": "Nom", "nameDesc": "Comment appeler ce Jeton d'API ? ", @@ -177,11 +178,14 @@ }, "thousands": ",", "zeroRecords": "Aucun enregistrement correspondant trouvcé" - } + }, + "loadingRecords": "Chargement ..." }, "error": { "agree": "Agree", "bedrockError": "Téléchargement Bedrock non disponible. Merci de vérifier", + "bigBucket1": "Echec de vérification de l'état de Big Bucket. Veuillez vérifier", + "bigBucket2": " pour l'information la plus à jour.", "cancel": "Annuler", "contact": "Contacter le Support de Crafty Control via Discord", "craftyStatus": "Page de statut de Crafty", @@ -203,6 +207,8 @@ "not-downloaded": "Nous ne parvenons pas à trouver le fichier exécutable. A-t-il fini de Télécharger ? Les permissions permettent elles l'exécution ?", "portReminder": "Nous avons détecté que c'est la première fois que {} est exécuté. Assurez-vous de transférer le port {} via votre routeur/pare-feu pour le rendre accessible à distance depuis Internet.", "privMsg": "et le ", + "return": "Revenir au Tableau de Bord", + "selfHost": "Si vous hébergez vous-même ce repo, veuillez vérifier votre adresse et votre guide de dépannage.", "serverJars1": "l'API Server JARs est inaccessible. Merci de vérifier", "serverJars2": "pour les informations les plus à jour.", "start-error": "Le serveur {} n'a pas pu démarrer avec le code d'erreur : {}", @@ -600,6 +606,7 @@ }, "startup": { "almost": "Finalisation. Patienter ...", + "cache": "Mise à jour du fichier cache de Big Bucket", "internals": "Configuration et Démarrage des composants internes de Crafty", "internet": "Vérification de la connexion à Internet", "server": "Initialisation ", diff --git a/app/translations/fy_NL_incomplete.json b/app/translations/fy_NL_incomplete.json index 98785a2b..6c5d76e8 100644 --- a/app/translations/fy_NL_incomplete.json +++ b/app/translations/fy_NL_incomplete.json @@ -99,6 +99,7 @@ "welcome": "Wolkom by Crafty Controller" }, "datatables": { + "loadingRecords": "Laden...", "i18n": { "aria": { "sortAscending": ": aktivearje om kolom oprinnend te sortearjen", @@ -529,4 +530,4 @@ "userSettings": "Brûkersynstellingen", "uses": "Oantal gebrûk tastien (-1==Gjin limyt)" } -} +} \ No newline at end of file diff --git a/app/translations/he_IL.json b/app/translations/he_IL.json index 7e2b1403..865222cd 100644 --- a/app/translations/he_IL.json +++ b/app/translations/he_IL.json @@ -20,6 +20,7 @@ "created": "נוצר", "deleteKeyConfirmation": "האם ברצונך למחוק מפתח API זה? אי אפשר לבטל את זה.", "deleteKeyConfirmationTitle": "? ${keyId} API-להסיר את מפתח ה", + "fullAccess": "גישה מלאה להכל", "getToken": "קבלת אסימון", "name": "שם", "nameDesc": "הזה API-איך תרצו לקרוא לאסימון ה", @@ -177,7 +178,8 @@ }, "thousands": ",", "zeroRecords": "לא נמצאו תוצאות תואמות" - } + }, + "loadingRecords": "...טוען" }, "error": { "agree": "מסכים", @@ -203,6 +205,7 @@ "not-downloaded": "לא הצלחנו למצוא את קובץ ההפעלה שלך. האם זה סיים להוריד? האם ההרשאות מוגדרות בשביל הפעלה?", "portReminder": "זיהינו שזו הפעם הראשונה ש-{} מופעל. הקפידו להעביר את היציאה {} דרך הנתב/חומת האש שלכם כדי להפוך אותה לנגישה מרחוק מהאינטרנט.", "privMsg": "וה", + "return": "חזרה לפאנל", "serverJars1": "API של צנצנות השרת אינו נגיש. אנא בדוק", "serverJars2": "למידע מעודכן ביותר.", "start-error": "השרת {} לא הצליח להתחיל עם קוד שגיאה: {}", diff --git a/app/translations/hr_HR_incomplete.json b/app/translations/hr_HR_incomplete.json index 3f63770f..392ee098 100644 --- a/app/translations/hr_HR_incomplete.json +++ b/app/translations/hr_HR_incomplete.json @@ -99,6 +99,7 @@ "welcome": "Dobrodošli u Crafty Controller" }, "datatables": { + "loadingRecords": "Učitavanje...", "i18n": { "aria": { "sortAscending": ": aktiviraj za sortiranje stupca uzlazno", @@ -529,4 +530,4 @@ "userSettings": "Korisničke postavke", "uses": "Broj dopuštenih upotreba (-1==Bez ograničenja)" } -} +} \ No newline at end of file diff --git a/app/translations/id_ID_incomplete.json b/app/translations/id_ID_incomplete.json index cfec5557..c2e8813d 100644 --- a/app/translations/id_ID_incomplete.json +++ b/app/translations/id_ID_incomplete.json @@ -100,6 +100,7 @@ "welcome": "Selamat Datang Di Crafty Controller" }, "datatables": { + "loadingRecords": "Loading...", "i18n": { "aria": { "sortAscending": ": aktifkan untuk mengurutkan kolom menaik", @@ -536,4 +537,4 @@ "userSettings": "Pengaturan Pengguna", "uses": "Jumlah penggunaan yang diizinkan (-1==No Limit)" } -} +} \ No newline at end of file diff --git a/app/translations/it_IT.json b/app/translations/it_IT.json index adb59c7d..84b7e39a 100644 --- a/app/translations/it_IT.json +++ b/app/translations/it_IT.json @@ -20,6 +20,7 @@ "created": "Creato", "deleteKeyConfirmation": "Vuoi cancellare questa chiave API? Non puoi tornare indietro.", "deleteKeyConfirmationTitle": "Rimuovere la chiave API ${keyId}?", + "fullAccess": " Accesso completo", "getToken": "Prendi un Token", "name": "Nome", "nameDesc": "Come desideri chiamare questo Token API? ", @@ -177,11 +178,14 @@ }, "thousands": ",", "zeroRecords": "Nessun record corrispondente trovato" - } + }, + "loadingRecords": "Carico..." }, "error": { "agree": "Conferma", "bedrockError": "I download di Bedrock non sono disponibili. Si prega di controllare", + "bigBucket1": "Controllo integrità di Big Bucket fallito. Controlla nuovamente", + "bigBucket2": "per informazioni più aggiornate.", "cancel": "Annulla", "contact": "Contact Crafty Control Support via Discord", "craftyStatus": "Stato di Crafty", @@ -203,6 +207,8 @@ "not-downloaded": "We can't seem to find your executable file. Has it finished downloading? Are the permissions set to executable?", "portReminder": "We have detected this is the first time {} has been run. Make sure to forward port {} through your router/firewall to make this remotely accessible from the internet.", "privMsg": "e il ", + "return": "Torna alla pagina iniziale", + "selfHost": "se stai ospitando te questo repo, controlla il tuo indirizzo o consulta la nostra guida di risoluzione dei problemi.", "serverJars1": "API JAR del server non raggiungibile. Si prega di controllare", "serverJars2": "per informazioni più aggiornate.", "start-error": "Server {} failed to start with error code: {}", @@ -600,6 +606,7 @@ }, "startup": { "almost": "Finalizzazione. Tieniti forte...", + "cache": "Aggiornamento cache di Big Bucket", "internals": "Configurazione e avvio dei componenti interni di Crafty", "internet": "Controllo connessione a internet", "server": "Inizializzazione ", diff --git a/app/translations/lol_EN.json b/app/translations/lol_EN.json index aa07ff5f..387be725 100644 --- a/app/translations/lol_EN.json +++ b/app/translations/lol_EN.json @@ -20,6 +20,7 @@ "created": "CREATED", "deleteKeyConfirmation": "U SURE U WANTZ TO DELETE DIS? CAN'T UNDO!", "deleteKeyConfirmationTitle": "I CAN EATZ IT??? : ${keyId}?", + "fullAccess": "All da Doors Open", "getToken": "GIT TOKEN", "name": "NAME", "nameDesc": "WUT WUD U LIEK 2 CALL DIS API TOKEN? ", @@ -177,11 +178,14 @@ }, "thousands": ",", "zeroRecords": "No matching records found" - } + }, + "loadingRecords": "Loading..." }, "error": { "agree": "YESH PLS", "bedrockError": "BEDROCKZ DOWNLOADZ NO WORKY. CHECK PLZ.", + "bigBucket1": "Big Bucket No Worky. Plz check", + "bigBucket2": "for da freshest nooz.", "cancel": "NOPEZ", "contact": "CONTACK CWAFTY CONTROLLR SUPORT ON DA DIZORD", "craftyStatus": "CWAFTY'S STATUSZ", @@ -203,6 +207,8 @@ "not-downloaded": "SOZ BUT I FAILDZ CAN'T SEEM TO FINDZ YOUR FISH. PLZ GIB MEZ IT. I HUNGRY.", "portReminder": "WE HAS DETECTD DIS AR TEH FURST TIEM {} IZ BEAN RUN. IF U WANTS IT ACESIBLE TO NEIGHBORHOOD CATS PLZ UNLOCK CAT_FLAP, {}, THRU UR ROUTR IF U HAS NOT DUN SO.", "privMsg": "AND THEEZ ", + "return": "Go Bak to Dashbored", + "selfHost": "If u iz self-hostin' dis repo, check ur addy or peep our fix-it guide.", "serverJars1": "CAN'T TALK TO SERVER JARS API. CHECKZ", "serverJars2": "TO SEE NEWZ STUFFZ.", "start-error": "CHAIR {} FAILD 2 START WIF OOF CODE: {}", @@ -600,6 +606,7 @@ }, "startup": { "almost": "ALMOST DUN. HOLD ON TO YER WHISKERS...", + "cache": "Fightin' for a Big Bucket o Fish", "internals": "SETTIN' UP AN' STARTIN' CWAFTY'S INSIDE BITZ", "internet": "LOOKIN' FOR OUTER SPACE TALKY", "server": "WAKIN' UPZ ", diff --git a/app/translations/lv_LV.json b/app/translations/lv_LV.json index d337d38f..a80fee2d 100644 --- a/app/translations/lv_LV.json +++ b/app/translations/lv_LV.json @@ -20,6 +20,7 @@ "created": "Izveidots", "deleteKeyConfirmation": "Vai vēlies dzēst šo API atslēgu? Šo nevar atdarīt.", "deleteKeyConfirmationTitle": "Noņemt API atslēgu ${keyId}?", + "fullAccess": "Pilna piekļuve", "getToken": "Saņemt Pilnvaru (Token)", "name": "Nosaukums", "nameDesc": "Kā jūs vēlaties nosaukt šo Pilnvaru (Token)? ", @@ -178,11 +179,14 @@ }, "thousands": ",", "zeroRecords": "Nav atrasti atbilstoši ieraksti" - } + }, + "loadingRecords": "Ielādē..." }, "error": { "agree": "Piekrītu", "bedrockError": "Bedrock lejupielādes nav pieejamas. Lūdzu pārbaudi", + "bigBucket1": "Big Bucket stāvokļa pārbaude neizdevās. Lūdzu izpētiet", + "bigBucket2": "priekš jaunākās informācijas.", "cancel": "Atcelt", "contact": "Sazinies ar Crafty Control Atbalstu izmantojot Discord", "craftyStatus": "Crafty statusa lapa", @@ -204,6 +208,8 @@ "not-downloaded": "Mēs nevaram atrast jūsu izpildāmo failu. Vai tas ir beidzis lejupielādēties? Vai tā peikļuves ir uzstādītas kā palaižamas?", "portReminder": "Mēs noteicām ka šī ir pirmā reize, kad {} ir ticis palaists. Pārliecinies izlaist portu {} cauri savam rūterim/ugunsmūrim lai padarītu šo attāli pieejamu no interneta.", "privMsg": "un ", + "return": "Atgriezties uz pārskatu", + "selfHost": "Ja jūs paši uzturat šo repozitoriju, pārbaudiet savu adresi vai apskatiet mūsu kļūdu novēršanas dokumentāciju.", "serverJars1": "Serveru JAR API nav sasniedzams. Lūdzu pārbaudiet", "serverJars2": "priekš jaunākās informācijas.", "start-error": "Serveris {} neveiskmīgi startējās ar kļūdas kodu: {}", @@ -601,6 +607,7 @@ }, "startup": { "almost": "Pabeidz. Vēl tik nedaudz...", + "cache": "Atjauno Big Bucket keša failu", "internals": "Konfigurē un Startē Crafty iekšējās komponenetes", "internet": "Pārbauda interneta savienojumu", "server": "Inicializē ", diff --git a/app/translations/nl_BE.json b/app/translations/nl_BE.json index d20e209c..a29287cb 100644 --- a/app/translations/nl_BE.json +++ b/app/translations/nl_BE.json @@ -20,6 +20,7 @@ "created": "Gecreëerd", "deleteKeyConfirmation": "Wilt u deze API sleutel verwijderen? Dit kan niet ongedaan gemaakt worden.", "deleteKeyConfirmationTitle": "API sleutel verwijderen ${keyId}?", + "fullAccess": "Volledige toegang", "getToken": "Verkrijg een Token", "name": "Naam", "nameDesc": "Hoe wilt u dit API token noemen? ", @@ -177,11 +178,14 @@ }, "thousands": ",", "zeroRecords": "Geen overeenkomende records gevonden" - } + }, + "loadingRecords": "Bezig met laden..." }, "error": { "agree": "Akkoord", "bedrockError": "Bedrock-downloads niet beschikbaar. Controleer alstublieft", + "bigBucket1": "Big Bucket-gezondheidscontrole mislukt. Controleer alstublieft", + "bigBucket2": "voor de meest recente informatie.", "cancel": "Annuleren", "contact": "Neem contact op met Crafty Control ondersteuning via Discord", "craftyStatus": "Crafty's statuspagina", @@ -203,6 +207,8 @@ "not-downloaded": "We kunnen uw uitvoerbare bestand niet vinden. Is het klaar met downloaden? Zijn de rechten ingesteld op uitvoerbaar?", "portReminder": "We hebben ontdekt dat dit de eerste keer is dat {} wordt uitgevoerd. Zorg ervoor dat u poort {} doorstuurt via uw router/firewall om deze op afstand toegankelijk te maken vanaf het internet.", "privMsg": "en de ", + "return": "Terug naar Dashboard", + "selfHost": "Als u deze repository zelf host, controleer dan uw adres of raadpleeg onze handleiding voor probleemoplossing.", "serverJars1": "Server JARs API niet bereikbaar. Controleer alstublieft", "serverJars2": "voor de meest recente informatie.", "start-error": "Server {} kan niet starten met foutcode: {}", @@ -600,6 +606,7 @@ }, "startup": { "almost": "De laatste hand leggen. Houd je vast...", + "cache": "Big Bucket-cachebestand vernieuwen", "internals": "Crafty's interne componenten configureren en starten", "internet": "Controleren op internetverbinding", "server": "Initialiseren ", diff --git a/app/translations/nl_NL_incomplete.json b/app/translations/nl_NL_incomplete.json index 8fd97240..1d158594 100644 --- a/app/translations/nl_NL_incomplete.json +++ b/app/translations/nl_NL_incomplete.json @@ -99,6 +99,7 @@ "welcome": "Welkom bij Crafty Controller" }, "datatables": { + "loadingRecords": "Laden...", "i18n": { "aria": { "sortAscending": ": activeer om kolom oplopend te sorteren", @@ -529,4 +530,4 @@ "userSettings": "Gebruikersinstellingen", "uses": "Aantal keer toegestaan (-1==Geen limiet)" } -} +} \ No newline at end of file diff --git a/app/translations/pl_PL.json b/app/translations/pl_PL.json index ade8c5a5..fdb3d892 100644 --- a/app/translations/pl_PL.json +++ b/app/translations/pl_PL.json @@ -20,6 +20,7 @@ "created": "Stworzono", "deleteKeyConfirmation": "Czy chcesz usunąć ten klucz API? Nie można tego cofnąć.", "deleteKeyConfirmationTitle": "Usunąć Klucz API ${keyId}?", + "fullAccess": "Pełny dostęp", "getToken": "Zdobądź token", "name": "Nazwa", "nameDesc": "Jak chcesz nazwać ten klucz API? ", @@ -177,11 +178,14 @@ }, "thousands": ",", "zeroRecords": "Nie znaleziono pasujacego wyniku" - } + }, + "loadingRecords": "Wczytywanie..." }, "error": { "agree": "Zgadzam się", "bedrockError": "Pobieranie serwera bedrock jest teraz niedostępne. Proszę sprawdź", + "bigBucket1": "Odświeżanie Big Bucket nie powiodło się. Sprawdź", + "bigBucket2": "dla najnowszych informacji.", "cancel": "Anuluj", "contact": "Podrzebujesz pomocy? Zapraszamy na serwer discord Crafty Controler", "craftyStatus": "Status strony Craftyiego", @@ -203,6 +207,8 @@ "not-downloaded": "Nie możemy znaleść twojego pliku serwera. Czy skończył się pobierać? Czy permisje są ustawione na wykonywanle?", "portReminder": "Zauważyliśmy że to jest pierwszy raz {} kiedy był włączony. Upewnij się że otworzyłeś port {} na swoim routerze/firewallu aby korzystać z tego poza domem.", "privMsg": "i także ", + "return": "Powrót do panelu", + "selfHost": "Jeśli zarządasz tą repozytorią upewnij się że adres hest poprawny, w innym przypadku odwiedź strone rozwiązywania problemów.", "serverJars1": "API Server Jars jest niedostępne. Proszę sprawdź", "serverJars2": "dla najnowzsych informacji.", "start-error": "Serwer {} nie mógł się odpalić z powodu: {}", @@ -599,6 +605,7 @@ }, "startup": { "almost": "Prawie gotowe! Jeszcze tylko chwilka...", + "cache": "Odświeżanie pamięci podręcznej Big Bucket", "internals": "Konfigurowanie i włączanie backendu...", "internet": "Sprawdzam połączenie z internetem", "server": "Włączanie ", diff --git a/app/translations/pt_BR_incomplete.json b/app/translations/pt_BR_incomplete.json index c8844f40..dcd375f6 100644 --- a/app/translations/pt_BR_incomplete.json +++ b/app/translations/pt_BR_incomplete.json @@ -100,6 +100,7 @@ "welcome": "Bem-vindo ao Crafty Controller" }, "datatables": { + "loadingRecords": "Carregando...", "i18n": { "aria": { "sortAscending": ": ative para ordenar a coluna de forma ascendente", @@ -537,4 +538,4 @@ "userSettings": "Configurações do Usuário", "uses": "Número de Usos Permitidos (-1==Sem Limite)" } -} +} \ No newline at end of file diff --git a/app/translations/th_TH.json b/app/translations/th_TH.json index bbb82dae..8c9d014e 100644 --- a/app/translations/th_TH.json +++ b/app/translations/th_TH.json @@ -20,6 +20,7 @@ "created": "สร้างเมื่อ", "deleteKeyConfirmation": "คุณต้องการลบคีย์ API นี้หรือไม่ สิ่งนี้ไม่สามารถยกเลิกได้", "deleteKeyConfirmationTitle": "ลบคีย์ API นี้ ${keyId} หรือไม่?", + "fullAccess": "เข้าถึงได้ทั้งหมด", "getToken": "แสดงโทเค็น", "name": "ชื่อ", "nameDesc": "คุณต้องการเรียกโทเค็น API นี้ว่าอะไร ? ", @@ -177,7 +178,8 @@ }, "thousands": ",", "zeroRecords": "ไม่พบรายการที่ตรงกัน" - } + }, + "loadingRecords": "กำลังโหลด..." }, "error": { "agree": "ยอมรับ", @@ -203,6 +205,7 @@ "not-downloaded": "ดูเหมือนว่าเราจะไม่พบแฟ้มกระทำการของคุณ (.jar) ตรวจสอบให้แน่ใจว่าการดาวโหลดน์เสร็จสิ้นแล้ว, การอนุญาตถูกตั้งไปยังแฟ้มกระทำการหรือไม่?", "portReminder": "เราตรวจพบว่านี่เป็นครั้งแรกที่มีการเรียกใช้ {} ตรวจสอบให้แน่ใจว่าได้ Forward port {} ผ่านเราเตอร์/ไฟร์วอลล์ของคุณเพื่อให้สามารถเข้าถึงได้จากอินเทอร์เน็ตจากระยะไกล", "privMsg": "และ ", + "return": "ย้อนกลับไปยังแผงควบคุม", "serverJars1": "ไม่สามารถเข้าถึงเซิร์ฟเวอร์ JARs API กรุณาตรวจสอบ", "serverJars2": "เพื่อข้อมูลที่ทันสมัยที่สุด", "start-error": "เซิร์ฟเวอร์ {} ไม่สามารถเริ่มต้นได้เนื่องจากรหัสข้อผิดพลาด: {}", diff --git a/app/translations/tr_TR.json b/app/translations/tr_TR.json index cddd2a50..c05c6d66 100644 --- a/app/translations/tr_TR.json +++ b/app/translations/tr_TR.json @@ -20,6 +20,7 @@ "created": "Oluşturuldu", "deleteKeyConfirmation": "Bu API anahtarını silmek istediğine emin misin? Bu geri alınamaz.", "deleteKeyConfirmationTitle": "${keyId} API anahtarını kaldırma işlemi.", + "fullAccess": "Tam Erişim", "getToken": "Bir Token Al", "name": "Ad", "nameDesc": "Bu API tokeninin adı ne olsun?", @@ -177,11 +178,14 @@ }, "thousands": ".", "zeroRecords": "Eşleşen kayıt bulunamadı" - } + }, + "loadingRecords": "Yükleniyor..." }, "error": { "agree": "Kabul Et", "bedrockError": "Bedrock indirmeleri kullanılamıyor. Lütfen kontrol edin", + "bigBucket1": "Big Bucket sağlık kontrolü yapılamadı. En güncel bilgileri burada bulabilirsiniz:", + "bigBucket2": ".", "cancel": "İptal", "contact": "Crafty Control Destek Discord Sunucusu", "craftyStatus": "Crafty'nin durum sayfası", @@ -203,6 +207,8 @@ "not-downloaded": "Çalıştırılabilir dosyanızı bulamıyoruz. İndirme işlemi tamamlandı mı? İzinler çalıştırılabilir olarak ayarlandı mı?", "portReminder": "{} ilk kez çalıştırılıyor olduğunu tespit ettik. Bunu internetten uzaktan erişilebilir kılmak için {} bağlantı noktasını yönlendiriciniz/güvenlik duvarınız üzerinden ilettiğinizden emin olun.", "privMsg": "ve ", + "return": "Arayüze Geri Dön", + "selfHost": "Bu depoyu kendiniz barındırıyorsanız lütfen adresinizi kontrol ediniz veya sorun giderme kılavuzumuza bakınız.", "serverJars1": "Sunucu JARs API'ına erişilemiyor.", "serverJars2": "en güncel bilgilere sahiptir", "start-error": "{} sunucusu başlamatılamadı. Hata kodu: {}", @@ -599,6 +605,7 @@ }, "startup": { "almost": "Bitiriliyor. Sıkı tutun...", + "cache": "Big Bucket önbellek dosyası yenileniyor", "internals": "Crafty'nin dahili bileşenlerini konfigüre etme ve başlatma", "internet": "İnternet bağlantısı kontrol ediliyor", "server": "Başlatılıyor ", diff --git a/app/translations/uk_UA.json b/app/translations/uk_UA.json index 74b683dd..ed614e59 100644 --- a/app/translations/uk_UA.json +++ b/app/translations/uk_UA.json @@ -20,6 +20,7 @@ "created": "Створений", "deleteKeyConfirmation": "Ви дійсно бажаєте видалити API ключ? Це незворотня дія.", "deleteKeyConfirmationTitle": "Видалення API ключ ${keyId}?", + "fullAccess": "Повний доступ", "getToken": "Отримати Токен", "name": "Ім'я", "nameDesc": "Як ви хочете назвати даний API токен?", @@ -177,11 +178,14 @@ }, "thousands": ",", "zeroRecords": "Не знайдено збігів в базі" - } + }, + "loadingRecords": "Завантаження..." }, "error": { "agree": "Згодний", "bedrockError": "Bedrock недоступний для скачування. Перевірте будь ласка", + "bigBucket1": "Перевірка Big Bucket не пройдено. Будь ласка, перевірте", + "bigBucket2": "для найактуальнішої інформації.", "cancel": "Відміна", "contact": "Зв'язатись з Crafty Control підтримкою через Discord", "craftyStatus": "Crafty's статус", @@ -203,6 +207,8 @@ "not-downloaded": "Здається, ми не можемо знайти ваш виконуваний файл. Чи завершилось завантаження? Чи встановлено дозволи на виконуваний файл?", "portReminder": "Ми виявили це вперше {} був запущений. Обов’язково перенаправте порт {} через ваш маршрутизатор/брандмауер, щоб зробити це доступним з Інтернету.", "privMsg": "і ", + "return": "Повернутись до панелі", + "selfHost": "Якщо ви самостійно розміщуєте цей репозеторій, перевірте свою адресу або зверніться до нашого посібника з усунення несправностей.", "serverJars1": "API сервера JAR недоступний. Будь ласка, перевірте", "serverJars2": "для найактуальнішої інформації.", "start-error": "Сервер {} не запустився через помилку: {}", @@ -599,6 +605,7 @@ }, "startup": { "almost": "Закінчуємо. Тримайся міцніше...", + "cache": "Оновлення файлу кешу Big Bucket", "internals": "Налаштування та запуск внутрішніх компонентів Crafty ", "internet": "Перевірка доступу до інтернету", "server": "Ініціалізація ", diff --git a/app/translations/zh_CN.json b/app/translations/zh_CN.json index 0b817b7e..6b488382 100644 --- a/app/translations/zh_CN.json +++ b/app/translations/zh_CN.json @@ -20,6 +20,7 @@ "created": "创建时间", "deleteKeyConfirmation": "您想要删除这个 API 密钥吗?此操作不能撤销。", "deleteKeyConfirmationTitle": "删除 API 密钥 ${keyId}?", + "fullAccess": "完全访问", "getToken": "获得一个令牌", "name": "名称", "nameDesc": "你想把这个 API 令牌叫做什么?", @@ -177,11 +178,14 @@ }, "thousands": ",", "zeroRecords": "没有找到匹配的记录" - } + }, + "loadingRecords": "正在加载……" }, "error": { "agree": "同意", "bedrockError": "基岩版下载不可用。请检查", + "bigBucket1": "Big Bucket 查活失败。请检查", + "bigBucket2": "以获取最新信息。", "cancel": "取消", "contact": "通过 Discord 联系 Crafty Control 支持", "craftyStatus": "Crafty 的状态页面", @@ -203,6 +207,8 @@ "not-downloaded": "我们似乎找不到您的可执行文件。它下载完成了吗?可执行文件的权限设置正确了吗?", "portReminder": "我们检测到这是你首次运行 {}。请确保从您的路由器/防火墙转发 {} 端口,以使程序可以从公网远程访问。", "privMsg": "以及", + "return": "返回仪表板", + "selfHost": "如果您自托管此仓库,请检查您的地址或参考我们的故障排除指南。", "serverJars1": "无法访问服务器 JAR API。请检查", "serverJars2": "以获取最新信息。", "start-error": "服务器 {} 启动失败,错误代码为:{}", @@ -600,6 +606,7 @@ }, "startup": { "almost": "即将完成。请稍候……", + "cache": "正在刷新 Big Bucket 缓存文件", "internals": "正在配置并启动 Crafty 的内部组件", "internet": "正在检查网络连接", "server": "正在初始化 ", diff --git a/main.py b/main.py index ebaf7806..fd84328b 100644 --- a/main.py +++ b/main.py @@ -17,6 +17,7 @@ from app.classes.models.users import HelperUsers from app.classes.models.management import HelpersManagement from app.classes.shared.import_helper import ImportHelpers from app.classes.shared.websocket_manager import WebSocketManager +from app.classes.logging.log_formatter import JsonFormatter console = Console() helper = Helpers() @@ -117,7 +118,7 @@ def controller_setup(): def tasks_starter(): """ Method starts stats recording, app scheduler, and - serverjars/steamCMD cache refreshers + big bucket/steamCMD cache refreshers """ # start stats logging tasks_manager.start_stats_recording() @@ -127,8 +128,8 @@ def tasks_starter(): tasks_manager.start_scheduler() # refresh our cache and schedule for every 12 hoursour cache refresh - # for serverjars.com - tasks_manager.serverjar_cache_refresher() + # for big bucket.com + tasks_manager.big_bucket_cache_refresher() def signal_handler(signum, _frame): @@ -212,6 +213,8 @@ def setup_starter(): time.sleep(2) controller_setup_thread.start() + web_sock.broadcast("update", {"section": "cache"}) + controller.big_bucket.manual_refresh_cache() # Wait for the setup threads to finish web_sock.broadcast( "update", @@ -284,6 +287,11 @@ def setup_logging(debug=True): logging.config.dictConfig(logging_config) + # Apply JSON formatting to the "audit" handler + for handler in logging.getLogger().handlers: + if handler.name == "audit_log_handler": + handler.setFormatter(JsonFormatter()) + else: logging.basicConfig(level=logging.DEBUG) logging.warning(f"Unable to read logging config from {logging_config_file}") diff --git a/sonar-project.properties b/sonar-project.properties index 892bb292..bd184b84 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -3,7 +3,7 @@ sonar.organization=crafty-controller # This is the name and version displayed in the SonarCloud UI. sonar.projectName=Crafty 4 -sonar.projectVersion=4.3.3 +sonar.projectVersion=4.4.1 sonar.python.version=3.9, 3.10, 3.11 sonar.exclusions=app/migrations/**, app/frontend/static/assets/vendors/**