Răsfoiți Sursa

feat: Display assigned user groups in Admin Panel

Description:
This PR adds the ability to view a user’s assigned groups in the Admin Panel when editing a user.

Backend Changes:
    Added a new endpoint:
    GET /api/v1/users/{user_id}/groups

        Returns the list of groups assigned to a specific user.
        Requires admin privileges.

Frontend Changes:
    Implemented getUserGroupsById API function to call the new backend endpoint, in lib/apis/users.

    Updated EditUserModal.svelte to:
        Load user groups asynchronously when the modal is opened.
        Display the groups inline in the form before the Save button.
        Show a loading state while fetching, and a “No groups assigned” message if none exist.

Result:
Admins can now see which groups a user belongs to directly from the edit user modal,
improving visibility and reducing the need to navigate away for group membership checks.
Athanasios Oikonomou 2 luni în urmă
părinte
comite
dc453efa5c

+ 10 - 0
backend/open_webui/routers/users.py

@@ -501,3 +501,13 @@ async def delete_user_by_id(user_id: str, user=Depends(get_admin_user)):
         status_code=status.HTTP_403_FORBIDDEN,
         detail=ERROR_MESSAGES.ACTION_PROHIBITED,
     )
+
+
+############################
+# GetUserGroupsById
+############################
+
+
+@router.get("/{user_id}/groups")
+async def get_user_groups_by_id(user_id: str, user=Depends(get_admin_user)):
+    return Groups.get_groups_by_member_id(user_id)

+ 27 - 0
src/lib/apis/users/index.ts

@@ -443,3 +443,30 @@ export const updateUserById = async (token: string, userId: string, user: UserUp
 
 	return res;
 };
+
+export const getUserGroupsById = async (token: string, userId: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/users/${userId}/groups`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.error(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};

+ 31 - 1
src/lib/components/admin/Users/UserList/EditUserModal.svelte

@@ -4,7 +4,7 @@
 	import { createEventDispatcher } from 'svelte';
 	import { onMount, getContext } from 'svelte';
 
-	import { updateUserById } from '$lib/apis/users';
+	import { updateUserById, getUserGroupsById } from '$lib/apis/users';
 
 	import Modal from '$lib/components/common/Modal.svelte';
 	import localizedFormat from 'dayjs/plugin/localizedFormat';
@@ -27,6 +27,9 @@
 		password: ''
 	};
 
+	let _user_groups: any[] = [];
+	let loadingGroups = false;
+
 	const submitHandler = async () => {
 		const res = await updateUserById(localStorage.token, selectedUser.id, _user).catch((error) => {
 			toast.error(`${error}`);
@@ -38,10 +41,23 @@
 		}
 	};
 
+	const loadUserGroups = async () => {
+		if (!selectedUser?.id) return;
+		loadingGroups = true;
+		try {
+			_user_groups = await getUserGroupsById(localStorage.token, selectedUser.id);
+		} catch (error) {
+			toast.error(`${error}`);
+		} finally {
+			loadingGroups = false;
+		}
+	};
+
 	onMount(() => {
 		if (selectedUser) {
 			_user = selectedUser;
 			_user.password = '';
+			loadUserGroups();
 		}
 	});
 </script>
@@ -152,6 +168,20 @@
 							</div>
 						</div>
 
+						<div class="flex flex-col w-full">
+							<div class="mb-1 text-xs text-gray-500">{$i18n.t('Groups')}</div>
+
+							{#if loadingGroups}
+								<div class="text-sm font-medium text-white">{$i18n.t('Loading groups...')}</div>
+							{:else if _user_groups.length === 0}
+								<div class="text-sm font-medium text-white">{$i18n.t('No groups assigned')}</div>
+							{:else}
+								<div class="text-sm font-medium text-white">
+									{_user_groups.map(g => g.name).join(', ')}
+								</div>
+							{/if}
+						</div>
+
 						<div class="flex justify-end pt-3 text-sm font-medium">
 							<button
 								class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full flex flex-row space-x-1 items-center"