diff --git a/containers/backupchecks/src/backend/app/admin_logging.py b/containers/backupchecks/src/backend/app/admin_logging.py index 97e571e..747a6fe 100644 --- a/containers/backupchecks/src/backend/app/admin_logging.py +++ b/containers/backupchecks/src/backend/app/admin_logging.py @@ -6,10 +6,10 @@ from typing import Optional from flask_login import current_user from .database import db -from .models import AdminLog +from .models import AuditLog -def log_admin_event( +def log_audit_event( event_type: str, message: str, details: Optional[str] = None, @@ -17,7 +17,7 @@ def log_admin_event( username: Optional[str] = None, commit: bool = True, ) -> None: - """Write an entry to the in-app AdminLog table. + """Write an entry to the in-app AuditLog table. - This is the source for the /logging page in the website (not container logs). - Retention: keep only the last 7 days. @@ -30,7 +30,7 @@ def log_admin_event( except Exception: username = None - entry = AdminLog( + entry = AuditLog( user=username, event_type=(event_type or "event")[:64], message=(message or "")[:2000], @@ -41,7 +41,7 @@ def log_admin_event( # Enforce retention: keep only the last 7 days try: cutoff = datetime.utcnow() - timedelta(days=7) - AdminLog.query.filter(AdminLog.created_at < cutoff).delete(synchronize_session=False) + AuditLog.query.filter(AuditLog.created_at < cutoff).delete(synchronize_session=False) except Exception: # Never block the main action because of retention cleanup. pass @@ -54,3 +54,7 @@ def log_admin_event( except Exception: # If logging fails, do not raise and do not print to container logs. db.session.rollback() + + +# Legacy alias for backwards compatibility +log_admin_event = log_audit_event diff --git a/containers/backupchecks/src/backend/app/main/routes_core.py b/containers/backupchecks/src/backend/app/main/routes_core.py index 5388fae..079e669 100644 --- a/containers/backupchecks/src/backend/app/main/routes_core.py +++ b/containers/backupchecks/src/backend/app/main/routes_core.py @@ -230,7 +230,7 @@ def dashboard(): @login_required @roles_required("admin", "operator") def logging_page(): - # Server-side view of AdminLog entries. + # Server-side view of AuditLog entries (system audit trail). try: page = int(request.args.get("page", "1")) except ValueError: @@ -239,7 +239,7 @@ def logging_page(): page = 1 per_page = 20 - query = AdminLog.query.order_by(AdminLog.created_at.desc().nullslast(), AdminLog.id.desc()) + query = AuditLog.query.order_by(AuditLog.created_at.desc().nullslast(), AuditLog.id.desc()) total_items = query.count() total_pages = max(1, math.ceil(total_items / per_page)) if total_items else 1 if page > total_pages: diff --git a/containers/backupchecks/src/backend/app/main/routes_shared.py b/containers/backupchecks/src/backend/app/main/routes_shared.py index ebd9175..4eff4e2 100644 --- a/containers/backupchecks/src/backend/app/main/routes_shared.py +++ b/containers/backupchecks/src/backend/app/main/routes_shared.py @@ -34,7 +34,7 @@ from ..job_matching import build_job_match_key, find_matching_job from ..database import db from ..models import ( SystemSettings, - AdminLog, + AuditLog, Customer, Job, JobRun, @@ -597,7 +597,7 @@ def _log_admin_event(event_type: str, message: str, details: str | None = None) except Exception: username = None - entry = AdminLog( + entry = AuditLog( user=username, event_type=event_type, message=message, @@ -608,7 +608,7 @@ def _log_admin_event(event_type: str, message: str, details: str | None = None) # Enforce retention: keep only the last 7 days try: cutoff = datetime.utcnow() - timedelta(days=7) - AdminLog.query.filter(AdminLog.created_at < cutoff).delete(synchronize_session=False) + AuditLog.query.filter(AuditLog.created_at < cutoff).delete(synchronize_session=False) except Exception: # If cleanup fails we still try to commit the new entry pass diff --git a/containers/backupchecks/src/backend/app/migrations.py b/containers/backupchecks/src/backend/app/migrations.py index 813fcb7..323bcf6 100644 --- a/containers/backupchecks/src/backend/app/migrations.py +++ b/containers/backupchecks/src/backend/app/migrations.py @@ -1024,6 +1024,52 @@ def migrate_news_tables() -> None: print("[migrations] migrate_news_tables completed.") +def migrate_rename_admin_logs_to_audit_logs() -> None: + """Rename admin_logs table to audit_logs for better semantic clarity. + + The table name 'admin_logs' was misleading as it contains both admin actions + and automated system events. The new name 'audit_logs' better reflects its + purpose as a comprehensive audit trail for both user actions and system events. + + This migration is idempotent and safe to run multiple times. + """ + engine = db.get_engine() + with engine.begin() as conn: + # Check if old table exists and new table doesn't + old_exists = conn.execute( + text( + """ + SELECT 1 FROM information_schema.tables + WHERE table_name = 'admin_logs' + LIMIT 1 + """ + ) + ).first() is not None + + new_exists = conn.execute( + text( + """ + SELECT 1 FROM information_schema.tables + WHERE table_name = 'audit_logs' + LIMIT 1 + """ + ) + ).first() is not None + + if old_exists and not new_exists: + # Rename the table + conn.execute(text("ALTER TABLE admin_logs RENAME TO audit_logs")) + print("[migrations] Renamed admin_logs → audit_logs") + elif new_exists and not old_exists: + print("[migrations] audit_logs already exists (admin_logs already migrated)") + elif old_exists and new_exists: + # Both exist - this shouldn't happen but handle gracefully + print("[migrations] WARNING: Both admin_logs and audit_logs exist. Manual intervention may be needed.") + else: + # Neither exists - table will be created by db.create_all() + print("[migrations] audit_logs table will be created by db.create_all()") + + def run_migrations() -> None: print("[migrations] Starting migrations...") migrate_add_username_to_users() @@ -1064,6 +1110,7 @@ def run_migrations() -> None: migrate_reporting_report_config() migrate_performance_indexes() migrate_system_settings_require_daily_dashboard_visit() + migrate_rename_admin_logs_to_audit_logs() print("[migrations] All migrations completed.") diff --git a/containers/backupchecks/src/backend/app/models.py b/containers/backupchecks/src/backend/app/models.py index 54e4fde..2d14e97 100644 --- a/containers/backupchecks/src/backend/app/models.py +++ b/containers/backupchecks/src/backend/app/models.py @@ -146,8 +146,8 @@ class SystemSettings(db.Model): -class AdminLog(db.Model): - __tablename__ = "admin_logs" +class AuditLog(db.Model): + __tablename__ = "audit_logs" id = db.Column(db.Integer, primary_key=True) created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) @@ -157,6 +157,10 @@ class AdminLog(db.Model): message = db.Column(db.Text, nullable=False) details = db.Column(db.Text, nullable=True) + +# Legacy alias for backwards compatibility during migration +AdminLog = AuditLog + class Customer(db.Model): __tablename__ = "customers"