diff --git a/application/single_app/config.py b/application/single_app/config.py index 6250de22..deadc08f 100644 --- a/application/single_app/config.py +++ b/application/single_app/config.py @@ -96,7 +96,7 @@ EXECUTOR_TYPE = 'thread' EXECUTOR_MAX_WORKERS = 30 SESSION_TYPE = 'filesystem' -VERSION = "0.250.051" +VERSION = "0.250.052" SESSION_COOKIE_SAMESITE = os.getenv('SESSION_COOKIE_SAMESITE', 'Lax') SESSION_COOKIE_HTTPONLY = os.getenv('SESSION_COOKIE_HTTPONLY', 'true').lower() != 'false' diff --git a/application/single_app/templates/_sidebar_nav.html b/application/single_app/templates/_sidebar_nav.html index 61e682e5..5cd8a227 100644 --- a/application/single_app/templates/_sidebar_nav.html +++ b/application/single_app/templates/_sidebar_nav.html @@ -844,7 +844,7 @@ - {% if request.endpoint == 'control_center' and ((app_settings.require_member_of_control_center_admin and session.get('user') and 'ControlCenterAdmin' in session['user']['roles']) or (app_settings.require_member_of_control_center_dashboard_reader and session.get('user') and 'ControlCenterDashboardReader' in session['user']['roles']) or (not app_settings.require_member_of_control_center_admin and 'Admin' in session['user']['roles'])) %} + {% if request.endpoint == 'frontend_control_center.control_center' and ((app_settings.require_member_of_control_center_admin and session.get('user') and 'ControlCenterAdmin' in session['user']['roles']) or (app_settings.require_member_of_control_center_dashboard_reader and session.get('user') and 'ControlCenterDashboardReader' in session['user']['roles']) or (not app_settings.require_member_of_control_center_admin and 'Admin' in session['user']['roles'])) %} {% set control_center_menu_expanded = sidebar_menu_state.get('controlCenter', true) %}
diff --git a/docs/explanation/fixes/CONTROL_CENTER_LEFT_NAV_ENDPOINT_FIX.md b/docs/explanation/fixes/CONTROL_CENTER_LEFT_NAV_ENDPOINT_FIX.md new file mode 100644 index 00000000..9358ee30 --- /dev/null +++ b/docs/explanation/fixes/CONTROL_CENTER_LEFT_NAV_ENDPOINT_FIX.md @@ -0,0 +1,40 @@ +# Control Center Left Nav Endpoint Fix + +Fixed/Implemented in version: **0.250.052** + +## Issue Description + +Admins could open Control Center while the left navigation Control Center section stayed hidden, even when the ControlCenterAdmin and ControlCenterDashboardReader app-role settings were disabled. + +## Root Cause Analysis + +The full sidebar template checked for the unqualified endpoint name `control_center`. The route is registered on the `frontend_control_center` Blueprint, so Flask exposes the endpoint as `frontend_control_center.control_center`. The mismatch prevented the page-local Control Center left-nav section from rendering. + +## Technical Details + +Files modified: + +- `application/single_app/templates/_sidebar_nav.html` +- `application/single_app/config.py` +- `functional_tests/test_control_center_left_nav_endpoint.py` + +Code changes summary: + +- Updated the Control Center sidebar condition to match the blueprint-qualified endpoint. +- Preserved the existing role fallback where regular `Admin` users get Control Center navigation when ControlCenterAdmin enforcement is disabled. +- Added a regression test for the endpoint condition and version bump. + +## Validation + +Validation approach: + +- Run `python functional_tests/test_control_center_left_nav_endpoint.py`. +- Run `git -c core.whitespace=blank-at-eol,blank-at-eof,space-before-tab,cr-at-eol diff --check`. + +Before: the Control Center page could load but the left nav section was not rendered because the endpoint check did not match. + +After: the sidebar condition matches `frontend_control_center.control_center`, allowing authorized admins to see the Control Center left-nav section. + +Related issue: Fixes #1009 + +Version reference: `application/single_app/config.py` version `0.250.052`. \ No newline at end of file diff --git a/docs/explanation/release_notes.md b/docs/explanation/release_notes.md index 34267919..56351e4f 100644 --- a/docs/explanation/release_notes.md +++ b/docs/explanation/release_notes.md @@ -2,6 +2,15 @@ For feature-focused and fix-focused drill-downs by version, see [Features by Version](/explanation/features/) and [Fixes by Version](/explanation/fixes/). +### **(v0.250.052)** + +#### Bug Fixes + +* **Control Center Left Nav Endpoint Fix** + * Fixed an issue where admins could open Control Center while the left navigation Control Center section stayed hidden when ControlCenterAdmin enforcement was disabled. + * Updated the sidebar endpoint check to use the blueprint-qualified `frontend_control_center.control_center` route and added regression coverage for the regular Admin fallback. + * (Ref: microsoft/simplechat#1009, `_sidebar_nav.html`, `test_control_center_left_nav_endpoint.py`) + ### **(v0.250.051)** #### User Interface Enhancements diff --git a/functional_tests/test_control_center_left_nav_endpoint.py b/functional_tests/test_control_center_left_nav_endpoint.py new file mode 100644 index 00000000..8b7c3bb4 --- /dev/null +++ b/functional_tests/test_control_center_left_nav_endpoint.py @@ -0,0 +1,91 @@ +# test_control_center_left_nav_endpoint.py +#!/usr/bin/env python3 +""" +Functional test for Control Center left nav endpoint matching. +Version: 0.250.052 +Implemented in: 0.250.052 + +This test ensures the Control Center sidebar section uses the blueprint-qualified +endpoint so admins see the left nav when ControlCenterAdmin enforcement is disabled. +""" + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +def read_text(relative_path: str) -> str: + """Read a repository file as UTF-8 text.""" + return (ROOT / relative_path).read_text(encoding="utf-8") + + +def test_control_center_sidebar_uses_blueprint_endpoint() -> bool: + """Validate the Control Center sidebar section matches the registered endpoint.""" + print("Testing Control Center sidebar endpoint match...") + sidebar_template = read_text("application/single_app/templates/_sidebar_nav.html") + + expected_endpoint_check = "request.endpoint == 'frontend_control_center.control_center'" + stale_endpoint_check = "request.endpoint == 'control_center'" + + if expected_endpoint_check not in sidebar_template: + print("Control Center sidebar does not use the blueprint-qualified endpoint.") + return False + + if stale_endpoint_check in sidebar_template: + print("Control Center sidebar still contains the stale unqualified endpoint check.") + return False + + print("Control Center sidebar endpoint match found.") + return True + + +def test_control_center_regular_admin_fallback_preserved() -> bool: + """Validate regular admins still get Control Center nav when app-role enforcement is disabled.""" + print("Testing Control Center regular Admin fallback...") + sidebar_template = read_text("application/single_app/templates/_sidebar_nav.html") + + required_snippets = [ + "not app_settings.require_member_of_control_center_admin and 'Admin' in session['user']['roles']", + "app_settings.require_member_of_control_center_dashboard_reader", + "ControlCenterDashboardReader", + "ControlCenterAdmin", + ] + + missing_snippets = [snippet for snippet in required_snippets if snippet not in sidebar_template] + if missing_snippets: + print(f"Missing Control Center role fallback snippets: {missing_snippets}") + return False + + print("Control Center regular Admin fallback preserved.") + return True + + +def test_config_version_bumped_for_control_center_left_nav_fix() -> bool: + """Validate the repository version bump for the Control Center left nav fix.""" + print("Testing config version bump for Control Center left nav fix...") + config_content = read_text("application/single_app/config.py") + + if 'VERSION = "0.250.052"' not in config_content: + print("Config version was not bumped to 0.250.052") + return False + + print("Config version bump found.") + return True + + +if __name__ == "__main__": + checks = [ + test_control_center_sidebar_uses_blueprint_endpoint, + test_control_center_regular_admin_fallback_preserved, + test_config_version_bumped_for_control_center_left_nav_fix, + ] + + results = [] + for check in checks: + print(f"\nRunning {check.__name__}...") + results.append(check()) + + success = all(results) + print(f"\nResults: {sum(results)}/{len(results)} checks passed") + raise SystemExit(0 if success else 1) \ No newline at end of file