Quellcode durchsuchen

refac: user list sub-standard code

Timothy Jaeryang Baek vor 3 Monaten
Ursprung
Commit
f9d238e850

+ 60 - 14
backend/open_webui/models/users.py

@@ -10,6 +10,8 @@ from open_webui.models.groups import Groups
 
 from pydantic import BaseModel, ConfigDict
 from sqlalchemy import BigInteger, Column, String, Text
+from sqlalchemy import or_
+
 
 ####################
 # User DB Schema
@@ -67,6 +69,11 @@ class UserModel(BaseModel):
 ####################
 
 
+class UserListResponse(BaseModel):
+    users: list[UserModel]
+    total: int
+
+
 class UserResponse(BaseModel):
     id: str
     name: str
@@ -161,25 +168,62 @@ class UsersTable:
 
     def get_users(
         self,
+        filter: Optional[dict] = None,
         skip: Optional[int] = None,
         limit: Optional[int] = None,
-        query_key: Optional[int] = None
-    ) -> list[UserModel]:
+    ) -> UserListResponse:
         with get_db() as db:
+            query = db.query(User)
 
-            if not query_key:
-                query = db.query(User).order_by(User.created_at.desc())
-            else:
-                query = (
-                    db.query(User)
-                    .filter(
+            if filter:
+                query_key = filter.get("query")
+                if query_key:
+                    query = query.filter(
                         or_(
-                            User.name.ilike(f'%{query_key}%'),
-                            User.email.ilike(f'%{query_key}%')
+                            User.name.ilike(f"%{query_key}%"),
+                            User.email.ilike(f"%{query_key}%"),
                         )
                     )
-                    .order_by(User.created_at.desc())
-                )
+
+                order_by = filter.get("order_by")
+                direction = filter.get("direction")
+
+                if order_by == "name":
+                    if direction == "asc":
+                        query = query.order_by(User.name.asc())
+                    else:
+                        query = query.order_by(User.name.desc())
+                elif order_by == "email":
+                    if direction == "asc":
+                        query = query.order_by(User.email.asc())
+                    else:
+                        query = query.order_by(User.email.desc())
+
+                elif order_by == "created_at":
+                    if direction == "asc":
+                        query = query.order_by(User.created_at.asc())
+                    else:
+                        query = query.order_by(User.created_at.desc())
+
+                elif order_by == "last_active_at":
+                    if direction == "asc":
+                        query = query.order_by(User.last_active_at.asc())
+                    else:
+                        query = query.order_by(User.last_active_at.desc())
+
+                elif order_by == "updated_at":
+                    if direction == "asc":
+                        query = query.order_by(User.updated_at.asc())
+                    else:
+                        query = query.order_by(User.updated_at.desc())
+                elif order_by == "role":
+                    if direction == "asc":
+                        query = query.order_by(User.role.asc())
+                    else:
+                        query = query.order_by(User.role.desc())
+
+            else:
+                query = query.order_by(User.created_at.desc())
 
             if skip:
                 query = query.offset(skip)
@@ -187,8 +231,10 @@ class UsersTable:
                 query = query.limit(limit)
 
             users = query.all()
-
-            return [UserModel.model_validate(user) for user in users]
+            return {
+                "users": [UserModel.model_validate(user) for user in users],
+                "total": db.query(User).count(),
+            }
 
     def get_users_by_user_ids(self, user_ids: list[str]) -> list[UserModel]:
         with get_db() as db:

+ 30 - 14
backend/open_webui/routers/users.py

@@ -6,6 +6,7 @@ from open_webui.models.groups import Groups
 from open_webui.models.chats import Chats
 from open_webui.models.users import (
     UserModel,
+    UserListResponse,
     UserRoleUpdateForm,
     Users,
     UserSettings,
@@ -33,23 +34,38 @@ router = APIRouter()
 ############################
 
 
-@router.get("/", response_model=list[UserModel])
+PAGE_ITEM_COUNT = 10
+
+
+@router.get("/", response_model=UserListResponse)
 async def get_users(
-    page: Optional[int] = None,
-    limit: Optional[int] = None,
-    q: Optional[str] = None,
+    query: Optional[str] = None,
+    order_by: Optional[str] = None,
+    direction: Optional[str] = None,
+    page: Optional[int] = 1,
     user=Depends(get_admin_user),
 ):
-    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)
+    limit = PAGE_ITEM_COUNT
+
+    page = max(1, page)
+    skip = (page - 1) * limit
+
+    filter = {}
+    if query:
+        filter["query"] = query
+    if order_by:
+        filter["order_by"] = order_by
+    if direction:
+        filter["direction"] = direction
+
+    return Users.get_users(filter=filter, skip=skip, limit=limit)
+
+
+@router.get("/all", response_model=UserListResponse)
+async def get_all_users(
+    user=Depends(get_admin_user),
+):
+    return Users.get_users()
 
 
 ############################

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

@@ -116,65 +116,54 @@ export const updateUserRole = async (token: string, id: string, role: string) =>
 	return res;
 };
 
-export const getUsers = async (token: string, page?: number, limit: number = 10, q?: string) => {
+export const getUsers = async (
+	token: string,
+	query?: string,
+	orderBy?: string,
+	direction?: string,
+	page = 1
+) => {
 	let error = null;
 	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}`
-			}
-		})
-			.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;
-			});
+
+	let searchParams = new URLSearchParams();
+
+	searchParams.set('page', `${page}`);
+
+	if (query) {
+		searchParams.set('query', query);
 	}
+
+	if (orderBy) {
+		searchParams.set('order_by', orderBy);
+	}
+
+	if (direction) {
+		searchParams.set('direction', direction);
+	}
+
+	res = await fetch(`${WEBUI_API_BASE_URL}/users/?${searchParams.toString()}`, {
+		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 : [];
+
+	return res;
 };
 
 export const getUserSettings = async (token: string) => {

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

@@ -5,34 +5,19 @@
 	import { goto } from '$app/navigation';
 	import { user } from '$lib/stores';
 
-	import { getUsers } from '$lib/apis/users';
-
 	import UserList from './Users/UserList.svelte';
 	import Groups from './Users/Groups.svelte';
 
 	const i18n = getContext('i18n');
 
-	let users = [];
-	let totalUsers = 0;
-
 	let selectedTab = 'overview';
 	let loaded = false;
 
-	$: if (selectedTab) {
-		getUsersHandler();
-	}
-
-	const getUsersHandler = async () => {
-		users = await getUsers(localStorage.token);
-	};
-
 	onMount(async () => {
 		if ($user?.role !== 'admin') {
 			await goto('/');
-		} else {
-			users = await getUsers(localStorage.token);
-			totalUsers = users.length;
 		}
+
 		loaded = true;
 
 		const containerElement = document.getElementById('users-tabs-container');
@@ -104,9 +89,9 @@
 
 	<div class="flex-1 mt-1 lg:mt-0 overflow-y-scroll">
 		{#if selectedTab === 'overview'}
-			<UserList {totalUsers}/>
+			<UserList />
 		{:else if selectedTab === 'groups'}
-			<Groups {users} />
+			<Groups />
 		{/if}
 	</div>
 </div>

+ 52 - 54
src/lib/components/admin/Users/UserList.svelte

@@ -33,13 +33,16 @@
 
 	const i18n = getContext('i18n');
 
-	export let totalUsers = 0;
-	let users = []
+	let page = 1;
 
-	let search = '';
-	let selectedUser = null;
+	let users = [];
+	let total = 0;
 
-	let page = 1;
+	let query = '';
+	let orderBy = 'created_at'; // default sort key
+	let direction = 'asc'; // default sort order
+
+	let selectedUser = null;
 
 	let showDeleteConfirmDialog = false;
 	let showAddUserModal = false;
@@ -54,7 +57,7 @@
 		});
 
 		if (res) {
-			users = await getUsers(localStorage.token);
+			getUserList();
 		}
 	};
 
@@ -64,48 +67,43 @@
 			return null;
 		});
 		if (res) {
-			users = await getUsers(localStorage.token);
-		}
-	};
-
-	const fetchUserPage = async () => {
-		try {
-			users = await getUsers(localStorage.token, page);
-		} catch (err) {
-			console.error("Error fetching users: " + err);
+			getUserList();
 		}
 	};
-	let sortKey = 'created_at'; // default sort key
-	let sortOrder = 'asc'; // default sort order
 
-	function setSortKey(key) {
-		if (sortKey === key) {
-			sortOrder = sortOrder === 'asc' ? 'desc' : 'asc';
+	const setSortKey = (key) => {
+		if (orderBy === key) {
+			direction = direction === 'asc' ? 'desc' : 'asc';
 		} else {
-			sortKey = key;
-			sortOrder = 'asc';
+			orderBy = key;
+			direction = 'asc';
 		}
-	}
+	};
 
-	const queryUser = async (q) => {
+	const getUserList = async () => {
 		try {
-			const result = await getUsers(localStorage.token, undefined, 10, q);
-			filteredUsers = result.slice((page - 1) * 10, page * 10);
+			const res = await getUsers(localStorage.token, query, orderBy, direction, page).catch(
+				(error) => {
+					toast.error(`${error}`);
+					return null;
+				}
+			);
+
+			if (res) {
+				users = res.users;
+				total = res.total;
+			}
 		} catch (err) {
 			console.error(err);
 		}
 	};
 
-	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);
+	$: if (page) {
+		getUserList();
+	}
+
+	$: if (query !== null && orderBy && direction) {
+		getUserList();
 	}
 </script>
 
@@ -122,7 +120,7 @@
 		{selectedUser}
 		sessionUser={$user}
 		on:save={async () => {
-			users = await getUsers(localStorage.token);
+			getUserList();
 		}}
 	/>
 {/key}
@@ -130,7 +128,7 @@
 <AddUserModal
 	bind:show={showAddUserModal}
 	on:save={async () => {
-		users = await getUsers(localStorage.token);
+		getUserList();
 	}}
 />
 <UserChatsModal bind:show={showUserChatsModal} user={selectedUser} />
@@ -193,7 +191,7 @@
 				</div>
 				<input
 					class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-hidden bg-transparent"
-					bind:value={search}
+					bind:value={query}
 					placeholder={$i18n.t('Search')}
 				/>
 			</div>
@@ -232,9 +230,9 @@
 					<div class="flex gap-1.5 items-center">
 						{$i18n.t('Role')}
 
-						{#if sortKey === 'role'}
+						{#if orderBy === 'role'}
 							<span class="font-normal"
-								>{#if sortOrder === 'asc'}
+								>{#if direction === 'asc'}
 									<ChevronUp className="size-2" />
 								{:else}
 									<ChevronDown className="size-2" />
@@ -255,9 +253,9 @@
 					<div class="flex gap-1.5 items-center">
 						{$i18n.t('Name')}
 
-						{#if sortKey === 'name'}
+						{#if orderBy === 'name'}
 							<span class="font-normal"
-								>{#if sortOrder === 'asc'}
+								>{#if direction === 'asc'}
 									<ChevronUp className="size-2" />
 								{:else}
 									<ChevronDown className="size-2" />
@@ -278,9 +276,9 @@
 					<div class="flex gap-1.5 items-center">
 						{$i18n.t('Email')}
 
-						{#if sortKey === 'email'}
+						{#if orderBy === 'email'}
 							<span class="font-normal"
-								>{#if sortOrder === 'asc'}
+								>{#if direction === 'asc'}
 									<ChevronUp className="size-2" />
 								{:else}
 									<ChevronDown className="size-2" />
@@ -302,9 +300,9 @@
 					<div class="flex gap-1.5 items-center">
 						{$i18n.t('Last Active')}
 
-						{#if sortKey === 'last_active_at'}
+						{#if orderBy === 'last_active_at'}
 							<span class="font-normal"
-								>{#if sortOrder === 'asc'}
+								>{#if direction === 'asc'}
 									<ChevronUp className="size-2" />
 								{:else}
 									<ChevronDown className="size-2" />
@@ -324,9 +322,9 @@
 				>
 					<div class="flex gap-1.5 items-center">
 						{$i18n.t('Created at')}
-						{#if sortKey === 'created_at'}
+						{#if orderBy === 'created_at'}
 							<span class="font-normal"
-								>{#if sortOrder === 'asc'}
+								>{#if direction === 'asc'}
 									<ChevronUp className="size-2" />
 								{:else}
 									<ChevronDown className="size-2" />
@@ -348,9 +346,9 @@
 					<div class="flex gap-1.5 items-center">
 						{$i18n.t('OAuth ID')}
 
-						{#if sortKey === 'oauth_sub'}
+						{#if orderBy === 'oauth_sub'}
 							<span class="font-normal"
-								>{#if sortOrder === 'asc'}
+								>{#if direction === 'asc'}
 									<ChevronUp className="size-2" />
 								{:else}
 									<ChevronDown className="size-2" />
@@ -368,7 +366,7 @@
 			</tr>
 		</thead>
 		<tbody class="">
-			{#each filteredUsers as user, userIdx}
+			{#each users as user, userIdx}
 				<tr class="bg-white dark:bg-gray-900 dark:border-gray-850 text-xs">
 					<td class="px-3 py-1 min-w-[7rem] w-28">
 						<button
@@ -495,10 +493,10 @@
 	ⓘ {$i18n.t("Click on the user role button to change a user's role.")}
 </div>
 
-<Pagination bind:page count={totalUsers} perPage={10}/>
+<Pagination bind:page count={total} perPage={10} />
 
 {#if !$config?.license_metadata}
-	{#if totalUsers > 50}
+	{#if total > 50}
 		<div class="text-sm">
 			<Markdown
 				content={`