diff --git a/containers/backupchecks/src/backend/app/main/routes_settings.py b/containers/backupchecks/src/backend/app/main/routes_settings.py index af9f86a..3d07f03 100644 --- a/containers/backupchecks/src/backend/app/main/routes_settings.py +++ b/containers/backupchecks/src/backend/app/main/routes_settings.py @@ -439,6 +439,10 @@ def settings(): # Checkbox: present in form = checked, absent = unchecked. settings.require_daily_dashboard_visit = bool(request.form.get("require_daily_dashboard_visit")) + # Environment indicator is in the same form (General tab), so process it here. + # Checkbox: present in form = checked, absent = unchecked. + settings.is_sandbox_environment = bool(request.form.get("is_sandbox_environment")) + # Autotask integration if "autotask_enabled" in request.form: settings.autotask_enabled = bool(request.form.get("autotask_enabled")) diff --git a/containers/backupchecks/src/backend/app/main/routes_shared.py b/containers/backupchecks/src/backend/app/main/routes_shared.py index e53fab2..ebd9175 100644 --- a/containers/backupchecks/src/backend/app/main/routes_shared.py +++ b/containers/backupchecks/src/backend/app/main/routes_shared.py @@ -109,7 +109,11 @@ def get_user_roles() -> list[str]: @main_bp.app_context_processor def _inject_role_context(): - return {"active_role": get_active_role(), "user_roles": get_user_roles()} + return { + "active_role": get_active_role(), + "user_roles": get_user_roles(), + "system_settings": _get_or_create_settings(), + } def roles_required(*roles): diff --git a/containers/backupchecks/src/backend/app/migrations.py b/containers/backupchecks/src/backend/app/migrations.py index be30fd6..813fcb7 100644 --- a/containers/backupchecks/src/backend/app/migrations.py +++ b/containers/backupchecks/src/backend/app/migrations.py @@ -1034,6 +1034,7 @@ def run_migrations() -> None: migrate_system_settings_daily_jobs_start_date() migrate_system_settings_ui_timezone() migrate_system_settings_autotask_integration() + migrate_system_settings_sandbox_environment() migrate_customers_autotask_company_mapping() migrate_mail_messages_columns() migrate_mail_messages_parse_columns() @@ -1138,6 +1139,38 @@ def migrate_system_settings_require_daily_dashboard_visit() -> None: print(f"[migrations] Failed to migrate system_settings.require_daily_dashboard_visit: {exc}") +def migrate_system_settings_sandbox_environment() -> None: + """Add is_sandbox_environment column to system_settings if missing. + + When enabled, a visual banner is displayed on all pages to indicate + this is not a production environment. + """ + table = "system_settings" + column = "is_sandbox_environment" + + try: + engine = db.get_engine() + except Exception as exc: + print(f"[migrations] Could not get engine for system_settings sandbox environment migration: {exc}") + return + + try: + if _column_exists(table, column): + print("[migrations] system_settings.is_sandbox_environment already exists.") + return + + with engine.begin() as conn: + conn.execute( + text( + f'ALTER TABLE "{table}" ADD COLUMN {column} BOOLEAN NOT NULL DEFAULT FALSE' + ) + ) + + print("[migrations] migrate_system_settings_sandbox_environment completed.") + except Exception as exc: + print(f"[migrations] Failed to migrate system_settings.is_sandbox_environment: {exc}") + + def migrate_performance_indexes() -> None: """Add performance indexes for frequently queried foreign key columns. diff --git a/containers/backupchecks/src/backend/app/models.py b/containers/backupchecks/src/backend/app/models.py index 8ecfd83..54e4fde 100644 --- a/containers/backupchecks/src/backend/app/models.py +++ b/containers/backupchecks/src/backend/app/models.py @@ -112,6 +112,11 @@ class SystemSettings(db.Model): # their first page view each day before they can navigate elsewhere. require_daily_dashboard_visit = db.Column(db.Boolean, nullable=False, default=False) + # Development/Sandbox environment indicator. + # When enabled, a visual banner is displayed on all pages to indicate + # this is not a production environment. + is_sandbox_environment = db.Column(db.Boolean, nullable=False, default=False) + # Autotask integration settings autotask_enabled = db.Column(db.Boolean, nullable=False, default=False) autotask_environment = db.Column(db.String(32), nullable=True) # sandbox | production diff --git a/containers/backupchecks/src/static/css/sandbox.css b/containers/backupchecks/src/static/css/sandbox.css new file mode 100644 index 0000000..75666f8 --- /dev/null +++ b/containers/backupchecks/src/static/css/sandbox.css @@ -0,0 +1,27 @@ +/* Sandbox environment visual indicator banner + * Displays a diagonal "SANDBOX" ribbon in the top-left corner + * when the is_sandbox_environment setting is enabled. + */ + +.sandbox-banner { + position: fixed; + top: 30px; + left: -60px; + width: 250px; + background-color: #dc3545; + color: white; + text-align: center; + transform: rotate(-45deg); + z-index: 9999; + pointer-events: none; /* Banner itself is not clickable, elements behind it are */ + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); +} + +.sandbox-banner-text { + display: block; + padding: 8px 0; + font-weight: bold; + font-size: 16px; + letter-spacing: 2px; + text-transform: uppercase; +} diff --git a/containers/backupchecks/src/templates/layout/base.html b/containers/backupchecks/src/templates/layout/base.html index 24b3449..27f4927 100644 --- a/containers/backupchecks/src/templates/layout/base.html +++ b/containers/backupchecks/src/templates/layout/base.html @@ -11,6 +11,7 @@ /> + {% block head %}{% endblock %} @@ -52,6 +53,12 @@ + {% if system_settings and system_settings.is_sandbox_environment %} +
+ SANDBOX +
+ {% endif %} +