diff --git a/CHANGELOG.md b/CHANGELOG.md index 6197feba..5515ec9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ TBD - Reset query arguments on login if `?next` is not available ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/750)) ### Tweaks - Add link to go back to dashboard on error page ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/743)) +- Set audit logging to logfile instead of DB ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/751)) ### Lang - Changes of phrase in `cs_CS` translation ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/749))

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/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/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/web/panel_handler.py b/app/classes/web/panel_handler.py index e1beb547..0ceffb7c 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -1506,8 +1506,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/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/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/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/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 81f5ef21..79026d51 100644 --- a/app/translations/cs_CS.json +++ b/app/translations/cs_CS.json @@ -117,6 +117,7 @@ "welcome": "Vítejte v Crafty Controlleru" }, "datatables": { + "loadingRecords": "Načítání...", "i18n": { "aria": { "sortAscending": ": aktivace řazení sloupce vzestupně", @@ -693,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 f1594576..c8358427 100644 --- a/app/translations/de_DE.json +++ b/app/translations/de_DE.json @@ -117,6 +117,7 @@ "welcome": "Willkommen bei Crafty Controller" }, "datatables": { + "loadingRecords": "Laden...", "i18n": { "aria": { "sortAscending": ": Aktivieren, um die Spalte aufsteigend zu sortieren", @@ -674,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 a74250e1..a0723002 100644 --- a/app/translations/en_EN.json +++ b/app/translations/en_EN.json @@ -116,6 +116,7 @@ "welcome": "Welcome to Crafty Controller" }, "datatables": { + "loadingRecords": "Loading...", "i18n": { "aria": { "sortAscending": ": activate to sort column ascending", @@ -672,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 361b896b..7531ed1c 100644 --- a/app/translations/es_ES.json +++ b/app/translations/es_ES.json @@ -117,6 +117,7 @@ "welcome": "Bienvenido a Crafty Controller" }, "datatables": { + "loadingRecords": "Cargando...", "i18n": { "aria": { "sortAscending": ": activar para ordenar las columnas de manera ascendente", @@ -674,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 24a4ee7e..739ed9d9 100644 --- a/app/translations/fr_FR.json +++ b/app/translations/fr_FR.json @@ -117,6 +117,7 @@ "welcome": "Bienvenue sur Crafty Controller" }, "datatables": { + "loadingRecords": "Chargement ...", "i18n": { "aria": { "sortAscending": ": activer pour trier les colonnes dans l'ordre croissant", @@ -674,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 5d472597..6c552ab0 100644 --- a/app/translations/he_IL.json +++ b/app/translations/he_IL.json @@ -117,6 +117,7 @@ "welcome": "ברוכים הבאים ל-פאנל קראפטי" }, "datatables": { + "loadingRecords": "...טוען", "i18n": { "aria": { "sortAscending": ": הפעילו כדי למיין עמודות בסדר עולה", @@ -674,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 16a863f3..e93b4828 100644 --- a/app/translations/it_IT.json +++ b/app/translations/it_IT.json @@ -117,6 +117,7 @@ "welcome": "Benvenuto su Crafty Controller" }, "datatables": { + "loadingRecords": "Carico...", "i18n": { "aria": { "sortAscending": ": attiva per ordinare le colonne in modo ascendente", @@ -674,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 22d1c268..2a0450ca 100644 --- a/app/translations/lol_EN.json +++ b/app/translations/lol_EN.json @@ -117,6 +117,7 @@ "welcome": "WELCOM 2 CWAFTY CONTROLLR" }, "datatables": { + "loadingRecords": "Loading...", "i18n": { "aria": { "sortAscending": ": activate to sort column ascending", @@ -674,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 b66d9dbc..43501f47 100644 --- a/app/translations/lv_LV.json +++ b/app/translations/lv_LV.json @@ -118,6 +118,7 @@ "welcome": "Esiet sveicināts Crafty Controller" }, "datatables": { + "loadingRecords": "Ielādē...", "i18n": { "aria": { "sortAscending": ": aktivizēt lai kārotu kolonnu augoši", @@ -675,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 9ccf4f2b..ba2d792b 100644 --- a/app/translations/nl_BE.json +++ b/app/translations/nl_BE.json @@ -117,6 +117,7 @@ "welcome": "Welkom bij Crafty Controller " }, "datatables": { + "loadingRecords": "Bezig met laden...", "i18n": { "aria": { "sortAscending": ": activeren om kolom oplopend te sorteren", @@ -674,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 9229b474..c0b5c0f4 100644 --- a/app/translations/pl_PL.json +++ b/app/translations/pl_PL.json @@ -117,6 +117,7 @@ "welcome": "Witamy w Crafty Controller" }, "datatables": { + "loadingRecords": "Wczytywanie...", "i18n": { "aria": { "sortAscending": ": aktywuj, aby sortować kolumny w góre", @@ -673,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 e6a8be73..96a1a0e5 100644 --- a/app/translations/th_TH.json +++ b/app/translations/th_TH.json @@ -117,6 +117,7 @@ "welcome": "ยินดีต้อนรับสู่ Crafty Controller" }, "datatables": { + "loadingRecords": "กำลังโหลด...", "i18n": { "aria": { "sortAscending": ": เปิดใช้งานเพื่อเรียงลำดับคอลัมน์จากน้อยไปมาก", @@ -673,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 5da06afc..789c7dc9 100644 --- a/app/translations/tr_TR.json +++ b/app/translations/tr_TR.json @@ -117,6 +117,7 @@ "welcome": "Crafty Controller'a Hoşgeldiniz!" }, "datatables": { + "loadingRecords": "Yükleniyor...", "i18n": { "aria": { "sortAscending": ": artan sütun sıralamasını aktifleştir", @@ -673,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 eff48185..56f02b6c 100644 --- a/app/translations/uk_UA.json +++ b/app/translations/uk_UA.json @@ -117,6 +117,7 @@ "welcome": "Ласкаво просимо у Crafty Controller" }, "datatables": { + "loadingRecords": "Завантаження...", "i18n": { "aria": { "sortAscending": ": активуйте, щоб сортувати стовпці за зростанням", @@ -673,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 cf9f4b1c..c9283d8d 100644 --- a/app/translations/zh_CN.json +++ b/app/translations/zh_CN.json @@ -117,6 +117,7 @@ "welcome": "欢迎来到 Crafty Controller" }, "datatables": { + "loadingRecords": "正在加载……", "i18n": { "aria": { "sortAscending": ":激活对队列的升序排列", @@ -674,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}")