From 9451b13dc632e76c484cede8104722ace4acb0da Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Thu, 8 Jan 2026 21:55:57 +0100 Subject: [PATCH] feat: add ENABLE_USER_STATUS toggle for admin-controlled user status visibility (#20488) * feat: add ENABLE_USER_STATUS toggle for admin-controlled user status visibility feat: add ENABLE_USER_STATUS toggle for admin-controlled user status visibility Add a new admin panel toggle (Admin > Settings > General) called "User Status" that allows administrators to globally enable or disable user status functionality. When disabled: - User status API endpoints return 403 Forbidden - Status emoji, message, and "Update your status" button are hidden from the user menu The setting: - Defaults to True (enabled) - Can be overridden via ENABLE_USER_STATUS environment variable - Persists across restarts using PersistentConfig Files modified: - backend/open_webui/config.py - Added ENABLE_USER_STATUS PersistentConfig - backend/open_webui/main.py - App state init and features dict - backend/open_webui/routers/auths.py - AdminConfig model and endpoints - backend/open_webui/routers/users.py - 403 guards on status endpoints - src/lib/components/admin/Settings/General.svelte - Toggle UI - src/lib/components/layout/Sidebar/UserMenu.svelte - Conditional status display * Update UserMenu.svelte feat: add ENABLE_USER_STATUS toggle for admin-controlled user status visibility Add a new admin panel toggle (Admin > Settings > General) called "User Status" that allows administrators to globally enable or disable user status functionality. When disabled: - User status API endpoints return 403 Forbidden - Active/Away indicator with blinking dot is hidden from the user menu - Status emoji, message, and "Update your status" button are hidden from the user menu The setting: - Defaults to True (enabled) - Can be overridden via ENABLE_USER_STATUS environment variable - Persists across restarts using PersistentConfig Files modified: - backend/open_webui/config.py - Added ENABLE_USER_STATUS PersistentConfig - backend/open_webui/main.py - App state init and features dict - backend/open_webui/routers/auths.py - AdminConfig model and endpoints - backend/open_webui/routers/users.py - 403 guards on status endpoints - src/lib/components/admin/Settings/General.svelte - Toggle UI - src/lib/components/layout/Sidebar/UserMenu.svelte - Conditional status display * nuke the indicator * fix --- backend/open_webui/config.py | 6 ++++++ backend/open_webui/main.py | 3 +++ backend/open_webui/routers/auths.py | 4 ++++ backend/open_webui/routers/users.py | 12 ++++++++++++ src/lib/components/admin/Settings/General.svelte | 8 ++++++++ src/lib/components/layout/Sidebar.svelte | 8 ++++++-- src/lib/components/layout/Sidebar/UserMenu.svelte | 1 + 7 files changed, 40 insertions(+), 2 deletions(-) diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index 3fee61f6e3..6a00c21c02 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -1574,6 +1574,12 @@ ENABLE_NOTES = PersistentConfig( os.environ.get("ENABLE_NOTES", "True").lower() == "true", ) +ENABLE_USER_STATUS = PersistentConfig( + "ENABLE_USER_STATUS", + "users.enable_status", + os.environ.get("ENABLE_USER_STATUS", "True").lower() == "true", +) + ENABLE_EVALUATION_ARENA_MODELS = PersistentConfig( "ENABLE_EVALUATION_ARENA_MODELS", "evaluation.arena.enable", diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index 2f384ee7d3..46a3350021 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -369,6 +369,7 @@ from open_webui.config import ( FOLDER_MAX_FILE_COUNT, ENABLE_CHANNELS, ENABLE_NOTES, + ENABLE_USER_STATUS, ENABLE_COMMUNITY_SHARING, ENABLE_MESSAGE_RATING, ENABLE_USER_WEBHOOKS, @@ -789,6 +790,7 @@ app.state.config.ENABLE_NOTES = ENABLE_NOTES app.state.config.ENABLE_COMMUNITY_SHARING = ENABLE_COMMUNITY_SHARING app.state.config.ENABLE_MESSAGE_RATING = ENABLE_MESSAGE_RATING app.state.config.ENABLE_USER_WEBHOOKS = ENABLE_USER_WEBHOOKS +app.state.config.ENABLE_USER_STATUS = ENABLE_USER_STATUS app.state.config.ENABLE_EVALUATION_ARENA_MODELS = ENABLE_EVALUATION_ARENA_MODELS app.state.config.EVALUATION_ARENA_MODELS = EVALUATION_ARENA_MODELS @@ -1933,6 +1935,7 @@ async def get_app_config(request: Request): "enable_community_sharing": app.state.config.ENABLE_COMMUNITY_SHARING, "enable_message_rating": app.state.config.ENABLE_MESSAGE_RATING, "enable_user_webhooks": app.state.config.ENABLE_USER_WEBHOOKS, + "enable_user_status": app.state.config.ENABLE_USER_STATUS, "enable_admin_export": ENABLE_ADMIN_EXPORT, "enable_admin_chat_access": ENABLE_ADMIN_CHAT_ACCESS, "enable_google_drive_integration": app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION, diff --git a/backend/open_webui/routers/auths.py b/backend/open_webui/routers/auths.py index 2c6fc6108b..63032d71db 100644 --- a/backend/open_webui/routers/auths.py +++ b/backend/open_webui/routers/auths.py @@ -994,6 +994,7 @@ async def get_admin_config(request: Request, user=Depends(get_admin_user)): "ENABLE_MEMORIES": request.app.state.config.ENABLE_MEMORIES, "ENABLE_NOTES": request.app.state.config.ENABLE_NOTES, "ENABLE_USER_WEBHOOKS": request.app.state.config.ENABLE_USER_WEBHOOKS, + "ENABLE_USER_STATUS": request.app.state.config.ENABLE_USER_STATUS, "PENDING_USER_OVERLAY_TITLE": request.app.state.config.PENDING_USER_OVERLAY_TITLE, "PENDING_USER_OVERLAY_CONTENT": request.app.state.config.PENDING_USER_OVERLAY_CONTENT, "RESPONSE_WATERMARK": request.app.state.config.RESPONSE_WATERMARK, @@ -1019,6 +1020,7 @@ class AdminConfig(BaseModel): ENABLE_MEMORIES: bool ENABLE_NOTES: bool ENABLE_USER_WEBHOOKS: bool + ENABLE_USER_STATUS: bool PENDING_USER_OVERLAY_TITLE: Optional[str] = None PENDING_USER_OVERLAY_CONTENT: Optional[str] = None RESPONSE_WATERMARK: Optional[str] = None @@ -1068,6 +1070,7 @@ async def update_admin_config( request.app.state.config.ENABLE_MESSAGE_RATING = form_data.ENABLE_MESSAGE_RATING request.app.state.config.ENABLE_USER_WEBHOOKS = form_data.ENABLE_USER_WEBHOOKS + request.app.state.config.ENABLE_USER_STATUS = form_data.ENABLE_USER_STATUS request.app.state.config.PENDING_USER_OVERLAY_TITLE = ( form_data.PENDING_USER_OVERLAY_TITLE @@ -1097,6 +1100,7 @@ async def update_admin_config( "ENABLE_MEMORIES": request.app.state.config.ENABLE_MEMORIES, "ENABLE_NOTES": request.app.state.config.ENABLE_NOTES, "ENABLE_USER_WEBHOOKS": request.app.state.config.ENABLE_USER_WEBHOOKS, + "ENABLE_USER_STATUS": request.app.state.config.ENABLE_USER_STATUS, "PENDING_USER_OVERLAY_TITLE": request.app.state.config.PENDING_USER_OVERLAY_TITLE, "PENDING_USER_OVERLAY_CONTENT": request.app.state.config.PENDING_USER_OVERLAY_CONTENT, "RESPONSE_WATERMARK": request.app.state.config.RESPONSE_WATERMARK, diff --git a/backend/open_webui/routers/users.py b/backend/open_webui/routers/users.py index f10448458f..ac53b73c8b 100644 --- a/backend/open_webui/routers/users.py +++ b/backend/open_webui/routers/users.py @@ -333,8 +333,14 @@ async def update_user_settings_by_session_user( @router.get("/user/status") async def get_user_status_by_session_user( + request: Request, user=Depends(get_verified_user), db: Session = Depends(get_session) ): + if not request.app.state.config.ENABLE_USER_STATUS: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail=ERROR_MESSAGES.ACTION_PROHIBITED, + ) user = Users.get_user_by_id(user.id, db=db) if user: return user @@ -352,10 +358,16 @@ async def get_user_status_by_session_user( @router.post("/user/status/update") async def update_user_status_by_session_user( + request: Request, form_data: UserStatus, user=Depends(get_verified_user), db: Session = Depends(get_session), ): + if not request.app.state.config.ENABLE_USER_STATUS: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail=ERROR_MESSAGES.ACTION_PROHIBITED, + ) user = Users.get_user_by_id(user.id, db=db) if user: user = Users.update_user_status_by_id(user.id, form_data, db=db) diff --git a/src/lib/components/admin/Settings/General.svelte b/src/lib/components/admin/Settings/General.svelte index c327937411..a739d852cb 100644 --- a/src/lib/components/admin/Settings/General.svelte +++ b/src/lib/components/admin/Settings/General.svelte @@ -757,6 +757,14 @@ +
+
+ {$i18n.t('User Status')} +
+ + +
+
{$i18n.t('Response Watermark')} diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte index 70281503e4..223b1331b7 100644 --- a/src/lib/components/layout/Sidebar.svelte +++ b/src/lib/components/layout/Sidebar.svelte @@ -793,7 +793,7 @@ {#if $user !== undefined && $user !== null} { if (e.detail === 'archived-chat') { @@ -812,6 +812,7 @@ aria-label={$i18n.t('Open User Profile Menu')} /> + {#if $config?.features?.enable_user_status}
+ {/if}
@@ -1360,7 +1362,7 @@ {#if $user !== undefined && $user !== null} { if (e.detail === 'archived-chat') { @@ -1379,6 +1381,7 @@ aria-label={$i18n.t('Open User Profile Menu')} /> + {#if $config?.features?.enable_user_status}
+ {/if}
{$user?.name}
diff --git a/src/lib/components/layout/Sidebar/UserMenu.svelte b/src/lib/components/layout/Sidebar/UserMenu.svelte index 782d0577db..ef91dab133 100644 --- a/src/lib/components/layout/Sidebar/UserMenu.svelte +++ b/src/lib/components/layout/Sidebar/UserMenu.svelte @@ -203,6 +203,7 @@
{/if} + {