Bladeren bron

Merge pull request #13267 from AnapatChaiwongse/dev

feat: implement pagination for /admin/users
Tim Jaeryang Baek 3 maanden geleden
bovenliggende
commit
db06a925fe

+ 17 - 2
backend/open_webui/models/users.py

@@ -160,11 +160,26 @@ class UsersTable:
             return None
 
     def get_users(
-        self, skip: Optional[int] = None, limit: Optional[int] = None
+        self,
+        skip: Optional[int] = None,
+        limit: Optional[int] = None,
+        query_key: Optional[int] = None
     ) -> list[UserModel]:
         with get_db() as db:
 
-            query = db.query(User).order_by(User.created_at.desc())
+            if not query_key:
+                query = db.query(User).order_by(User.created_at.desc())
+            else:
+                query = (
+                    db.query(User)
+                    .filter(
+                        or_(
+                            User.name.ilike(f'%{query_key}%'),
+                            User.email.ilike(f'%{query_key}%')
+                        )
+                    )
+                    .order_by(User.created_at.desc())
+                )
 
             if skip:
                 query = query.offset(skip)

+ 12 - 2
backend/open_webui/routers/users.py

@@ -35,11 +35,21 @@ router = APIRouter()
 
 @router.get("/", response_model=list[UserModel])
 async def get_users(
-    skip: Optional[int] = None,
+    page: Optional[int] = None,
     limit: Optional[int] = None,
+    q: Optional[str] = None,
     user=Depends(get_admin_user),
 ):
-    return Users.get_users(skip, limit)
+    if q:
+        skip: Optional[int] = None
+        if page:
+            skip = (page - 1) * limit
+        return Users.get_users(skip=skip, limit=limit, query_key=q)
+    else:
+        skip: Optional[int] = None
+        if page:
+            skip = (page - 1) * limit
+        return Users.get_users(skip=skip, limit=limit)
 
 
 ############################

+ 53 - 19
src/lib/apis/users/index.ts

@@ -116,30 +116,64 @@ export const updateUserRole = async (token: string, id: string, role: string) =>
 	return res;
 };
 
-export const getUsers = async (token: string) => {
+export const getUsers = async (token: string, page?: number, limit: number = 10, q?: string) => {
 	let error = null;
-
-	const res = await fetch(`${WEBUI_API_BASE_URL}/users/`, {
-		method: 'GET',
-		headers: {
-			'Content-Type': 'application/json',
-			Authorization: `Bearer ${token}`
-		}
-	})
-		.then(async (res) => {
-			if (!res.ok) throw await res.json();
-			return res.json();
+	let res = null;
+	if (q !== undefined) {
+		res = await fetch(`${WEBUI_API_BASE_URL}/users/?q=${q}`, {
+			method: 'GET',
+			headers: {
+				'Content-Type': 'application/json',
+				Authorization: `Bearer ${token}`
+			}
 		})
-		.catch((err) => {
-			console.log(err);
-			error = err.detail;
-			return null;
-		});
-
+			.then(async (res) => {
+				if (!res.ok) throw await res.json();
+				return res.json();
+			})
+			.catch((err) => {
+				console.log(err);
+				error = err.detail;
+				return null;
+			});
+		} else if (page !== undefined) {
+		res = await fetch(`${WEBUI_API_BASE_URL}/users/?page=${page}&limit=${limit}`, {
+			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.log(err);
+				error = err.detail;
+				return null;
+			});
+	} else {
+		res = await fetch(`${WEBUI_API_BASE_URL}/users/`, {
+			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.log(err);
+				error = err.detail;
+				return null;
+			});
+	}
 	if (error) {
 		throw error;
 	}
-
 	return res ? res : [];
 };
 

+ 3 - 1
src/lib/components/admin/Users.svelte

@@ -13,6 +13,7 @@
 	const i18n = getContext('i18n');
 
 	let users = [];
+	let totalUsers = 0;
 
 	let selectedTab = 'overview';
 	let loaded = false;
@@ -30,6 +31,7 @@
 			await goto('/');
 		} else {
 			users = await getUsers(localStorage.token);
+			totalUsers = users.length;
 		}
 		loaded = true;
 
@@ -102,7 +104,7 @@
 
 	<div class="flex-1 mt-1 lg:mt-0 overflow-y-scroll">
 		{#if selectedTab === 'overview'}
-			<UserList {users} />
+			<UserList {totalUsers}/>
 		{:else if selectedTab === 'groups'}
 			<Groups {users} />
 		{/if}

+ 30 - 21
src/lib/components/admin/Users/UserList.svelte

@@ -33,7 +33,8 @@
 
 	const i18n = getContext('i18n');
 
-	export let users = [];
+	export let totalUsers = 0;
+	let users = []
 
 	let search = '';
 	let selectedUser = null;
@@ -67,6 +68,13 @@
 		}
 	};
 
+	const fetchUserPage = async () => {
+		try {
+			users = await getUsers(localStorage.token, page);
+		} catch (err) {
+			console.error("Error fetching users: " + err);
+		}
+	};
 	let sortKey = 'created_at'; // default sort key
 	let sortOrder = 'asc'; // default sort order
 
@@ -79,25 +87,26 @@
 		}
 	}
 
-	let filteredUsers;
+	const queryUser = async (q) => {
+		try {
+			const result = await getUsers(localStorage.token, undefined, 10, q);
+			filteredUsers = result.slice((page - 1) * 10, page * 10);
+		} catch (err) {
+			console.error(err);
+		}
+	};
 
-	$: filteredUsers = users
-		.filter((user) => {
-			if (search === '') {
-				return true;
-			} else {
-				let name = user.name.toLowerCase();
-				let email = user.email.toLowerCase();
-				const query = search.toLowerCase();
-				return name.includes(query) || email.includes(query);
-			}
-		})
-		.sort((a, b) => {
-			if (a[sortKey] < b[sortKey]) return sortOrder === 'asc' ? -1 : 1;
-			if (a[sortKey] > b[sortKey]) return sortOrder === 'asc' ? 1 : -1;
-			return 0;
-		})
-		.slice((page - 1) * 20, page * 20);
+	let filteredUsers;
+	$: if (search.trim() === '') {
+		filteredUsers = users
+			.sort((a, b) => {
+				if (a[sortKey] < b[sortKey]) return sortOrder === 'asc' ? -1 : 1;
+				if (a[sortKey] > b[sortKey]) return sortOrder === 'asc' ? 1 : -1;
+				return 0;
+			})
+	} else {
+		queryUser(search);
+	}
 </script>
 
 <ConfirmDialog
@@ -486,10 +495,10 @@
 	ⓘ {$i18n.t("Click on the user role button to change a user's role.")}
 </div>
 
-<Pagination bind:page count={users.length} />
+<Pagination bind:page count={totalUsers} perPage={10}/>
 
 {#if !$config?.license_metadata}
-	{#if users.length > 50}
+	{#if totalUsers > 50}
 		<div class="text-sm">
 			<Markdown
 				content={`