diff --git a/backend/open_webui/models/users.py b/backend/open_webui/models/users.py index 69691a5d5..3b95d1609 100644 --- a/backend/open_webui/models/users.py +++ b/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: diff --git a/backend/open_webui/routers/users.py b/backend/open_webui/routers/users.py index cac7215a4..0c348f51e 100644 --- a/backend/open_webui/routers/users.py +++ b/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() ############################ diff --git a/src/lib/apis/users/index.ts b/src/lib/apis/users/index.ts index d8a7bb871..85f5f2252 100644 --- a/src/lib/apis/users/index.ts +++ b/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) => { diff --git a/src/lib/components/admin/Users.svelte b/src/lib/components/admin/Users.svelte index 7abde9dc5..e777757e0 100644 --- a/src/lib/components/admin/Users.svelte +++ b/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 @@