diff --git a/backend/open_webui/models/users.py b/backend/open_webui/models/users.py index 605299528..69691a5d5 100644 --- a/backend/open_webui/models/users.py +++ b/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) diff --git a/backend/open_webui/routers/users.py b/backend/open_webui/routers/users.py index 29638199e..cac7215a4 100644 --- a/backend/open_webui/routers/users.py +++ b/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) ############################ diff --git a/src/lib/apis/users/index.ts b/src/lib/apis/users/index.ts index f479b130c..d8a7bb871 100644 --- a/src/lib/apis/users/index.ts +++ b/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 : []; }; diff --git a/src/lib/components/admin/Users.svelte b/src/lib/components/admin/Users.svelte index 00659ad3e..7abde9dc5 100644 --- a/src/lib/components/admin/Users.svelte +++ b/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 @@
{#if selectedTab === 'overview'} - + {:else if selectedTab === 'groups'} {/if} diff --git a/src/lib/components/admin/Users/UserList.svelte b/src/lib/components/admin/Users/UserList.svelte index 02fcdd717..b36940dae 100644 --- a/src/lib/components/admin/Users/UserList.svelte +++ b/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); + } - + {#if !$config?.license_metadata} - {#if users.length > 50} + {#if totalUsers > 50}