diff --git a/.gitlab/lint.yml b/.gitlab/lint.yml index bc797808..37649e1a 100644 --- a/.gitlab/lint.yml +++ b/.gitlab/lint.yml @@ -5,7 +5,7 @@ yamllint: stage: lint image: registry.gitlab.com/pipeline-components/yamllint:latest tags: - - docker + - saas-linux-medium-amd64 rules: - if: "$CODE_QUALITY_DISABLED" when: never @@ -18,7 +18,7 @@ jsonlint: stage: lint image: registry.gitlab.com/pipeline-components/jsonlint:latest tags: - - docker + - saas-linux-medium-amd64 rules: - if: "$CODE_QUALITY_DISABLED" when: never @@ -33,7 +33,7 @@ black: stage: lint image: registry.gitlab.com/pipeline-components/black:latest tags: - - docker + - saas-linux-medium-amd64 rules: - if: "$CODE_QUALITY_DISABLED" when: never @@ -46,7 +46,7 @@ pylint: stage: lint image: registry.gitlab.com/pipeline-components/pylint:latest tags: - - docker + - saas-linux-medium-amd64 rules: - if: "$CODE_QUALITY_DISABLED" when: never @@ -69,7 +69,7 @@ sonarcloud-check: name: sonarsource/sonar-scanner-cli:latest entrypoint: [""] tags: - - docker + - saas-linux-medium-amd64 rules: - if: "$SONAR_TOKEN == null" when: never @@ -91,7 +91,7 @@ lang-check: stage: lint image: alpine:latest tags: - - docker + - saas-linux-medium-amd64 rules: - if: "$CODE_QUALITY_DISABLED" when: never diff --git a/CHANGELOG.md b/CHANGELOG.md index 3be9e1a1..7f0699ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,17 @@ # Changelog ## --- [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 +- Reset query arguments on login if `?next` is not available ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/750)) +- Fix child schedule failing to load after del parent ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/753)) ### Tweaks -TBD +- Add link to go back to dashboard on error page ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/743)) +- Set audit logging to logfile instead of DB ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/751)) ### Lang -TBD +- Changes of phrase in `cs_CS` translation ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/749))

## --- [4.3.2] - 2024/04/07 diff --git a/app/classes/controllers/management_controller.py b/app/classes/controllers/management_controller.py index 7085b503..cab755b6 100644 --- a/app/classes/controllers/management_controller.py +++ b/app/classes/controllers/management_controller.py @@ -95,9 +95,6 @@ class ManagementController: # ********************************************************************************** # Audit_Log Methods # ********************************************************************************** - @staticmethod - def get_activity_log(): - return HelpersManagement.get_activity_log() def add_to_audit_log(self, user_id, log_msg, server_id=None, source_ip=None): return self.management_helper.add_to_audit_log( diff --git a/app/classes/controllers/server_perms_controller.py b/app/classes/controllers/server_perms_controller.py index 37893e9e..4586b4aa 100644 --- a/app/classes/controllers/server_perms_controller.py +++ b/app/classes/controllers/server_perms_controller.py @@ -17,6 +17,10 @@ class ServerPermsController: def get_server_user_list(server_id): return PermissionsServers.get_server_user_list(server_id) + @staticmethod + def get_permissions(permissions_mask): + return PermissionsServers.get_permissions(permissions_mask) + @staticmethod def list_defined_permissions(): permissions_list = PermissionsServers.get_permissions_list() @@ -61,6 +65,22 @@ class ServerPermsController: def get_permissions_mask(role_id, server_id): return PermissionsServers.get_permissions_mask(role_id, server_id) + @staticmethod + def get_lowest_api_perm_mask(user_server_permissions_mask, api_key_permssions_mask): + mask = "" + # If this isn't an API key we'll know the request came from basic + # authentication and ignore the API key permissions mask. + if not api_key_permssions_mask: + return user_server_permissions_mask + for _index, (user_perm, api_perm) in enumerate( + zip(user_server_permissions_mask, api_key_permssions_mask) + ): + if user_perm == "1" and api_perm == "1": + mask += "1" + else: + mask += "0" + return mask + @staticmethod def set_permission( permission_mask, permission_tested: EnumPermissionsServer, value @@ -82,6 +102,11 @@ class ServerPermsController: def get_api_key_permissions_list(key: ApiKeys, server_id: str): return PermissionsServers.get_api_key_permissions_list(key, server_id) + @staticmethod + def get_user_permissions_mask(user_id: str, server_id: str): + user = HelperUsers.get_user_model(user_id) + return PermissionsServers.get_user_permissions_mask(user, server_id) + @staticmethod def get_authorized_servers_stats_from_roles(user_id): user_roles = HelperUsers.get_user_roles_id(user_id) diff --git a/app/classes/logging/log_formatter.py b/app/classes/logging/log_formatter.py new file mode 100644 index 00000000..e3f2b4f7 --- /dev/null +++ b/app/classes/logging/log_formatter.py @@ -0,0 +1,53 @@ +import logging +import logging.config +import json +from datetime import datetime + + +class JsonEncoderStrFallback(json.JSONEncoder): + def default(self, o): + try: + return super().default(o) + except TypeError as exc: + if "not JSON serializable" in str(exc): + return str(o) + raise + + +class JsonEncoderDatetime(JsonEncoderStrFallback): + def default(self, o): + if isinstance(o, datetime): + return o.strftime("%Y-%m-%dT%H:%M:%S%z") + + return super().default(o) + + +class JsonFormatter(logging.Formatter): + def formatTime(self, record, datefmt=None): + """ + Override formatTime to customize the time format. + """ + timestamp = datetime.fromtimestamp(record.created) + if datefmt: + # Use the specified date format + return timestamp.strftime(datefmt) + # Default date format: YYYY-MM-DD HH:MM:SS,mmm + secs = int(record.msecs) + return f"{timestamp.strftime('%Y-%m-%d %H:%M:%S')},{secs:03d}" + + def format(self, record): + log_data = { + "level": record.levelname, + "time": self.formatTime(record), + "log_msg": record.getMessage(), + } + + # Filter out standard log record attributes and include only custom ones + custom_attrs = ["user_name", "user_id", "server_id", "source_ip"] + extra_attrs = { + key: value for key, value in record.__dict__.items() if key in custom_attrs + } + + # Merge extra attributes with log data + log_data.update(extra_attrs) + return json.dumps(log_data) diff --git a/app/classes/models/crafty_permissions.py b/app/classes/models/crafty_permissions.py index 7430f332..e7a159d9 100644 --- a/app/classes/models/crafty_permissions.py +++ b/app/classes/models/crafty_permissions.py @@ -187,7 +187,7 @@ class PermissionsCrafty: @staticmethod def get_api_key_permissions_list(key: ApiKeys): user = HelperUsers.get_user(key.user_id) - if user["superuser"] and key.superuser: + if user["superuser"] and key.full_access: return PermissionsCrafty.get_permissions_list() if user["superuser"]: # User is superuser but API key isn't diff --git a/app/classes/models/management.py b/app/classes/models/management.py index ffe207c2..e59dd39c 100644 --- a/app/classes/models/management.py +++ b/app/classes/models/management.py @@ -16,28 +16,10 @@ from app.classes.models.base_model import BaseModel from app.classes.models.users import HelperUsers from app.classes.models.servers import Servers from app.classes.models.server_permissions import PermissionsServers -from app.classes.shared.main_models import DatabaseShortcuts from app.classes.shared.websocket_manager import WebSocketManager logger = logging.getLogger(__name__) - - -# ********************************************************************************** -# Audit_Log Class -# ********************************************************************************** -class AuditLog(BaseModel): - audit_id = AutoField() - created = DateTimeField(default=datetime.datetime.now) - user_name = CharField(default="") - user_id = IntegerField(default=0, index=True) - source_ip = CharField(default="127.0.0.1") - server_id = ForeignKeyField( - Servers, backref="audit_server", null=True - ) # When auditing global events, use server ID null - log_msg = TextField(default="") - - class Meta: - table_name = "audit_log" +auth_logger = logging.getLogger("audit_log") # ********************************************************************************** @@ -149,10 +131,6 @@ class HelpersManagement: # ********************************************************************************** # Audit_Log Methods # ********************************************************************************** - @staticmethod - def get_activity_log(): - query = AuditLog.select() - return DatabaseShortcuts.return_db_rows(query) def add_to_audit_log(self, user_id, log_msg, server_id=None, source_ip=None): logger.debug(f"Adding to audit log User:{user_id} - Message: {log_msg} ") @@ -166,50 +144,28 @@ class HelpersManagement: WebSocketManager().broadcast_user(user, "notification", audit_msg) except Exception as e: logger.error(f"Error broadcasting to user {user} - {e}") - - AuditLog.insert( - { - AuditLog.user_name: user_data["username"], - AuditLog.user_id: user_id, - AuditLog.server_id: server_id, - AuditLog.log_msg: audit_msg, - AuditLog.source_ip: source_ip, - } - ).execute() - # deletes records when there's more than 300 - ordered = AuditLog.select().order_by(+AuditLog.created) - for item in ordered: - if not self.helper.get_setting("max_audit_entries"): - max_entries = 300 - else: - max_entries = self.helper.get_setting("max_audit_entries") - if AuditLog.select().count() > max_entries: - AuditLog.delete().where(AuditLog.audit_id == item.audit_id).execute() - else: - return + auth_logger.info( + str(log_msg), + extra={ + "user_name": user_data["username"], + "user_id": user_id, + "server_id": server_id, + "source_ip": source_ip, + }, + ) def add_to_audit_log_raw(self, user_name, user_id, server_id, log_msg, source_ip): - AuditLog.insert( - { - AuditLog.user_name: user_name, - AuditLog.user_id: user_id, - AuditLog.server_id: server_id, - AuditLog.log_msg: log_msg, - AuditLog.source_ip: source_ip, - } - ).execute() - # deletes records when there's more than 300 - ordered = AuditLog.select().order_by(+AuditLog.created) - for item in ordered: - # configurable through app/config/config.json - if not self.helper.get_setting("max_audit_entries"): - max_entries = 300 - else: - max_entries = self.helper.get_setting("max_audit_entries") - if AuditLog.select().count() > max_entries: - AuditLog.delete().where(AuditLog.audit_id == item.audit_id).execute() - else: - return + if isinstance(server_id, Servers) and server_id is not None: + server_id = server_id.server_id + auth_logger.info( + str(log_msg), + extra={ + "user_name": user_name, + "user_id": user_id, + "server_id": server_id, + "source_ip": source_ip, + }, + ) @staticmethod def create_crafty_row(): diff --git a/app/classes/models/server_permissions.py b/app/classes/models/server_permissions.py index 56f9d8ac..12301e30 100644 --- a/app/classes/models/server_permissions.py +++ b/app/classes/models/server_permissions.py @@ -264,7 +264,7 @@ class PermissionsServers: @staticmethod def get_api_key_permissions_list(key: ApiKeys, server_id: str): user = HelperUsers.get_user(key.user_id) - if user["superuser"] and key.superuser: + if user["superuser"] and key.full_access: return PermissionsServers.get_permissions_list() roles_list = HelperUsers.get_user_roles_id(user["user_id"]) role_server = ( diff --git a/app/classes/models/users.py b/app/classes/models/users.py index 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/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 0db13ecb..c0e55673 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 @@ -1239,9 +1239,11 @@ class PanelHandler(BaseHandler): page_data["schedule"]["interval_type"] = schedule.interval_type if schedule.interval_type == "reaction": difficulty = "reaction" - page_data["parent"] = self.controller.management.get_scheduled_task( - schedule.parent - ) + page_data["parent"] = None + if schedule.parent: + page_data["parent"] = self.controller.management.get_scheduled_task( + schedule.parent + ) elif schedule.cron_string == "": difficulty = "basic" page_data["parent"] = None @@ -1358,6 +1360,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") @@ -1505,8 +1510,6 @@ class PanelHandler(BaseHandler): template = "panel/panel_edit_role.html" elif page == "activity_logs": - page_data["audit_logs"] = self.controller.management.get_activity_log() - template = "panel/activity_logs.html" elif page == "download_file": diff --git a/app/classes/web/public_handler.py b/app/classes/web/public_handler.py index 21e2d495..a3d89d25 100644 --- a/app/classes/web/public_handler.py +++ b/app/classes/web/public_handler.py @@ -48,7 +48,10 @@ class PublicHandler(BaseHandler): } if self.request.query: - page_data["query"] = self.request.query_arguments.get("next")[0].decode() + request_query = self.request.query_arguments.get("next") + if not request_query: + self.redirect("/login") + page_data["query"] = request_query[0].decode() # sensible defaults template = "public/404.html" diff --git a/app/classes/web/routes/api/crafty/clogs/index.py b/app/classes/web/routes/api/crafty/clogs/index.py index 97a24a34..f42d7e2c 100644 --- a/app/classes/web/routes/api/crafty/clogs/index.py +++ b/app/classes/web/routes/api/crafty/clogs/index.py @@ -1,3 +1,5 @@ +import os +import json from app.classes.web.base_api_handler import BaseApiHandler @@ -22,9 +24,17 @@ class ApiCraftyLogIndexHandler(BaseApiHandler): raise NotImplementedError if log_type == "audit": + with open( + os.path.join(self.controller.project_root, "logs", "audit.log"), + "r", + encoding="utf-8", + ) as f: + log_lines = [json.loads(line) for line in f] + rev_log_lines = log_lines[::-1] + return self.finish_json( 200, - {"status": "ok", "data": self.controller.management.get_activity_log()}, + {"status": "ok", "data": rev_log_lines}, ) if log_type == "session": diff --git a/app/classes/web/routes/api/servers/server/action.py b/app/classes/web/routes/api/servers/server/action.py index 526899b5..aba06da3 100644 --- a/app/classes/web/routes/api/servers/server/action.py +++ b/app/classes/web/routes/api/servers/server/action.py @@ -18,13 +18,14 @@ class ApiServersServerActionHandler(BaseApiHandler): if server_id not in [str(x["server_id"]) for x in auth_data[0]]: # if the user doesn't have access to the server, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) - - if ( - EnumPermissionsServer.COMMANDS - not in self.controller.server_perms.get_user_id_permissions_list( + mask = self.controller.server_perms.get_lowest_api_perm_mask( + self.controller.server_perms.get_user_permissions_mask( auth_data[4]["user_id"], server_id - ) - ): + ), + auth_data[5], + ) + server_permissions = self.controller.server_perms.get_permissions(mask) + if EnumPermissionsServer.COMMANDS not in server_permissions: # if the user doesn't have Commands permission, return an error return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) diff --git a/app/classes/web/routes/api/servers/server/backups/backup/index.py b/app/classes/web/routes/api/servers/server/backups/backup/index.py index 70ceb2b2..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"}) 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/user/api.py b/app/classes/web/routes/api/users/user/api.py index 9bdafadf..2abb8463 100644 --- a/app/classes/web/routes/api/users/user/api.py +++ b/app/classes/web/routes/api/users/user/api.py @@ -75,7 +75,7 @@ class ApiUsersUserKeyHandler(BaseApiHandler): "name": key.name, "server_permissions": key.server_permissions, "crafty_permissions": key.crafty_permissions, - "superuser": key.superuser, + "full_access": key.full_access, } ) self.finish_json( @@ -99,7 +99,7 @@ class ApiUsersUserKeyHandler(BaseApiHandler): "type": "string", "pattern": "^[01]{3}$", # 8 bits, see EnumPermissionsCrafty }, - "superuser": {"type": "boolean"}, + "full_access": {"type": "boolean"}, }, "additionalProperties": False, "minProperties": 1, @@ -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"], ) diff --git a/app/classes/web/server_handler.py b/app/classes/web/server_handler.py index 62b76f3c..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 diff --git a/app/classes/web/upload_handler.py b/app/classes/web/upload_handler.py index 0667dd12..747fa63b 100644 --- a/app/classes/web/upload_handler.py +++ b/app/classes/web/upload_handler.py @@ -42,7 +42,7 @@ class UploadHandler(BaseHandler): if self.upload_type == "server_import": superuser = exec_user["superuser"] if api_key is not None: - superuser = superuser and api_key.superuser + superuser = superuser and api_key.full_access user_id = exec_user["user_id"] stream_size_value = self.helper.get_setting("stream_size_GB") @@ -133,7 +133,7 @@ class UploadHandler(BaseHandler): elif self.upload_type == "background": superuser = exec_user["superuser"] if api_key is not None: - superuser = superuser and api_key.superuser + superuser = superuser and api_key.full_access user_id = exec_user["user_id"] stream_size_value = self.helper.get_setting("stream_size_GB") @@ -212,7 +212,7 @@ class UploadHandler(BaseHandler): server_id = self.get_argument("server_id", None) superuser = exec_user["superuser"] if api_key is not None: - superuser = superuser and api_key.superuser + superuser = superuser and api_key.full_access user_id = exec_user["user_id"] stream_size_value = self.helper.get_setting("stream_size_GB") diff --git a/app/config/logging.json b/app/config/logging.json index fd1173eb..d0a20cdf 100644 --- a/app/config/logging.json +++ b/app/config/logging.json @@ -14,6 +14,9 @@ "auth": { "format": "%(asctime)s - [AUTH] - %(levelname)s - %(message)s" }, + "audit": { + "()": "app.classes.logging.log_formatter.JsonFormatter" + }, "cmd_queue": { "format": "%(asctime)s - [CMD_QUEUE] - %(levelname)s - %(message)s" } @@ -70,6 +73,14 @@ "maxBytes": 10485760, "backupCount": 20, "encoding": "utf8" + }, + "audit_log_handler": { + "class": "logging.handlers.RotatingFileHandler", + "formatter": "audit", + "filename": "logs/audit.log", + "maxBytes": 10485760, + "backupCount": 20, + "encoding": "utf8" } }, "loggers": { @@ -108,6 +119,12 @@ "cmd_queue_file_handler" ], "propagate": false + }, + "audit_log": { + "level": "INFO", + "handlers": [ + "audit_log_handler" + ] } } } \ No newline at end of file diff --git a/app/frontend/templates/panel/activity_logs.html b/app/frontend/templates/panel/activity_logs.html index 389edaf5..e053e50e 100644 --- a/app/frontend/templates/panel/activity_logs.html +++ b/app/frontend/templates/panel/activity_logs.html @@ -36,25 +36,21 @@ - - - - - + + + + + - {% for row in data['audit_logs'] %} - - - - - - {% end %}
UsernameTimeActionServer IDIPTimeUsernameActionServer IDIP
{{ row['user_name'] }} - {{ row['created'].strftime('%Y-%m-%d %H:%M:%S') }} + + Crafty Logo, Crafty is loading

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

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

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

diff --git a/app/frontend/templates/public/login.html b/app/frontend/templates/public/login.html index 275d2000..9c7d3f91 100644 --- a/app/frontend/templates/public/login.html +++ b/app/frontend/templates/public/login.html @@ -171,7 +171,6 @@ //Create an object from the form data entries let formDataObject = Object.fromEntries(formData.entries()); - console.log(formDataObject) let res = await fetch(`/login`, { method: 'POST', headers: { diff --git a/app/migrations/20240216_rework_servers_uuid.py b/app/migrations/20240216_rework_servers_uuid.py index facd0e42..7850346d 100644 --- a/app/migrations/20240216_rework_servers_uuid.py +++ b/app/migrations/20240216_rework_servers_uuid.py @@ -6,7 +6,6 @@ import logging from app.classes.shared.console import Console from app.classes.shared.migration import Migrator, MigrateHistory from app.classes.models.management import ( - AuditLog, Webhooks, Schedules, Backups, @@ -61,17 +60,6 @@ def migrate(migrator: Migrator, database, **kwargs): peewee.CharField(primary_key=True, default=str(uuid.uuid4())), ) - # Changes on Audit Log Table - migrator.alter_column_type( - AuditLog, - "server_id", - peewee.ForeignKeyField( - Servers, - backref="audit_server", - null=True, - field=peewee.CharField(primary_key=True, default=str(uuid.uuid4())), - ), - ) # Changes on Webhook Table migrator.alter_column_type( Webhooks, @@ -109,13 +97,6 @@ def rollback(migrator: Migrator, database, **kwargs): peewee.AutoField(), ) - # Changes on Audit Log Table - migrator.alter_column_type( - AuditLog, - "server_id", - peewee.IntegerField(default=None, index=True), - ) - # Changes on Webhook Table migrator.alter_column_type( Webhooks, diff --git a/app/migrations/20240217_rework_servers_uuid_part2.py b/app/migrations/20240217_rework_servers_uuid_part2.py index eab04c28..a4b01c45 100644 --- a/app/migrations/20240217_rework_servers_uuid_part2.py +++ b/app/migrations/20240217_rework_servers_uuid_part2.py @@ -6,7 +6,6 @@ import logging from app.classes.shared.console import Console from app.classes.shared.migration import Migrator, MigrateHistory from app.classes.models.management import ( - AuditLog, Webhooks, Schedules, Backups, @@ -73,20 +72,6 @@ def migrate(migrator: Migrator, database, **kwargs): try: logger.info("Migrating Data from Int to UUID (Foreign Keys)") Console.info("Migrating Data from Int to UUID (Foreign Keys)") - # Changes on Audit Log Table - for audit_log in AuditLog.select(): - old_server_id = audit_log.server_id_id - if old_server_id == "0" or old_server_id is None: - server_uuid = None - else: - try: - server = Servers.get_by_id(old_server_id) - server_uuid = server.server_uuid - except: - server_uuid = old_server_id - AuditLog.update(server_id=server_uuid).where( - AuditLog.audit_id == audit_log.audit_id - ).execute() # Changes on Webhooks Log Table for webhook in Webhooks.select(): @@ -247,21 +232,6 @@ def rollback(migrator: Migrator, database, **kwargs): try: logger.info("Migrating Data from UUID to Int (Foreign Keys)") Console.info("Migrating Data from UUID to Int (Foreign Keys)") - # Changes on Audit Log Table - for audit_log in AuditLog.select(): - old_server_id = audit_log.server_id_id - if old_server_id is None: - new_server_id = 0 - else: - try: - server = Servers.get_or_none(Servers.server_uuid == old_server_id) - new_server_id = server.server_id - except: - new_server_id = old_server_id - AuditLog.update(server_id=new_server_id).where( - AuditLog.audit_id == audit_log.audit_id - ).execute() - # Changes on Webhooks Log Table for webhook in Webhooks.select(): old_server_id = webhook.server_id_id diff --git a/app/migrations/20240317_apikey_full_access.py b/app/migrations/20240317_apikey_full_access.py new file mode 100644 index 00000000..6ae223e1 --- /dev/null +++ b/app/migrations/20240317_apikey_full_access.py @@ -0,0 +1,17 @@ +# Generated by database migrator +import peewee + + +def migrate(migrator, database, **kwargs): + migrator.rename_column("api_keys", "superuser", "full_access") + + """ + Write your migrations here. + """ + + +def rollback(migrator, database, **kwargs): + migrator.rename_column("api_keys", "full_access", "superuser") + """ + Write your rollback migrations here. + """ diff --git a/app/migrations/20240420_audit_log_drop.py b/app/migrations/20240420_audit_log_drop.py new file mode 100644 index 00000000..be153f11 --- /dev/null +++ b/app/migrations/20240420_audit_log_drop.py @@ -0,0 +1,34 @@ +import peewee +import datetime +from peewee import ( + AutoField, + DateTimeField, + CharField, + IntegerField, + ForeignKeyField, + TextField, +) + +from app.classes.shared.server import Servers + + +def migrate(migrator, db): + migrator.drop_table("audit_log") + + +def rollback(migrator, db): + class AuditLog(peewee.Model): + audit_id = AutoField() + created = DateTimeField(default=datetime.datetime.now) + user_name = CharField(default="") + user_id = IntegerField(default=0, index=True) + source_ip = CharField(default="127.0.0.1") + server_id = ForeignKeyField( + Servers, backref="audit_server", null=True + ) # When auditing global events, use server ID null + log_msg = TextField(default="") + + class Meta: + table_name = "audit_log" + + migrator.create_table(AuditLog) diff --git a/app/translations/cs_CS.json b/app/translations/cs_CS.json index 2229d5cb..79026d51 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? ", @@ -116,6 +117,7 @@ "welcome": "Vítejte v Crafty Controlleru" }, "datatables": { + "loadingRecords": "Načítání...", "i18n": { "aria": { "sortAscending": ": aktivace řazení sloupce vzestupně", @@ -218,6 +220,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: {}", @@ -612,7 +615,7 @@ "credits": "Zásluhy", "dashboard": "Ovládací panel", "documentation": "Dokumentace", - "inApp": "V app dokumentaci", + "inApp": "V lokalní dokumentaci", "navigation": "Navigace", "newServer": "Vytvořit nový server", "servers": "Servery" @@ -691,4 +694,4 @@ "webhook_body": "Webhook Body", "webhooks": "Webhooky" } -} +} \ No newline at end of file diff --git a/app/translations/de_DE.json b/app/translations/de_DE.json index c9ffea15..c8358427 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? ", @@ -116,6 +117,7 @@ "welcome": "Willkommen bei Crafty Controller" }, "datatables": { + "loadingRecords": "Laden...", "i18n": { "aria": { "sortAscending": ": Aktivieren, um die Spalte aufsteigend zu sortieren", @@ -203,6 +205,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", @@ -672,4 +675,4 @@ "webhook_body": "Webhook-Inhalt", "webhooks": "Webhooks" } -} +} \ No newline at end of file diff --git a/app/translations/en_EN.json b/app/translations/en_EN.json index 5c48b873..a0723002 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": { @@ -116,6 +116,7 @@ "welcome": "Welcome to Crafty Controller" }, "datatables": { + "loadingRecords": "Loading...", "i18n": { "aria": { "sortAscending": ": activate to sort column ascending", @@ -203,6 +204,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: {}", @@ -671,4 +673,4 @@ "webhook_body": "Webhook Body", "webhooks": "Webhooks" } -} +} \ No newline at end of file diff --git a/app/translations/es_ES.json b/app/translations/es_ES.json index 5f750fc7..7531ed1c 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? ", @@ -116,6 +117,7 @@ "welcome": "Bienvenido a Crafty Controller" }, "datatables": { + "loadingRecords": "Cargando...", "i18n": { "aria": { "sortAscending": ": activar para ordenar las columnas de manera ascendente", @@ -203,6 +205,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: {}", @@ -672,4 +675,4 @@ "webhook_body": "Cuerpo del Webhook", "webhooks": "Webhooks" } -} +} \ No newline at end of file diff --git a/app/translations/fi_FI_incomplete.json b/app/translations/fi_FI_incomplete.json index 55cd990c..8d6168a6 100644 --- a/app/translations/fi_FI_incomplete.json +++ b/app/translations/fi_FI_incomplete.json @@ -100,6 +100,7 @@ "welcome": "Tervetuloa Crafty Controller" }, "datatables": { + "loadingRecords": "Ladataan...", "i18n": { "aria": { "sortAscending": ": lajittele sarake nousevasti", @@ -560,4 +561,4 @@ "userSettings": "Käyttäjäasetukset", "uses": "Sallittujen käyttäkertojen määtä (-1 == Ei rajaa)" } -} +} \ No newline at end of file diff --git a/app/translations/fr_FR.json b/app/translations/fr_FR.json index be429c83..739ed9d9 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 ? ", @@ -116,6 +117,7 @@ "welcome": "Bienvenue sur Crafty Controller" }, "datatables": { + "loadingRecords": "Chargement ...", "i18n": { "aria": { "sortAscending": ": activer pour trier les colonnes dans l'ordre croissant", @@ -203,6 +205,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 : {}", @@ -672,4 +675,4 @@ "webhook_body": "Corps du Webhook", "webhooks": "Webhooks" } -} +} \ No newline at end of file diff --git a/app/translations/fy_NL_incomplete.json b/app/translations/fy_NL_incomplete.json index 98785a2b..6c5d76e8 100644 --- a/app/translations/fy_NL_incomplete.json +++ b/app/translations/fy_NL_incomplete.json @@ -99,6 +99,7 @@ "welcome": "Wolkom by Crafty Controller" }, "datatables": { + "loadingRecords": "Laden...", "i18n": { "aria": { "sortAscending": ": aktivearje om kolom oprinnend te sortearjen", @@ -529,4 +530,4 @@ "userSettings": "Brûkersynstellingen", "uses": "Oantal gebrûk tastien (-1==Gjin limyt)" } -} +} \ No newline at end of file diff --git a/app/translations/he_IL.json b/app/translations/he_IL.json index 7e2b1403..6c552ab0 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-איך תרצו לקרוא לאסימון ה", @@ -116,6 +117,7 @@ "welcome": "ברוכים הבאים ל-פאנל קראפטי" }, "datatables": { + "loadingRecords": "...טוען", "i18n": { "aria": { "sortAscending": ": הפעילו כדי למיין עמודות בסדר עולה", @@ -203,6 +205,7 @@ "not-downloaded": "לא הצלחנו למצוא את קובץ ההפעלה שלך. האם זה סיים להוריד? האם ההרשאות מוגדרות בשביל הפעלה?", "portReminder": "זיהינו שזו הפעם הראשונה ש-{} מופעל. הקפידו להעביר את היציאה {} דרך הנתב/חומת האש שלכם כדי להפוך אותה לנגישה מרחוק מהאינטרנט.", "privMsg": "וה", + "return": "חזרה לפאנל", "serverJars1": "API של צנצנות השרת אינו נגיש. אנא בדוק", "serverJars2": "למידע מעודכן ביותר.", "start-error": "השרת {} לא הצליח להתחיל עם קוד שגיאה: {}", @@ -672,4 +675,4 @@ "webhook_body": "גוף ה-Webhook", "webhooks": "Webhooks" } -} +} \ No newline at end of file diff --git a/app/translations/hr_HR_incomplete.json b/app/translations/hr_HR_incomplete.json index 3f63770f..392ee098 100644 --- a/app/translations/hr_HR_incomplete.json +++ b/app/translations/hr_HR_incomplete.json @@ -99,6 +99,7 @@ "welcome": "Dobrodošli u Crafty Controller" }, "datatables": { + "loadingRecords": "Učitavanje...", "i18n": { "aria": { "sortAscending": ": aktiviraj za sortiranje stupca uzlazno", @@ -529,4 +530,4 @@ "userSettings": "Korisničke postavke", "uses": "Broj dopuštenih upotreba (-1==Bez ograničenja)" } -} +} \ No newline at end of file diff --git a/app/translations/id_ID_incomplete.json b/app/translations/id_ID_incomplete.json index cfec5557..c2e8813d 100644 --- a/app/translations/id_ID_incomplete.json +++ b/app/translations/id_ID_incomplete.json @@ -100,6 +100,7 @@ "welcome": "Selamat Datang Di Crafty Controller" }, "datatables": { + "loadingRecords": "Loading...", "i18n": { "aria": { "sortAscending": ": aktifkan untuk mengurutkan kolom menaik", @@ -536,4 +537,4 @@ "userSettings": "Pengaturan Pengguna", "uses": "Jumlah penggunaan yang diizinkan (-1==No Limit)" } -} +} \ No newline at end of file diff --git a/app/translations/it_IT.json b/app/translations/it_IT.json index adb59c7d..e93b4828 100644 --- a/app/translations/it_IT.json +++ b/app/translations/it_IT.json @@ -20,6 +20,7 @@ "created": "Creato", "deleteKeyConfirmation": "Vuoi cancellare questa chiave API? Non puoi tornare indietro.", "deleteKeyConfirmationTitle": "Rimuovere la chiave API ${keyId}?", + "fullAccess": " Accesso completo", "getToken": "Prendi un Token", "name": "Nome", "nameDesc": "Come desideri chiamare questo Token API? ", @@ -116,6 +117,7 @@ "welcome": "Benvenuto su Crafty Controller" }, "datatables": { + "loadingRecords": "Carico...", "i18n": { "aria": { "sortAscending": ": attiva per ordinare le colonne in modo ascendente", @@ -203,6 +205,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": "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: {}", @@ -672,4 +675,4 @@ "webhook_body": "Corpo del Webhook", "webhooks": "Webhook" } -} +} \ No newline at end of file diff --git a/app/translations/lol_EN.json b/app/translations/lol_EN.json index aa07ff5f..2a0450ca 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? ", @@ -116,6 +117,7 @@ "welcome": "WELCOM 2 CWAFTY CONTROLLR" }, "datatables": { + "loadingRecords": "Loading...", "i18n": { "aria": { "sortAscending": ": activate to sort column ascending", @@ -203,6 +205,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: {}", @@ -672,4 +675,4 @@ "webhook_body": "WEBHOOK FISH", "webhooks": "WEBHOOKZ" } -} +} \ No newline at end of file diff --git a/app/translations/lv_LV.json b/app/translations/lv_LV.json index d337d38f..43501f47 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)? ", @@ -117,6 +118,7 @@ "welcome": "Esiet sveicināts Crafty Controller" }, "datatables": { + "loadingRecords": "Ielādē...", "i18n": { "aria": { "sortAscending": ": aktivizēt lai kārotu kolonnu augoši", @@ -204,6 +206,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: {}", @@ -673,4 +676,4 @@ "webhook_body": "Webhook Saturs", "webhooks": "Webhooki" } -} +} \ No newline at end of file diff --git a/app/translations/nl_BE.json b/app/translations/nl_BE.json index d20e209c..ba2d792b 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? ", @@ -116,6 +117,7 @@ "welcome": "Welkom bij Crafty Controller " }, "datatables": { + "loadingRecords": "Bezig met laden...", "i18n": { "aria": { "sortAscending": ": activeren om kolom oplopend te sorteren", @@ -203,6 +205,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: {}", @@ -672,4 +675,4 @@ "webhook_body": "Webhook-body", "webhooks": "Webhooks" } -} +} \ No newline at end of file diff --git a/app/translations/nl_NL_incomplete.json b/app/translations/nl_NL_incomplete.json index 8fd97240..1d158594 100644 --- a/app/translations/nl_NL_incomplete.json +++ b/app/translations/nl_NL_incomplete.json @@ -99,6 +99,7 @@ "welcome": "Welkom bij Crafty Controller" }, "datatables": { + "loadingRecords": "Laden...", "i18n": { "aria": { "sortAscending": ": activeer om kolom oplopend te sorteren", @@ -529,4 +530,4 @@ "userSettings": "Gebruikersinstellingen", "uses": "Aantal keer toegestaan (-1==Geen limiet)" } -} +} \ No newline at end of file diff --git a/app/translations/pl_PL.json b/app/translations/pl_PL.json index ade8c5a5..c0b5c0f4 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? ", @@ -116,6 +117,7 @@ "welcome": "Witamy w Crafty Controller" }, "datatables": { + "loadingRecords": "Wczytywanie...", "i18n": { "aria": { "sortAscending": ": aktywuj, aby sortować kolumny w góre", @@ -203,6 +205,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: {}", @@ -671,4 +674,4 @@ "webhook_body": "Treść Webhooka", "webhooks": "Webhooki" } -} +} \ No newline at end of file diff --git a/app/translations/pt_BR_incomplete.json b/app/translations/pt_BR_incomplete.json index c8844f40..dcd375f6 100644 --- a/app/translations/pt_BR_incomplete.json +++ b/app/translations/pt_BR_incomplete.json @@ -100,6 +100,7 @@ "welcome": "Bem-vindo ao Crafty Controller" }, "datatables": { + "loadingRecords": "Carregando...", "i18n": { "aria": { "sortAscending": ": ative para ordenar a coluna de forma ascendente", @@ -537,4 +538,4 @@ "userSettings": "Configurações do Usuário", "uses": "Número de Usos Permitidos (-1==Sem Limite)" } -} +} \ No newline at end of file diff --git a/app/translations/th_TH.json b/app/translations/th_TH.json index bbb82dae..96a1a0e5 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 นี้ว่าอะไร ? ", @@ -116,6 +117,7 @@ "welcome": "ยินดีต้อนรับสู่ Crafty Controller" }, "datatables": { + "loadingRecords": "กำลังโหลด...", "i18n": { "aria": { "sortAscending": ": เปิดใช้งานเพื่อเรียงลำดับคอลัมน์จากน้อยไปมาก", @@ -203,6 +205,7 @@ "not-downloaded": "ดูเหมือนว่าเราจะไม่พบแฟ้มกระทำการของคุณ (.jar) ตรวจสอบให้แน่ใจว่าการดาวโหลดน์เสร็จสิ้นแล้ว, การอนุญาตถูกตั้งไปยังแฟ้มกระทำการหรือไม่?", "portReminder": "เราตรวจพบว่านี่เป็นครั้งแรกที่มีการเรียกใช้ {} ตรวจสอบให้แน่ใจว่าได้ Forward port {} ผ่านเราเตอร์/ไฟร์วอลล์ของคุณเพื่อให้สามารถเข้าถึงได้จากอินเทอร์เน็ตจากระยะไกล", "privMsg": "และ ", + "return": "ย้อนกลับไปยังแผงควบคุม", "serverJars1": "ไม่สามารถเข้าถึงเซิร์ฟเวอร์ JARs API กรุณาตรวจสอบ", "serverJars2": "เพื่อข้อมูลที่ทันสมัยที่สุด", "start-error": "เซิร์ฟเวอร์ {} ไม่สามารถเริ่มต้นได้เนื่องจากรหัสข้อผิดพลาด: {}", @@ -671,4 +674,4 @@ "webhook_body": "ภายใน Webhook", "webhooks": "Webhooks" } -} +} \ No newline at end of file diff --git a/app/translations/tr_TR.json b/app/translations/tr_TR.json index cddd2a50..789c7dc9 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?", @@ -116,6 +117,7 @@ "welcome": "Crafty Controller'a Hoşgeldiniz!" }, "datatables": { + "loadingRecords": "Yükleniyor...", "i18n": { "aria": { "sortAscending": ": artan sütun sıralamasını aktifleştir", @@ -203,6 +205,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: {}", @@ -671,4 +674,4 @@ "webhook_body": "Webhook Gövdesi", "webhooks": "Webhooklar" } -} +} \ No newline at end of file diff --git a/app/translations/uk_UA.json b/app/translations/uk_UA.json index 74b683dd..56f02b6c 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 токен?", @@ -116,6 +117,7 @@ "welcome": "Ласкаво просимо у Crafty Controller" }, "datatables": { + "loadingRecords": "Завантаження...", "i18n": { "aria": { "sortAscending": ": активуйте, щоб сортувати стовпці за зростанням", @@ -203,6 +205,7 @@ "not-downloaded": "Здається, ми не можемо знайти ваш виконуваний файл. Чи завершилось завантаження? Чи встановлено дозволи на виконуваний файл?", "portReminder": "Ми виявили це вперше {} був запущений. Обов’язково перенаправте порт {} через ваш маршрутизатор/брандмауер, щоб зробити це доступним з Інтернету.", "privMsg": "і ", + "return": "Повернутись до панелі", "serverJars1": "API сервера JAR недоступний. Будь ласка, перевірте", "serverJars2": "для найактуальнішої інформації.", "start-error": "Сервер {} не запустився через помилку: {}", @@ -671,4 +674,4 @@ "webhook_body": "Код Вебхука", "webhooks": "Вебхуки" } -} +} \ No newline at end of file diff --git a/app/translations/zh_CN.json b/app/translations/zh_CN.json index 0b817b7e..c9283d8d 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 令牌叫做什么?", @@ -116,6 +117,7 @@ "welcome": "欢迎来到 Crafty Controller" }, "datatables": { + "loadingRecords": "正在加载……", "i18n": { "aria": { "sortAscending": ":激活对队列的升序排列", @@ -203,6 +205,7 @@ "not-downloaded": "我们似乎找不到您的可执行文件。它下载完成了吗?可执行文件的权限设置正确了吗?", "portReminder": "我们检测到这是你首次运行 {}。请确保从您的路由器/防火墙转发 {} 端口,以使程序可以从公网远程访问。", "privMsg": "以及", + "return": "返回仪表板", "serverJars1": "无法访问服务器 JAR API。请检查", "serverJars2": "以获取最新信息。", "start-error": "服务器 {} 启动失败,错误代码为:{}", @@ -672,4 +675,4 @@ "webhook_body": "Webhook 消息体(Body)", "webhooks": "Webhook" } -} +} \ No newline at end of file diff --git a/main.py b/main.py index ebaf7806..f8a4aab1 100644 --- a/main.py +++ b/main.py @@ -17,6 +17,7 @@ from app.classes.models.users import HelperUsers from app.classes.models.management import HelpersManagement from app.classes.shared.import_helper import ImportHelpers from app.classes.shared.websocket_manager import WebSocketManager +from app.classes.logging.log_formatter import JsonFormatter console = Console() helper = Helpers() @@ -284,6 +285,11 @@ def setup_logging(debug=True): logging.config.dictConfig(logging_config) + # Apply JSON formatting to the "audit" handler + for handler in logging.getLogger().handlers: + if handler.name == "audit_log_handler": + handler.setFormatter(JsonFormatter()) + else: logging.basicConfig(level=logging.DEBUG) logging.warning(f"Unable to read logging config from {logging_config_file}")