diff --git a/.gitlab/issue_templates/Bug.md b/.gitlab/issue_templates/Bug.md index 88b2a3b4..77136b69 100644 --- a/.gitlab/issue_templates/Bug.md +++ b/.gitlab/issue_templates/Bug.md @@ -3,16 +3,16 @@ - **Install Type:** Git Cloned(Manual) / Installer / WinPackage / Docker ## What Happened? -*A brief description of what happened when you tried to perform an action* + ## Expected result -*What should have happened when you performed the actions* + ## Steps to reproduce -*List the steps required to produce the error. These should be as few as possible* + ## Screenshots -Any relevant screenshots which show the issue* !-->* + ## Priority/Severity - [ ] High (anything that impacts the normal user flow or blocks app usage) diff --git a/.gitlab/issue_templates/Change Request.md b/.gitlab/issue_templates/Change Request.md index 995e7ad1..f767ee15 100644 --- a/.gitlab/issue_templates/Change Request.md +++ b/.gitlab/issue_templates/Change Request.md @@ -1,13 +1,14 @@ ## Summary -*Outline the issue being faced, and why this needs to change* + ## Area of the system -*This might only be one part, but may involve multiple sections, Login/Dashboad/Terminal/Config* + ## How does this currently work? + ## What is the desired way of working? -*After the change, what should the process/operation be?* + ## Priority/Severity - [ ] High (This will bring a huge increase in performance/productivity/usability) diff --git a/.gitlab/issue_templates/Feature Request.md b/.gitlab/issue_templates/Feature Request.md index 2450aa60..b26067d6 100644 --- a/.gitlab/issue_templates/Feature Request.md +++ b/.gitlab/issue_templates/Feature Request.md @@ -1,8 +1,8 @@ ## Problem Statement -*What is the issue being faced and needs addressing?* + ## Who will benefit? -*Will this fix a problem that only one user has, or will it benefit a lot of people* + ## Benefits and risks What benefits does this bring? @@ -16,10 +16,10 @@ ## Proposed solution -*How would you like to see this issue resolved?* + ## Examples -*Are there any examples of this which exist in other software?* + ## Priority/Severity - [ ] High (This will bring a huge increase in performance/productivity/usability) 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/.gitlab/merge_request_templates/Default.md b/.gitlab/merge_request_templates/Default.md index a82cb3f8..70bcd7db 100644 --- a/.gitlab/merge_request_templates/Default.md +++ b/.gitlab/merge_request_templates/Default.md @@ -1,22 +1,22 @@ ## What does this MR do and why? -___Describe in detail what your merge request does and why.___
-> *Please keep this description updated with any discussion that takes place so*
-*that reviewers can understand your intent. Keeping the description updated is*
-*especially important if they didn't participate in the discussion.*
+ + + + ## Screenshots or screen recordings -___These are strongly recommended to assist reviewers and reduce the time to merge your change.___
-> *Please include any relevant screenshots or screen recordings that will assist*
-*reviewers and future readers. If you need help visually verifying the change,*
-*please leave a comment and ping a GitLab reviewer, maintainer, or MR coach.*
+ + + + ## How to set up and validate locally -___Numbered steps to set up and validate the change are strongly suggested.___ + ## MR acceptance checklist diff --git a/.gitlab/scripts/linux_perms_fix.sh b/.gitlab/scripts/linux_perms_fix.sh index 24b92176..d727b16b 100644 --- a/.gitlab/scripts/linux_perms_fix.sh +++ b/.gitlab/scripts/linux_perms_fix.sh @@ -3,13 +3,46 @@ # Prompt the user for the directory path read -p "Enter the directory path to set permissions (/var/opt/minecraft/crafty): " directory_path +# Count the total number of directories +total_dirs=$(find "$directory_path" -type d 2>/dev/null | wc -l) + +# Count the total number of files +total_files=$(find "$directory_path" -type f 2>/dev/null | wc -l) + +# Initialize a counter for directories and files +dir_count=0 +file_count=0 + +# Function to print progress +print_progress() { + echo -ne "\rDirectories: $dir_count/$total_dirs Files: $file_count/$total_files" +} + # Check if the script is running within a Docker container if [ -f "/.dockerenv" ]; then echo "Script is running within a Docker container. Exiting with error." exit 1 # Exit with an error code if running in Docker else echo "Script is not running within a Docker container. Executing permissions changes..." - # Run the commands to set permissions - sudo chmod 700 $(find "$directory_path" -type d) - sudo chmod 644 $(find "$directory_path" -type f) -fi \ No newline at end of file + + # Run the commands to set permissions for directories + echo "Changing permissions for directories:" + for dir in $(find "$directory_path" -type d 2>/dev/null); do + if [ -e "$dir" ]; then + sudo chmod 700 "$dir" && ((dir_count++)) + fi + print_progress + done + + # Run the commands to set permissions for files + echo -e "\nChanging permissions for files:" + for file in $(find "$directory_path" -type f 2>/dev/null); do + if [ -e "$file" ]; then + sudo chmod 644 "$file" && ((file_count++)) + fi + print_progress + done + echo "You will now need to execute a chmod +x on all bedrock executables" +fi + +echo "" # Adding a new line after the loop for better readability \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 44750a39..cdc9d383 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,35 @@ # Changelog -## --- [4.3.2] - 2024/TBD +## --- [4.3.3] - 2024/TBD +### Refactor +- Refactor API keys "super user" to "full access" ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/731)) ### New features TBD ### Bug fixes TBD ### Tweaks -TBD +- Add link to go back to dashboard on error page ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/743)) ### Lang TBD

+## --- [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)) +### Bug fixes +- Fix migrator issue when jumping versions ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/734)) +- Fix backend issue causing error when restoring backups in 4.3.x ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/736)) +- Fix backend issue causing error when cloning servers in 4.3.x ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/741)) +- Bump orjson for CVE-2024-27454 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/747)) +- Fix calling of orjson JSONDecodeError class ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/747)) +- Fix stack on Crafty permissions route request in API ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/745)) +### Tweaks +- Clean up remaining http handler references ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/733)) +- Remove version disclosure on login page ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/737)) +- Add openjdk-21 for recent versions of MC ([Commit](https://gitlab.com/crafty-controller/crafty-4/-/commit/77b0c2c9d2eac124a7504a3d3916fa22d29fa9d1)) +### Lang +- Update `it_IT, cs_CS` ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/739) | [Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/742)) +

+ ## --- [4.3.1] - 2024/03/18 ### Bug fixes - Fix Server ID Rework for backups, schedules, and roles (INT ID to UUID migration) ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/729)) diff --git a/Dockerfile b/Dockerfile index e8bdb102..d05cf3d0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,6 +25,7 @@ RUN apt-get update \ openjdk-8-jre-headless \ openjdk-11-jre-headless \ openjdk-17-jre-headless \ + openjdk-21-jre-headless \ tzdata \ && apt-get autoremove \ && apt-get clean diff --git a/README.md b/README.md index ae70bd0e..64e26224 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.2 +# Crafty Controller 4.3.3 > Python based Control Panel for your Minecraft Server ## What is Crafty Controller? diff --git a/app/classes/controllers/server_perms_controller.py b/app/classes/controllers/server_perms_controller.py index f6632cd7..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() @@ -47,7 +51,7 @@ class ServerPermsController: new_server_id, role.role_id, PermissionsServers.get_permissions_mask( - int(role.role_id), int(old_server_id) + int(role.role_id), old_server_id ), ) # Permissions_Servers.add_role_server( @@ -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/minecraft/serverjars.py b/app/classes/minecraft/serverjars.py index 83731b52..944ec382 100644 --- a/app/classes/minecraft/serverjars.py +++ b/app/classes/minecraft/serverjars.py @@ -12,13 +12,15 @@ 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://serverjars.com" + self.base_url = "https://api.serverjars.com" self.paper_base = "https://api.papermc.io" @staticmethod @@ -82,6 +84,183 @@ class ServerJars: 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. @@ -132,151 +311,6 @@ class ServerJars: logger.error(f"An error occurred while constructing fetch URL: {e}") return None - def _get_api_result(self, call_url: str): - full_url = f"{self.base_url}{call_url}" - - try: - response = requests.get(full_url, timeout=2) - response.raise_for_status() - api_data = json.loads(response.content) - except Exception as e: - logger.error(f"Unable to load {full_url} api due to error: {e}") - return {} - - api_result = api_data.get("status") - api_response = api_data.get("response", {}) - - if api_result != "success": - logger.error(f"Api returned a failed status: {api_result}") - return {} - - return api_response - - 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_api_alive(self): - logger.info("Checking serverjars.com API status") - - check_url = f"{self.base_url}/api/fetchTypes" - try: - response = requests.get(check_url, timeout=2) - - if response.status_code in [200, 201]: - logger.info("Serverjars.com API is alive") - return True - except Exception as e: - logger.error(f"Unable to connect to serverjar.com api due to error: {e}") - return {} - - logger.error("unable to contact serverjars.com api") - return False - - def manual_refresh_cache(self): - cache_file = self.helper.serverjar_cache - - # debug override - # cache_old = True - - # if the API is down... we bomb out - if not self._check_api_alive(): - return False - - logger.info("Manual Refresh requested.") - now = datetime.now() - data = { - "last_refreshed": now.strftime("%m/%d/%Y, %H:%M:%S"), - "types": {}, - } - - jar_types = self._get_server_type_list() - data["types"].update(jar_types) - for s in data["types"]: - data["types"].update({s: dict.fromkeys(data["types"].get(s), {})}) - for j in data["types"].get(s): - versions = self._get_jar_details(j, s) - data["types"][s].update({j: versions}) - for item in PAPERJARS: - data["types"]["servers"][item] = self.get_paper_versions(item) - # save our cache - try: - with open(cache_file, "w", encoding="utf-8") as f: - f.write(json.dumps(data, indent=4)) - logger.info("Cache file refreshed") - - except Exception as e: - logger.error(f"Unable to update serverjars.com cache file: {e}") - - def refresh_cache(self): - cache_file = self.helper.serverjar_cache - cache_old = self.helper.is_file_older_than_x_days(cache_file) - - # debug override - # cache_old = True - - # if the API is down... we bomb out - if not self._check_api_alive(): - return False - - logger.info("Checking Cache file age") - # if file is older than 1 day - - if cache_old: - logger.info("Cache file is over 1 day old, refreshing") - now = datetime.now() - data = { - "last_refreshed": now.strftime("%m/%d/%Y, %H:%M:%S"), - "types": {}, - } - - jar_types = self._get_server_type_list() - data["types"].update(jar_types) - for s in data["types"]: - data["types"].update({s: dict.fromkeys(data["types"].get(s), {})}) - for j in data["types"].get(s): - versions = self._get_jar_details(j, s) - data["types"][s].update({j: versions}) - for item in PAPERJARS: - data["types"]["servers"][item] = self.get_paper_versions(item) - # save our cache - try: - with open(cache_file, "w", encoding="utf-8") as f: - f.write(json.dumps(data, indent=4)) - logger.info("Cache file refreshed") - - except Exception as e: - logger.error(f"Unable to update serverjars.com cache file: {e}") - - def _get_jar_details(self, server_type, jar_type="servers"): - url = f"/api/fetchAll/{jar_type}/{server_type}" - response = self._get_api_result(url) - temp = [] - for v in response: - temp.append(v.get("version")) - time.sleep(0.5) - return temp - - def _get_server_type_list(self): - url = "/api/fetchTypes/" - response = self._get_api_result(url) - if "bedrock" in response.keys(): - # remove pocketmine from options - del response["bedrock"] - return response - def download_jar(self, jar, server, version, path, server_id): update_thread = threading.Thread( name=f"server_download-{server_id}-{server}-{version}", 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/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 e44d06fb..3f96e651 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" @@ -408,7 +408,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, ): @@ -426,7 +426,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/main_controller.py b/app/classes/shared/main_controller.py index b6e824a5..9c3219ff 100644 --- a/app/classes/shared/main_controller.py +++ b/app/classes/shared/main_controller.py @@ -575,7 +575,7 @@ class Controller: ): server_obj = self.servers.get_server_obj(new_server_id) url = ( - "https://serverjars.com/api/fetchJar/" + "https://api.serverjars.com/api/fetchJar/" f"{create_data['category']}" f"/{create_data['type']}/{create_data['version']}" ) @@ -1131,7 +1131,7 @@ class Controller: server_obj.path = new_local_server_path failed = False for s in self.servers.failed_servers: - if int(s["server_id"]) == int(server.get("server_id")): + if s["server_id"] == server.get("server_id"): failed = True if not failed: self.servers.update_server(server_obj) diff --git a/app/classes/shared/migration.py b/app/classes/shared/migration.py index 6a93802b..287bb4f3 100644 --- a/app/classes/shared/migration.py +++ b/app/classes/shared/migration.py @@ -372,11 +372,11 @@ class MigrationManager(object): Create migrator """ migrator = Migrator(self.database) - # Removing the up_one to prevent running all - # migrations each time we got a new one. - # It's handled by migration.up() function. - # for name in self.done: - # self.up_one(name, migrator, True) + # Running false migrations to retrives the schemes of + # the precedents created tables in the table_dict element + # It's useful to run the new migrations + for name in self.done: + self.up_one(name, migrator, True) return migrator def compile(self, name, migrate="", rollback=""): 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 d61e3c0e..e1beb547 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 @@ -1356,6 +1356,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") @@ -1403,7 +1406,7 @@ class PanelHandler(BaseHandler): self.controller.management.add_to_audit_log( exec_user["user_id"], f"Removed user {target_user['username']} (UID:{user_id})", - server_id=0, + server_id=None, source_ip=self.get_remote_ip(), ) self.redirect("/panel/panel_config") diff --git a/app/classes/web/public_handler.py b/app/classes/web/public_handler.py index 467765ea..21e2d495 100644 --- a/app/classes/web/public_handler.py +++ b/app/classes/web/public_handler.py @@ -228,7 +228,7 @@ class PublicHandler(BaseHandler): ) # log this login self.controller.management.add_to_audit_log( - user_data.user_id, "Logged in", 0, self.get_remote_ip() + user_data.user_id, "Logged in", None, self.get_remote_ip() ) return self.finish_json( @@ -254,7 +254,7 @@ class PublicHandler(BaseHandler): ) # log this failed login attempt self.controller.management.add_to_audit_log( - user_data.user_id, "Tried to log in", 0, self.get_remote_ip() + user_data.user_id, "Tried to log in", None, self.get_remote_ip() ) return self.finish_json( 403, diff --git a/app/classes/web/routes/api/auth/login.py b/app/classes/web/routes/api/auth/login.py index 7a27c6f8..7e8131f3 100644 --- a/app/classes/web/routes/api/auth/login.py +++ b/app/classes/web/routes/api/auth/login.py @@ -101,7 +101,7 @@ class ApiAuthLoginHandler(BaseApiHandler): # log this login self.controller.management.add_to_audit_log( - user_data.user_id, "logged in via the API", 0, self.get_remote_ip() + user_data.user_id, "logged in via the API", None, self.get_remote_ip() ) self.finish_json( @@ -119,7 +119,7 @@ class ApiAuthLoginHandler(BaseApiHandler): else: # log this failed login attempt self.controller.management.add_to_audit_log( - user_data.user_id, "Tried to log in", 0, self.get_remote_ip() + user_data.user_id, "Tried to log in", None, self.get_remote_ip() ) self.finish_json( 401, diff --git a/app/classes/web/routes/api/crafty/config/index.py b/app/classes/web/routes/api/crafty/config/index.py index c901732c..40504d76 100644 --- a/app/classes/web/routes/api/crafty/config/index.py +++ b/app/classes/web/routes/api/crafty/config/index.py @@ -9,7 +9,6 @@ from app.classes.web.base_api_handler import BaseApiHandler config_json_schema = { "type": "object", "properties": { - "http_port": {"type": "integer"}, "https_port": {"type": "integer"}, "language": { "type": "string", @@ -107,7 +106,7 @@ class ApiCraftyConfigIndexHandler(BaseApiHandler): try: data = orjson.loads(self.request.body) - except orjson.decoder.JSONDecodeError as e: + except orjson.JSONDecodeError as e: return self.finish_json( 400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)} ) @@ -129,7 +128,7 @@ class ApiCraftyConfigIndexHandler(BaseApiHandler): self.controller.management.add_to_audit_log( user["user_id"], "edited config.json", - server_id=0, + server_id=None, source_ip=self.get_remote_ip(), ) @@ -188,7 +187,7 @@ class ApiCraftyCustomizeIndexHandler(BaseApiHandler): try: data = orjson.loads(self.request.body) - except orjson.decoder.JSONDecodeError as e: + except orjson.JSONDecodeError as e: return self.finish_json( 400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)} ) @@ -226,7 +225,7 @@ class ApiCraftyCustomizeIndexHandler(BaseApiHandler): self.controller.management.add_to_audit_log( user["user_id"], f"customized login photo: {data['photo']}/{data['opacity']}", - server_id=0, + server_id=None, source_ip=self.get_remote_ip(), ) self.controller.management.set_login_opacity(int(data["opacity"])) 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 91c4cc89..07cf7c26 100644 --- a/app/classes/web/routes/api/crafty/config/server_dir.py +++ b/app/classes/web/routes/api/crafty/config/server_dir.py @@ -68,7 +68,7 @@ class ApiCraftyConfigServerDirHandler(BaseApiHandler): try: data = orjson.loads(self.request.body) - except orjson.decoder.JSONDecodeError as e: + except orjson.JSONDecodeError as e: return self.finish_json( 400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)} ) @@ -109,7 +109,7 @@ class ApiCraftyConfigServerDirHandler(BaseApiHandler): self.controller.management.add_to_audit_log( auth_data[4]["user_id"], f"updated master servers dir to {new_dir}/servers", - server_id=0, + server_id=None, source_ip=self.get_remote_ip(), ) diff --git a/app/classes/web/routes/api/roles/index.py b/app/classes/web/routes/api/roles/index.py index 266afb23..0f656dbb 100644 --- a/app/classes/web/routes/api/roles/index.py +++ b/app/classes/web/routes/api/roles/index.py @@ -161,7 +161,7 @@ class ApiRolesIndexHandler(BaseApiHandler): self.controller.management.add_to_audit_log( user["user_id"], f"created role {role_name} (RID:{role_id})", - server_id=0, + server_id=None, source_ip=self.get_remote_ip(), ) diff --git a/app/classes/web/routes/api/roles/role/index.py b/app/classes/web/routes/api/roles/role/index.py index 5ed12d69..97362f5b 100644 --- a/app/classes/web/routes/api/roles/role/index.py +++ b/app/classes/web/routes/api/roles/role/index.py @@ -112,7 +112,7 @@ class ApiRolesRoleIndexHandler(BaseApiHandler): self.controller.management.add_to_audit_log( user["user_id"], f"deleted role with ID {role_id}", - server_id=0, + server_id=None, source_ip=self.get_remote_ip(), ) @@ -133,7 +133,7 @@ class ApiRolesRoleIndexHandler(BaseApiHandler): try: data = orjson.loads(self.request.body) - except orjson.decoder.JSONDecodeError as e: + except orjson.JSONDecodeError as e: return self.finish_json( 400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)} ) @@ -172,7 +172,7 @@ class ApiRolesRoleIndexHandler(BaseApiHandler): self.controller.management.add_to_audit_log( user["user_id"], f"modified role with ID {role_id}", - server_id=0, + server_id=None, source_ip=self.get_remote_ip(), ) diff --git a/app/classes/web/routes/api/servers/server/action.py b/app/classes/web/routes/api/servers/server/action.py index 01ce45c4..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"}) @@ -33,6 +34,17 @@ class ApiServersServerActionHandler(BaseApiHandler): self.controller.crafty_perms.can_create_server(auth_data[4]["user_id"]) or auth_data[4]["superuser"] ): + srv_object = self.controller.servers.get_server_instance_by_id( + server_id + ) + if srv_object.check_running(): + return self.finish_json( + 409, + { + "status": "error", + "error": "Server Running!", + }, + ) self._clone_server(server_id, auth_data[4]["user_id"]) return self.finish_json(200, {"status": "ok"}) return self.finish_json( @@ -67,20 +79,29 @@ class ApiServersServerActionHandler(BaseApiHandler): name_counter += 1 new_server_name = server_data.get("server_name") + f" (Copy {name_counter})" - new_server_id = self.controller.servers.create_server( - new_server_name, - None, - "", - None, - server_data.get("executable"), - None, - server_data.get("stop_command"), - server_data.get("type"), - user_id, - server_data.get("server_port"), + new_server_id = self.helper.create_uuid() + new_server_path = os.path.join(self.helper.servers_dir, new_server_id) + new_backup_path = os.path.join(self.helper.backup_path, new_server_id) + new_server_command = str(server_data.get("execution_command")).replace( + server_id, new_server_id + ) + new_server_log_path = server_data.get("log_path").replace( + server_id, new_server_id ) - new_server_path = os.path.join(self.helper.servers_dir, new_server_id) + self.controller.register_server( + new_server_name, + new_server_id, + new_server_path, + new_backup_path, + new_server_command, + server_data.get("executable"), + new_server_log_path, + server_data.get("stop_command"), + server_data.get("server_port"), + user_id, + server_data.get("type"), + ) self.controller.management.add_to_audit_log( user_id, @@ -92,18 +113,6 @@ class ApiServersServerActionHandler(BaseApiHandler): # copy the old server FileHelpers.copy_dir(server_data.get("path"), new_server_path) - # TODO get old server DB data to individual variables - new_server_command = str(server_data.get("execution_command")) - new_server_log_file = str( - self.helper.get_os_understandable_path(server_data.get("log_path")) - ) - - server: Servers = self.controller.servers.get_server_obj(new_server_id) - server.path = new_server_path - server.log_path = new_server_log_file - server.execution_command = new_server_command - self.controller.servers.update_server(server) - for role in self.controller.server_perms.get_server_roles(server_id): mask = self.controller.server_perms.get_permissions_mask( role.role_id, server_id 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 05edd3a9..1b9ff915 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"}) @@ -203,7 +209,7 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler): except JobLookupError as e: logger.info("No active tasks found for server: {e}") self.controller.remove_server(server_id, True) - except Exception as e: + except (FileNotFoundError, NotADirectoryError) as e: return self.finish_json( 400, {"status": "error", "error": f"NO BACKUP FOUND {e}"} ) 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 9e47bcfc..865fe25a 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)) @@ -82,13 +84,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 f7341d38..fef154a0 100644 --- a/app/classes/web/routes/api/users/index.py +++ b/app/classes/web/routes/api/users/index.py @@ -177,7 +177,7 @@ class ApiUsersIndexHandler(BaseApiHandler): self.controller.management.add_to_audit_log( user["user_id"], f"added user {username} (UID:{user_id}) with roles {roles}", - server_id=0, + server_id=None, source_ip=self.get_remote_ip(), ) diff --git a/app/classes/web/routes/api/users/user/api.py b/app/classes/web/routes/api/users/user/api.py index 1c7635f2..2abb8463 100644 --- a/app/classes/web/routes/api/users/user/api.py +++ b/app/classes/web/routes/api/users/user/api.py @@ -43,7 +43,7 @@ class ApiUsersUserKeyHandler(BaseApiHandler): auth_data[4]["user_id"], f"Generated a new API token for the key {key.name} " f"from user with UID: {key.user_id}", - server_id=0, + server_id=None, source_ip=self.get_remote_ip(), ) data_key = self.controller.authentication.generate( @@ -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, @@ -163,7 +163,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"], ) @@ -173,7 +173,7 @@ class ApiUsersUserKeyHandler(BaseApiHandler): f"Added API key {data['name']} with crafty permissions " f"{data['crafty_permissions_mask']}" f" and {data['server_permissions_mask']} for user with UID: {user_id}", - server_id=0, + server_id=None, source_ip=self.get_remote_ip(), ) self.finish_json(200, {"status": "ok", "data": {"id": key_id}}) @@ -233,7 +233,7 @@ class ApiUsersUserKeyHandler(BaseApiHandler): auth_data[4]["user_id"], f"Removed API key {target_key} " f"(ID: {key_id}) from user {auth_data[4]['user_id']}", - server_id=0, + server_id=None, source_ip=self.get_remote_ip(), ) diff --git a/app/classes/web/routes/api/users/user/index.py b/app/classes/web/routes/api/users/user/index.py index 1b7f6f91..6efee93e 100644 --- a/app/classes/web/routes/api/users/user/index.py +++ b/app/classes/web/routes/api/users/user/index.py @@ -94,7 +94,7 @@ class ApiUsersUserIndexHandler(BaseApiHandler): self.controller.management.add_to_audit_log( user["user_id"], f"deleted the user {user_id}", - server_id=0, + server_id=None, source_ip=self.get_remote_ip(), ) @@ -283,7 +283,7 @@ class ApiUsersUserIndexHandler(BaseApiHandler): f"edited user {user_obj.username} (UID: {user_id})" f"with roles {user_obj.roles}" ), - server_id=0, + server_id=None, source_ip=self.get_remote_ip(), ) diff --git a/app/classes/web/routes/api/users/user/permissions.py b/app/classes/web/routes/api/users/user/permissions.py index b6c8703a..5981eaf4 100644 --- a/app/classes/web/routes/api/users/user/permissions.py +++ b/app/classes/web/routes/api/users/user/permissions.py @@ -52,6 +52,8 @@ class ApiUsersUserPermissionsHandler(BaseApiHandler): }, ) + counter_data = PermissionsCrafty.get_created_quantity_list(user_id) + self.finish_json( 200, { @@ -59,9 +61,9 @@ class ApiUsersUserPermissionsHandler(BaseApiHandler): "data": { "permissions": res_data.permissions, "counters": { - SERVER_CREATION: res_data.created_server, - USER_CONFIG: res_data.created_user, - ROLES_CONFIG: res_data.created_role, + SERVER_CREATION: counter_data["SERVER_CREATION"], + USER_CONFIG: counter_data["USER_CONFIG"], + ROLES_CONFIG: counter_data["ROLES_CONFIG"], }, "limits": { SERVER_CREATION: res_data.limit_server_creation, diff --git a/app/classes/web/server_handler.py b/app/classes/web/server_handler.py index 545029aa..11853a47 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 @@ -147,7 +147,7 @@ class ServerHandler(BaseHandler): page_data["server_api"] = False if page_data["online"]: page_data["server_api"] = self.helper.check_address_status( - "https://serverjars.com/api/fetchTypes" + "https://api.serverjars.com" ) page_data["server_types"] = self.controller.server_jars.get_serverjar_data() page_data["js_server_types"] = json.dumps( diff --git a/app/classes/web/tornado_handler.py b/app/classes/web/tornado_handler.py index f65f4fca..6285edfc 100644 --- a/app/classes/web/tornado_handler.py +++ b/app/classes/web/tornado_handler.py @@ -98,7 +98,6 @@ class Webserver: # let's verify we have an SSL cert self.helper.create_self_signed_cert() - http_port = self.helper.get_setting("http_port") https_port = self.helper.get_setting("https_port") debug_errors = self.helper.get_setting("show_errors") @@ -110,9 +109,6 @@ class Webserver: cookie_secret = self.helper.random_string_generator(32) HelpersManagement.set_cookie_secret(cookie_secret) - if not http_port and http_port != 0: - http_port = 8000 - if not https_port: https_port = 8443 @@ -125,7 +121,7 @@ class Webserver: ), } - logger.info(f"Starting Web Server on ports http:{http_port} https:{https_port}") + logger.info(f"Starting Web Server on ports https:{https_port}") asyncio.set_event_loop(asyncio.new_event_loop()) 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/classes/web/websocket_handler.py b/app/classes/web/websocket_handler.py index cde97584..3e426797 100644 --- a/app/classes/web/websocket_handler.py +++ b/app/classes/web/websocket_handler.py @@ -55,7 +55,7 @@ class WebSocketHandler(tornado.websocket.WebSocketHandler): self.controller.management.add_to_audit_log_raw( "unknown", 0, - 0, + None, "Someone tried to connect via WebSocket without proper authentication", self.get_remote_ip(), ) diff --git a/app/config/version.json b/app/config/version.json index 8dffa727..22de834f 100644 --- a/app/config/version.json +++ b/app/config/version.json @@ -1,5 +1,5 @@ { "major": 4, "minor": 3, - "sub": 2 + "sub": 3 } 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 5a54ecca..275d2000 100644 --- a/app/frontend/templates/public/login.html +++ b/app/frontend/templates/public/login.html @@ -111,8 +111,7 @@
- Crafty Control - {{data['version'] }} + Crafty Control
diff --git a/app/migrations/20240216_rework_servers_uuid.py b/app/migrations/20240216_rework_servers_uuid.py index c741a84c..facd0e42 100644 --- a/app/migrations/20240216_rework_servers_uuid.py +++ b/app/migrations/20240216_rework_servers_uuid.py @@ -54,9 +54,6 @@ def migrate(migrator: Migrator, database, **kwargs): database = db try: - logger.info("Migrating Data from Int to UUID (Type Change)") - Console.info("Migrating Data from Int to UUID (Type Change)") - # Changes on Server Table migrator.alter_column_type( Servers, @@ -87,11 +84,6 @@ def migrate(migrator: Migrator, database, **kwargs): ), ) - migrator.run() - - logger.info("Migrating Data from Int to UUID (Type Change) : SUCCESS") - Console.info("Migrating Data from Int to UUID (Type Change) : SUCCESS") - except Exception as ex: logger.error("Error while migrating Data from Int to UUID (Type Change)") logger.error(ex) @@ -101,118 +93,6 @@ def migrate(migrator: Migrator, database, **kwargs): last_migration.delete() return - 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(): - old_server_id = webhook.server_id_id - try: - server = Servers.get_by_id(old_server_id) - server_uuid = server.server_uuid - except: - server_uuid = old_server_id - Webhooks.update(server_id=server_uuid).where( - Webhooks.id == webhook.id - ).execute() - - # Changes on Schedules Log Table - for schedule in Schedules.select(): - old_server_id = schedule.server_id_id - try: - server = Servers.get_by_id(old_server_id) - server_uuid = server.server_uuid - except: - server_uuid = old_server_id - Schedules.update(server_id=server_uuid).where( - Schedules.schedule_id == schedule.schedule_id - ).execute() - - # Changes on Backups Log Table - for backup in Backups.select(): - old_server_id = backup.server_id_id - try: - server = Servers.get_by_id(old_server_id) - server_uuid = server.server_uuid - except: - server_uuid = old_server_id - Backups.update(server_id=server_uuid).where( - Backups.server_id == old_server_id - ).execute() - - # Changes on RoleServers Log Table - for role_servers in RoleServers.select(): - old_server_id = role_servers.server_id_id - try: - server = Servers.get_by_id(old_server_id) - server_uuid = server.server_uuid - except: - server_uuid = old_server_id - RoleServers.update(server_id=server_uuid).where( - RoleServers.role_id == role_servers.id - and RoleServers.server_id == old_server_id - ).execute() - - logger.info("Migrating Data from Int to UUID (Foreign Keys) : SUCCESS") - Console.info("Migrating Data from Int to UUID (Foreign Keys) : SUCCESS") - - except Exception as ex: - logger.error("Error while migrating Data from Int to UUID (Foreign Keys)") - logger.error(ex) - Console.error("Error while migrating Data from Int to UUID (Foreign Keys)") - Console.error(ex) - last_migration = MigrateHistory.get_by_id(MigrateHistory.select().count()) - last_migration.delete() - return - - try: - logger.info("Migrating Data from Int to UUID (Primary Keys)") - Console.info("Migrating Data from Int to UUID (Primary Keys)") - # Migrating servers from the old id type to the new one - for server in Servers.select(): - Servers.update(server_id=server.server_uuid).where( - Servers.server_id == server.server_id - ).execute() - - logger.info("Migrating Data from Int to UUID (Primary Keys) : SUCCESS") - Console.info("Migrating Data from Int to UUID (Primary Keys) : SUCCESS") - - except Exception as ex: - logger.error("Error while migrating Data from Int to UUID (Primary Keys)") - logger.error(ex) - Console.error("Error while migrating Data from Int to UUID (Primary Keys)") - Console.error(ex) - last_migration = MigrateHistory.get_by_id(MigrateHistory.select().count()) - last_migration.delete() - return - - # Changes on Server Table - logger.info("Migrating Data from Int to UUID (Removing UUID Field from Servers)") - Console.info("Migrating Data from Int to UUID (Removing UUID Field from Servers)") - migrator.drop_columns("servers", ["server_uuid"]) - migrator.run() - logger.info( - "Migrating Data from Int to UUID (Removing UUID Field from Servers) : SUCCESS" - ) - Console.info( - "Migrating Data from Int to UUID (Removing UUID Field from Servers) : SUCCESS" - ) - return diff --git a/app/migrations/20240217_rework_servers_uuid_part2.py b/app/migrations/20240217_rework_servers_uuid_part2.py new file mode 100644 index 00000000..eab04c28 --- /dev/null +++ b/app/migrations/20240217_rework_servers_uuid_part2.py @@ -0,0 +1,326 @@ +import datetime +import uuid +import peewee +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, +) +from app.classes.models.server_permissions import RoleServers +from app.classes.models.base_model import BaseModel + +logger = logging.getLogger(__name__) + + +def migrate(migrator: Migrator, database, **kwargs): + """ + Write your migrations here. + """ + db = database + + # ********************************************************************************** + # Servers New Model from Old (easier to migrate without dunmping Database) + # ********************************************************************************** + class Servers(peewee.Model): + server_id = peewee.CharField(primary_key=True, default=str(uuid.uuid4())) + created = peewee.DateTimeField(default=datetime.datetime.now) + server_uuid = peewee.CharField(default="", index=True) + server_name = peewee.CharField(default="Server", index=True) + path = peewee.CharField(default="") + backup_path = peewee.CharField(default="") + executable = peewee.CharField(default="") + log_path = peewee.CharField(default="") + execution_command = peewee.CharField(default="") + auto_start = peewee.BooleanField(default=0) + auto_start_delay = peewee.IntegerField(default=10) + crash_detection = peewee.BooleanField(default=0) + stop_command = peewee.CharField(default="stop") + executable_update_url = peewee.CharField(default="") + server_ip = peewee.CharField(default="127.0.0.1") + server_port = peewee.IntegerField(default=25565) + logs_delete_after = peewee.IntegerField(default=0) + type = peewee.CharField(default="minecraft-java") + show_status = peewee.BooleanField(default=1) + created_by = peewee.IntegerField(default=-100) + shutdown_timeout = peewee.IntegerField(default=60) + ignored_exits = peewee.CharField(default="0") + + class Meta: + table_name = "servers" + database = db + + this_migration = MigrateHistory.get_or_none( + MigrateHistory.name == "20240217_rework_servers_uuid_part2" + ) + if this_migration is not None: + Console.debug("Update database already done, skipping this part") + return + else: + servers_columns = db.get_columns("servers") + if not any( + column_data.name == "server_uuid" for column_data in servers_columns + ): + Console.debug( + "Servers.server_uuid already deleted in Crafty version 4.3.0, skipping this part" + ) + return + + 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(): + old_server_id = webhook.server_id_id + try: + server = Servers.get_by_id(old_server_id) + server_uuid = server.server_uuid + except: + server_uuid = old_server_id + Webhooks.update(server_id=server_uuid).where( + Webhooks.id == webhook.id + ).execute() + + # Changes on Schedules Log Table + for schedule in Schedules.select(): + old_server_id = schedule.server_id_id + try: + server = Servers.get_by_id(old_server_id) + server_uuid = server.server_uuid + except: + server_uuid = old_server_id + Schedules.update(server_id=server_uuid).where( + Schedules.schedule_id == schedule.schedule_id + ).execute() + + # Changes on Backups Log Table + for backup in Backups.select(): + old_server_id = backup.server_id_id + try: + server = Servers.get_by_id(old_server_id) + server_uuid = server.server_uuid + except: + server_uuid = old_server_id + Backups.update(server_id=server_uuid).where( + Backups.server_id == old_server_id + ).execute() + + # Changes on RoleServers Log Table + for role_servers in RoleServers.select(): + old_server_id = role_servers.server_id_id + try: + server = Servers.get_by_id(old_server_id) + server_uuid = server.server_uuid + except: + server_uuid = old_server_id + RoleServers.update(server_id=server_uuid).where( + RoleServers.role_id == role_servers.id + and RoleServers.server_id == old_server_id + ).execute() + + logger.info("Migrating Data from Int to UUID (Foreign Keys) : SUCCESS") + Console.info("Migrating Data from Int to UUID (Foreign Keys) : SUCCESS") + + except Exception as ex: + logger.error("Error while migrating Data from Int to UUID (Foreign Keys)") + logger.error(ex) + Console.error("Error while migrating Data from Int to UUID (Foreign Keys)") + Console.error(ex) + last_migration = MigrateHistory.get_by_id(MigrateHistory.select().count()) + last_migration.delete() + return + + try: + logger.info("Migrating Data from Int to UUID (Primary Keys)") + Console.info("Migrating Data from Int to UUID (Primary Keys)") + # Migrating servers from the old id type to the new one + for server in Servers.select(): + Servers.update(server_id=server.server_uuid).where( + Servers.server_id == server.server_id + ).execute() + + logger.info("Migrating Data from Int to UUID (Primary Keys) : SUCCESS") + Console.info("Migrating Data from Int to UUID (Primary Keys) : SUCCESS") + + except Exception as ex: + logger.error("Error while migrating Data from Int to UUID (Primary Keys)") + logger.error(ex) + Console.error("Error while migrating Data from Int to UUID (Primary Keys)") + Console.error(ex) + last_migration = MigrateHistory.get_by_id(MigrateHistory.select().count()) + last_migration.delete() + return + + return + + +def rollback(migrator: Migrator, database, **kwargs): + """ + Write your rollback migrations here. + """ + db = database + + # Condition to prevent running rollback each time we've got a rollback to do + this_migration = MigrateHistory.get_or_none( + MigrateHistory.name == "20240217_rework_servers_uuid_part2" + ) + if this_migration is None: + Console.debug("Update database already done, skipping this part") + return + + # ********************************************************************************** + # Servers New Model from Old (easier to migrate without dunmping Database) + # ********************************************************************************** + class Servers(peewee.Model): + server_id = peewee.CharField(primary_key=True, default=str(uuid.uuid4())) + created = peewee.DateTimeField(default=datetime.datetime.now) + server_uuid = peewee.CharField(default="", index=True) + server_name = peewee.CharField(default="Server", index=True) + path = peewee.CharField(default="") + backup_path = peewee.CharField(default="") + executable = peewee.CharField(default="") + log_path = peewee.CharField(default="") + execution_command = peewee.CharField(default="") + auto_start = peewee.BooleanField(default=0) + auto_start_delay = peewee.IntegerField(default=10) + crash_detection = peewee.BooleanField(default=0) + stop_command = peewee.CharField(default="stop") + executable_update_url = peewee.CharField(default="") + server_ip = peewee.CharField(default="127.0.0.1") + server_port = peewee.IntegerField(default=25565) + logs_delete_after = peewee.IntegerField(default=0) + type = peewee.CharField(default="minecraft-java") + show_status = peewee.BooleanField(default=1) + created_by = peewee.IntegerField(default=-100) + shutdown_timeout = peewee.IntegerField(default=60) + ignored_exits = peewee.CharField(default="0") + + class Meta: + table_name = "servers" + database = db + + try: + logger.info("Migrating Data from UUID to Int (Primary Keys)") + Console.info("Migrating Data from UUID to Int (Primary Keys)") + # Migrating servers from the old id type to the new one + new_id = 0 + for server in Servers.select(): + new_id += 1 + Servers.update(server_uuid=server.server_id).where( + Servers.server_id == server.server_id + ).execute() + Servers.update(server_id=new_id).where( + Servers.server_id == server.server_id + ).execute() + + logger.info("Migrating Data from UUID to Int (Primary Keys) : SUCCESS") + Console.info("Migrating Data from UUID to Int (Primary Keys) : SUCCESS") + + except Exception as ex: + logger.error("Error while migrating Data from UUID to Int (Primary Keys)") + logger.error(ex) + Console.error("Error while migrating Data from UUID to Int (Primary Keys)") + Console.error(ex) + last_migration = MigrateHistory.get_by_id(MigrateHistory.select().count()) + last_migration.delete() + return + + 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 + 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 + Webhooks.update(server_id=new_server_id).where( + Webhooks.id == webhook.id + ).execute() + + # Changes on Schedules Log Table + for schedule in Schedules.select(): + old_server_id = schedule.server_id_id + 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 + Schedules.update(server_id=new_server_id).where( + Schedules.schedule_id == schedule.schedule_id + ).execute() + + # Changes on Backups Log Table + for backup in Backups.select(): + old_server_id = backup.server_id_id + 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 + Backups.update(server_id=new_server_id).where( + Backups.server_id == old_server_id + ).execute() + + # Changes on RoleServers Log Table + for role_servers in RoleServers.select(): + old_server_id = role_servers.server_id_id + 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 + RoleServers.update(server_id=new_server_id).where( + RoleServers.role_id == role_servers.id + and RoleServers.server_id == old_server_id + ).execute() + + logger.info("Migrating Data from UUID to Int (Foreign Keys) : SUCCESS") + Console.info("Migrating Data from UUID to Int (Foreign Keys) : SUCCESS") + + except Exception as ex: + logger.error("Error while migrating Data from UUID to Int (Foreign Keys)") + logger.error(ex) + Console.error("Error while migrating Data from UUID to Int (Foreign Keys)") + Console.error(ex) + last_migration = MigrateHistory.get_by_id(MigrateHistory.select().count()) + last_migration.delete() + return + + return diff --git a/app/migrations/20240310_fixing_rework_servers_uuid.py b/app/migrations/20240310_fixing_rework_servers_uuid.py index 3c613e52..cbde833b 100644 --- a/app/migrations/20240310_fixing_rework_servers_uuid.py +++ b/app/migrations/20240310_fixing_rework_servers_uuid.py @@ -7,6 +7,7 @@ from app.classes.shared.console import Console from app.classes.shared.migration import Migrator, MigrateHistory from app.classes.models.management import Schedules, Backups from app.classes.models.server_permissions import RoleServers +from app.classes.models.servers import Servers logger = logging.getLogger(__name__) @@ -17,40 +18,7 @@ def migrate(migrator: Migrator, database, **kwargs): """ db = database - # ********************************************************************************** - # Servers New Model from Old (easier to migrate without dunmping Database) - # ********************************************************************************** - class Servers(peewee.Model): - server_id = peewee.CharField(primary_key=True, default=str(uuid.uuid4())) - created = peewee.DateTimeField(default=datetime.datetime.now) - server_name = peewee.CharField(default="Server", index=True) - path = peewee.CharField(default="") - backup_path = peewee.CharField(default="") - executable = peewee.CharField(default="") - log_path = peewee.CharField(default="") - execution_command = peewee.CharField(default="") - auto_start = peewee.BooleanField(default=0) - auto_start_delay = peewee.IntegerField(default=10) - crash_detection = peewee.BooleanField(default=0) - stop_command = peewee.CharField(default="stop") - executable_update_url = peewee.CharField(default="") - server_ip = peewee.CharField(default="127.0.0.1") - server_port = peewee.IntegerField(default=25565) - logs_delete_after = peewee.IntegerField(default=0) - type = peewee.CharField(default="minecraft-java") - show_status = peewee.BooleanField(default=1) - created_by = peewee.IntegerField(default=-100) - shutdown_timeout = peewee.IntegerField(default=60) - ignored_exits = peewee.CharField(default="0") - - class Meta: - table_name = "servers" - database = db - try: - logger.info("Migrating Data from Int to UUID (Fixing Issue)") - Console.info("Migrating Data from Int to UUID (Fixing Issue)") - # Changes on Servers Roles Table migrator.alter_column_type( RoleServers, @@ -87,10 +55,13 @@ def migrate(migrator: Migrator, database, **kwargs): ), ) - migrator.run() - - logger.info("Migrating Data from Int to UUID (Fixing Issue) : SUCCESS") - Console.info("Migrating Data from Int to UUID (Fixing Issue) : SUCCESS") + # Drop Column after migration + servers_columns = db.get_columns("servers") + if any(column_data.name == "server_uuid" for column_data in servers_columns): + Console.debug( + "Servers.server_uuid not deleted before Crafty version 4.3.2, skipping this part" + ) + migrator.drop_columns("servers", ["server_uuid"]) except Exception as ex: logger.error("Error while migrating Data from Int to UUID (Fixing Issue)") @@ -130,3 +101,7 @@ def rollback(migrator: Migrator, database, **kwargs): "server_id", peewee.IntegerField(null=True), ) + + migrator.add_columns( + "servers", server_uuid=peewee.CharField(default="", index=True) + ) # Recreating the column for roll back 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/translations/cs_CS.json b/app/translations/cs_CS.json index f12fe284..81f5ef21 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? ", @@ -218,6 +219,7 @@ "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", "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: {}", diff --git a/app/translations/de_DE.json b/app/translations/de_DE.json index c9ffea15..f1594576 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? ", @@ -203,6 +204,7 @@ "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", "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", diff --git a/app/translations/en_EN.json b/app/translations/en_EN.json index 5c48b873..a74250e1 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": { @@ -203,6 +203,7 @@ "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 ", + "return": "Return to Dashboard", "serverJars1": "Server JARs API unreachable. Please check", "serverJars2": "for the most up to date information.", "start-error": "Server {} failed to start with error code: {}", diff --git a/app/translations/es_ES.json b/app/translations/es_ES.json index 5f750fc7..361b896b 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? ", @@ -203,6 +204,7 @@ "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", "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: {}", diff --git a/app/translations/fr_FR.json b/app/translations/fr_FR.json index be429c83..24a4ee7e 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 ? ", @@ -203,6 +204,7 @@ "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", "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 : {}", diff --git a/app/translations/he_IL.json b/app/translations/he_IL.json index 7e2b1403..5d472597 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-איך תרצו לקרוא לאסימון ה", @@ -203,6 +204,7 @@ "not-downloaded": "לא הצלחנו למצוא את קובץ ההפעלה שלך. האם זה סיים להוריד? האם ההרשאות מוגדרות בשביל הפעלה?", "portReminder": "זיהינו שזו הפעם הראשונה ש-{} מופעל. הקפידו להעביר את היציאה {} דרך הנתב/חומת האש שלכם כדי להפוך אותה לנגישה מרחוק מהאינטרנט.", "privMsg": "וה", + "return": "חזרה לפאנל", "serverJars1": "API של צנצנות השרת אינו נגיש. אנא בדוק", "serverJars2": "למידע מעודכן ביותר.", "start-error": "השרת {} לא הצליח להתחיל עם קוד שגיאה: {}", diff --git a/app/translations/it_IT_incomplete.json b/app/translations/it_IT.json similarity index 77% rename from app/translations/it_IT_incomplete.json rename to app/translations/it_IT.json index 99b29d7e..16a863f3 100644 --- a/app/translations/it_IT_incomplete.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? ", @@ -53,6 +54,20 @@ "translationTitle": "Traduttore in lingua", "translator": "Traduttori" }, + "customLogin": { + "apply": "Applica", + "backgroundUpload": "Carica sfondo", + "customLoginPage": "Personalizza la pagina di accesso", + "delete": "Cancella", + "labelLoginImage": "Scegli lo sfondo della finestra di accesso", + "loginBackground": "Immagini di sfondo della finestra di accesso", + "loginImage": "Carica un'immagine di sfondo per la schermata di accesso.", + "loginOpacity": "Seleziona l'opacità della finestra di accesso", + "pageTitle": "Pagina di accesso personalizzata", + "preview": "Anteprima", + "select": "Seleziona", + "selectImage": "Seleziona un'immagine" + }, "dashboard": { "actions": "Azioni", "allServers": "Tutti i Server", @@ -75,6 +90,7 @@ "dashboard": "Pannello di Controllo", "delay-explained": "Il servizio/agente è stato avviato di recente e sta ritardando l'avvio del server di Minecraft", "host": "Host", + "installing": "Installazione...", "kill": "Termina il Processo", "killing": "Terminando il processo...", "lastBackup": "Ultimo:", @@ -96,6 +112,7 @@ "starting": "Avvio ritardato", "status": "Stato", "stop": "Stop", + "storage": "Archiviazione", "version": "Versione", "welcome": "Benvenuto su Crafty Controller" }, @@ -164,20 +181,34 @@ } }, "error": { + "agree": "Conferma", + "bedrockError": "I download di Bedrock non sono disponibili. Si prega di controllare", + "cancel": "Annulla", "contact": "Contact Crafty Control Support via Discord", + "craftyStatus": "Stato di Crafty", + "cronFormat": "Rilevato formato Cron non valido", "embarassing": "Oh my, well, this is embarrassing.", "error": "Error!", "eulaAgree": "Do you agree?", "eulaMsg": "You must agree to the EULA. A copy of the Minecraft EULA is linked under this message.", "eulaTitle": "Agree To EULA", + "fileError": "Il tipo di file deve essere un'immagine.", "fileTooLarge": "Caricamento fallito. File da caricare troppo grande. Contatta un amministratore di sistema per assistenza.", "hereIsTheError": "Here is the error", + "installerJava": "Installazione fallita {} : L'installazione di server Forge richiede Java. Abbiamo rilevato che Java non è installato. Installa Java e riprova.", "internet": "We have detected the machine running Crafty has no connection to the internet. Client connections to the server may be limited.", + "migration": "Lo spazio di archiviazione del server principale di Crafty verrà migrato in una nuova posizione. Durante questo periodo tutti gli avvii dei server sono stati sospesi. Aspetta mentre completiamo la migrazione", "no-file": "We can't seem to locate the requested file. Double check the path. Does Crafty have proper permissions?", + "noInternet": "Crafty ha problemi ad accedere a internet. La creazione di server è stata disabilitata. Controlla la tua connessione a internet e aggiorna la pagina.", "noJava": "Server {} failed to start with error code: We have detected Java is not installed. Please install java then start the server.", "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", + "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: {}", + "superError": "Devi essere un super utente per eseguire questa azione.", "terribleFailure": "What a Terrible Failure!" }, "footer": { @@ -186,10 +217,14 @@ "version": "Versione" }, "login": { + "defaultPath": "La password immessa è il percorso delle credenziali predefinite, non la password. Trova la password predefinita in quella posizione.", + "disabled": "Account utente disabilitato. Per ulteriori informazioni, contatta l'amministratore di sistema.", "forgotPassword": "Password dimenticata", + "incorrect": "nome utente o password errati", "login": "Accedi", "password": "Password", - "username": "Nome utente" + "username": "Nome utente", + "viewStatus": "Visualizza la pagina di stato pubblica" }, "notify": { "activityLog": "Registri di Attività", @@ -201,24 +236,38 @@ "preparingLogs": "Per favore aspetta mentre prepariamo i tuoi registri... Ti invieremo una notifica quando saranno pronti. Potrebbe volerci un po' per installazioni grosse.", "supportLogs": "Registri di supporto" }, + "offline": { + "offline": "Disconnesso", + "pleaseConnect": "Connettiti a internet per usare Crafty." + }, "panelConfig": { "adminControls": "Controllo admin", "allowedServers": "Server permessi", + "apply": "Applica", "assignedRoles": "Ruoli assegnati", "cancel": "Cancella", "clearComms": "Pulisci i comandi non eseguiti", + "custom": "Personalizza Crafty", "delete": "Elimina", "edit": "Modifica", + "enableLang": "Abilita tutte le lingue", "enabled": "Abilitato", + "globalExplain": "Dove Crafty memorizza tutti i file del tuo server. (Aggiungeremo il percorso con /servers/[uuid of server])", + "globalServer": "Directory dei server globali", + "json": "Config.json", + "match": "Le password devono corrispondere", "newRole": "Aggiungi nuovo ruolo", "newUser": "Aggiungi nuovo utente", + "noMounts": "Non mostrare supporti su Dash", "pageTitle": "Configurazioni del pannello", "role": "Ruolo", "roleUsers": "Utenti del ruolo", "roles": "Ruoli", "save": "Salva", + "select": "Seleziona", "superConfirm": "Procedi solo se vuoi che questo utente abbia accesso a TUTTO (tutti gli utenti, server, impostazioni del pannello, ecc...). Può anche revocare i tuoi poteri da superuser.", "superConfirmTitle": "Abilitare lo superuser? Sei sicuro?", + "title": "Configurazione di Crafty", "user": "Utente", "users": "Utenti" }, @@ -242,14 +291,17 @@ "roleTitle": "Impostazioni del ruolo", "roleUserName": "Nome Utente", "roleUsers": "Utenti con il ruolo: ", + "selectManager": "Seleziona un gestore per questo ruolo", "serverAccess": "Abilitato?", "serverName": "Nome del Server", "serversDesc": "Server a cui questo ruolo è consentito l'accesso" }, "serverBackups": { + "after": "Esegui il comando prima del backup", "backupAtMidnight": "Auto-backup a mezzanotte?", "backupNow": "Effettua il Backup Ora!", "backupTask": "Un'azione di backup è cominciata.", + "before": "Esegui il comando dopo il backup", "cancel": "Cancella", "clickExclude": "Clicca per selezionare le esclusioni", "compress": "Comprimi backup", @@ -280,6 +332,7 @@ "bePatientDeleteFiles": "Per favore sii paziente mentre rimuoviamo il tuo server dal pannello di Crafty e cancelliamo tutti i files. Questa schermata si chiuderà in pochi istanti.", "bePatientUpdate": "Per favore sii paziente mentre aggiorniamo il server. I tempi di download possono variare dalla tua velocità di internet.
Questa schermata si aggiornerà a breve", "cancel": "Cancella", + "countPlayers": "Includi il server nel conteggio totale dei giocatori", "crashTime": "Crash Timeout", "crashTimeDesc": "Quanto dobbiamo aspettare per considerare il tuo server crashato per colpa di un timeout?", "deleteFilesQuestion": "Eliminare i file del server dalla macchina?", @@ -289,6 +342,8 @@ "deleteServerQuestionMessage": "Sei sicuro di voler eliminare questo server? Dopo la conferma non puoi tornare indietro...", "exeUpdateURL": "URL di aggiornamento dell'eseguibile del server", "exeUpdateURLDesc": "URL di download diretto per gli aggiornamenti del server.", + "ignoredExits": "Codici di arresto anomalo ignorati", + "ignoredExitsExplain": "I codici di arresto anomalo per il rilevamento degli incidenti di Crafty dovrebbero essere ignorati come un normale 'stop' (separato da virgole)", "javaNoChange": "Non cambiare la versione di Java", "javaVersion": "Cambia la versione di Java attualmente in uso", "javaVersionDesc": "Se vuoi cambiare versione di Java, assicurati che la path dell'eseguibile di Java sia immessa tra 'apostrofi' ('java' di default è esclusa)", @@ -319,7 +374,13 @@ "serverPortDesc": "La Porta a cui Crafty si dovrà collegare per le statistiche", "serverStopCommand": "Comando d'arresto del server", "serverStopCommandDesc": "Comando inviato al server per l'arresto", + "showStatus": "Mostra nella pagina di stato pubblica", + "shutdownTimeout": "Tempo di attesa dello spegnimento", + "statsHint1": "la porta che sta usando il server dovrebbe essere qui. Questo è il modo in cui Crafty apre una connessione al tuo server per le statistiche.", + "statsHint2": "Ciò non modifica la porta del tuo server. È comunque necessario modificare la porta nel file di configurazione del server.", "stopBeforeDeleting": "Per favore arresta il server prima di eliminarlo", + "timeoutExplain1": "Per quanto tempo Crafty attenderà lo spegnimento del tuo server dopo averlo eseguito", + "timeoutExplain2": "comando prima di forzare l'arresto del processo.", "update": "Aggiorna l'eseguibile", "yesDelete": "Sì, elimina", "yesDeleteFiles": "Sì, elimina i files" @@ -345,8 +406,12 @@ "backup": "Backup", "config": "Configura", "files": "Files", + "filter": "Filtra registri", + "filterList": "Parole filtrate", "logs": "Registri", + "metrics": "Metrica", "playerControls": "Gestisci i giocatori", + "reset": "Ripristina scorrimento", "schedule": "Programma", "serverDetails": "Dettagli del Server", "terminal": "Terminale" @@ -383,6 +448,11 @@ "waitUpload": "Per favore aspetta mentre carichiamo i tuoi files... Potrebbe volerci un momento.", "yesDelete": "Sì, conosco le conseguenze" }, + "serverMetrics": { + "resetZoom": "Ripristina ingrandimento", + "zoomHint1": "Per ingrandire il grafico, tieni premuto il tasto Maiusc, quindi usa la rotella di scorrimento.", + "zoomHint2": "In alternativa, tieni premuto il tasto Maiusc, quindi fai clic e trascina l'area su cui desideri ingrandire." + }, "serverPlayerManagement": { "bannedPlayers": "Giocatori Banditi", "loadingBannedPlayers": "Carico i giocatori banditi", @@ -410,23 +480,44 @@ "parent-explain": "Quale azione dovrebbe far eseguire questa?", "reaction": "Reazione", "restart": "Riavvia il Server", + "select": "Seleziona Base / Cron / Reazione a catena", "start": "Avvia il server", "stop": "Arresta il Server", "time": "Orario", "time-explain": "A che ora vuoi eseguire la tua azione programmata?" }, "serverSchedules": { + "action": "Azione", "areYouSure": "Eliminare l'azione programmata?", "cancel": "Cancella", "cannotSee": "Non vedi tutto?", "cannotSeeOnMobile": "Prova a cliccare su un'azione programmata per tutti i dettagli.", + "child": "Schedario secondario con ID ", + "close": "Chiudi", + "command": "Comando", "confirm": "Conferma", - "confirmDelete": "Vuoi eliminare l'azione programmata? Non puoi tornare indietro." + "confirmDelete": "Vuoi eliminare l'azione programmata? Non puoi tornare indietro.", + "create": "Crea nuovo schedario", + "cron": "Stringa Cron", + "delete": "Cancella", + "details": "Dettagli dello schedario", + "edit": "Modifica", + "enabled": "Abilitato", + "every": "Tutto", + "interval": "Intervallo", + "name": "Nome", + "newSchedule": "Nuovo schedario", + "nextRun": "Prossima operazione", + "no": "No", + "no-schedule": "Non sono attualmente presenti schedari per questo server. Per iniziare, clicca", + "scheduledTasks": "Attività schedate", + "yes": "Sì" }, "serverStats": { "cpuUsage": "Utilizzo del Processore", "description": "Descrizione", "errorCalculatingUptime": "Errore nel calcolo dei tempi di operazione", + "loadingMotd": "Caricamento MOTD", "memUsage": "Utilizzo della memoria", "offline": "Offline", "online": "Online", @@ -444,6 +535,8 @@ "commandInput": "Inserisci il comando", "delay-explained": "Il servizio/agente è stato avviato di recente e sta ritardando l'avvio del server di Minecraft", "downloading": "Scaricando...", + "importing": "Importazione...", + "installing": "Installazione...", "restart": "Riavvia", "sendCommand": "Invia il comando", "start": "Avvia", @@ -468,6 +561,7 @@ "importServerButton": "Importa Server!", "importZip": "Importa da un File Zip", "importing": "Importando il Server...", + "labelZipFile": "Scegli il tuo file zip", "maxMem": "Memoria Massima", "minMem": "Memoria minima", "myNewServer": "Il mio nuovo Server", @@ -478,6 +572,7 @@ "save": "Salva", "selectRole": "Seleziona i Ruoli", "selectRoot": "Seleziona la directory radice dell'archivio", + "selectServer": "Seleziona un server", "selectType": "Seleziona un Tipo", "selectVersion": "Seleziona una versione", "selectZipDir": "Seleziona il percorso in memoria in cui scompatteremo i file", @@ -485,9 +580,14 @@ "serverName": "Nome Server", "serverPath": "Percorso del Server", "serverPort": "Porta del Server", + "serverSelect": "Server selezionato", "serverType": "Tipo di Server", + "serverUpload": "Carica server zippato", "serverVersion": "Versione del Server", "sizeInGB": "Dimensione in GB", + "unsupported": "Le versioni di Minecraft inferiori alla 1.8 non sono supportate da Crafty. Puoi comunque installarlo. I risultati varieranno.", + "uploadButton": "Carica", + "uploadZip": "Carica file zip per l'importazione del server", "zipPath": "Percorso del Server" }, "sidebar": { @@ -495,10 +595,20 @@ "credits": "Crediti", "dashboard": "Pannello di controllo", "documentation": "Documentazione", + "inApp": "Documenti in app", "navigation": "Navigazione", "newServer": "Crea un Nuovo Server", "servers": "Servers" }, + "startup": { + "almost": "Finalizzazione. Tieniti forte...", + "internals": "Configurazione e avvio dei componenti interni di Crafty", + "internet": "Controllo connessione a internet", + "server": "Inizializzazione ", + "serverInit": "Inizializzazione server", + "starting": "Avvio di Crafty...", + "tasks": "Avvio attività schedate" + }, "userConfig": { "apiKey": "Chiavi API", "auth": "Autorizzato? ", @@ -519,6 +629,7 @@ "lastLogin": "Ultimo accesso: ", "lastUpdate": "Ultimo aggiornamento: ", "leaveBlank": "Per modificare l'utente senza cambiare la passowrd, lascia il campo in bianco.", + "manager": "Gestore", "member": "È membro?", "notExist": "Non puoi eliminare qualcosa che non esiste!", "pageTitle": "Modifica Utente", @@ -527,6 +638,7 @@ "permName": "Nome del Permesso", "repeat": "Ripeti la Password", "roleName": "Nome del ruolo", + "selectManager": "Seleziona un gestore per l'utente", "super": "Super User", "userLang": "Linguaggio dell'utente", "userName": "Nome utente", @@ -534,6 +646,32 @@ "userRoles": "Ruoli dell'utente", "userRolesDesc": "Ruoli di cui l'utente fa parte.", "userSettings": "Impostazioni Utente", + "userTheme": "Tema IU", "uses": "Numero di usi permessi (-1==Nessun limite)" + }, + "webhooks": { + "areYouSureDel": "Sei sicuro di voler eliminare questo webhook?", + "areYouSureRun": "Sei sicuro di voler testare questo webhook?", + "backup_server": "Backup del server completato", + "bot_name": "Nome del bot", + "color": "Seleziona l'accentuazione del colore", + "crash_detected": "Il server si è arrestato in modo anomalo", + "edit": "Modifica", + "enabled": "Abilitato", + "jar_update": "Eseguibile del server aggiornato", + "kill": "Server arrestato", + "name": "Nome", + "new": "Nuovo webhook", + "newWebhook": "Nuovo webhook", + "no-webhook": "Attualmente non ci sono webhook in questo server. Per iniziare, clicca", + "run": "Testa il webhook", + "send_command": "Comando del server ricevuto", + "start_server": "Server avviato", + "stop_server": "Server spento", + "trigger": "Attivazione a", + "type": "Tipo di Webhook", + "url": "URL del Webhook", + "webhook_body": "Corpo del Webhook", + "webhooks": "Webhook" } } diff --git a/app/translations/lol_EN.json b/app/translations/lol_EN.json index aa07ff5f..22d1c268 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? ", @@ -203,6 +204,7 @@ "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", "serverJars1": "CAN'T TALK TO SERVER JARS API. CHECKZ", "serverJars2": "TO SEE NEWZ STUFFZ.", "start-error": "CHAIR {} FAILD 2 START WIF OOF CODE: {}", diff --git a/app/translations/lv_LV.json b/app/translations/lv_LV.json index d337d38f..b66d9dbc 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)? ", @@ -204,6 +205,7 @@ "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", "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: {}", diff --git a/app/translations/nl_BE.json b/app/translations/nl_BE.json index d20e209c..9ccf4f2b 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? ", @@ -203,6 +204,7 @@ "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", "serverJars1": "Server JARs API niet bereikbaar. Controleer alstublieft", "serverJars2": "voor de meest recente informatie.", "start-error": "Server {} kan niet starten met foutcode: {}", diff --git a/app/translations/pl_PL.json b/app/translations/pl_PL.json index ade8c5a5..9229b474 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? ", @@ -203,6 +204,7 @@ "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", "serverJars1": "API Server Jars jest niedostępne. Proszę sprawdź", "serverJars2": "dla najnowzsych informacji.", "start-error": "Serwer {} nie mógł się odpalić z powodu: {}", diff --git a/app/translations/th_TH.json b/app/translations/th_TH.json index bbb82dae..e6a8be73 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 นี้ว่าอะไร ? ", @@ -203,6 +204,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..5da06afc 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?", @@ -203,6 +204,7 @@ "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", "serverJars1": "Sunucu JARs API'ına erişilemiyor.", "serverJars2": "en güncel bilgilere sahiptir", "start-error": "{} sunucusu başlamatılamadı. Hata kodu: {}", diff --git a/app/translations/uk_UA.json b/app/translations/uk_UA.json index 74b683dd..eff48185 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 токен?", @@ -203,6 +204,7 @@ "not-downloaded": "Здається, ми не можемо знайти ваш виконуваний файл. Чи завершилось завантаження? Чи встановлено дозволи на виконуваний файл?", "portReminder": "Ми виявили це вперше {} був запущений. Обов’язково перенаправте порт {} через ваш маршрутизатор/брандмауер, щоб зробити це доступним з Інтернету.", "privMsg": "і ", + "return": "Повернутись до панелі", "serverJars1": "API сервера JAR недоступний. Будь ласка, перевірте", "serverJars2": "для найактуальнішої інформації.", "start-error": "Сервер {} не запустився через помилку: {}", diff --git a/app/translations/zh_CN.json b/app/translations/zh_CN.json index 0b817b7e..cf9f4b1c 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 令牌叫做什么?", @@ -203,6 +204,7 @@ "not-downloaded": "我们似乎找不到您的可执行文件。它下载完成了吗?可执行文件的权限设置正确了吗?", "portReminder": "我们检测到这是你首次运行 {}。请确保从您的路由器/防火墙转发 {} 端口,以使程序可以从公网远程访问。", "privMsg": "以及", + "return": "返回仪表板", "serverJars1": "无法访问服务器 JAR API。请检查", "serverJars2": "以获取最新信息。", "start-error": "服务器 {} 启动失败,错误代码为:{}", diff --git a/requirements.txt b/requirements.txt index e3a58bad..ed0f7698 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,5 +18,5 @@ termcolor==1.1 tornado==6.3.3 tzlocal==5.1 jsonschema==4.19.1 -orjson==3.9.7 +orjson==3.9.15 prometheus-client==0.17.1 diff --git a/sonar-project.properties b/sonar-project.properties index a5f5e258..892bb292 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.2 +sonar.projectVersion=4.3.3 sonar.python.version=3.9, 3.10, 3.11 sonar.exclusions=app/migrations/**, app/frontend/static/assets/vendors/**