Support pagination in /admin/users API

Update Users.svelte

Update index.ts

Update users.py

Update users.py

Update UserList.svelte
This commit is contained in:
KaeOhhhh 2025-04-28 11:11:41 +07:00 committed by Anapat Chaiwongse
parent 1349bc47b1
commit 2112067054
5 changed files with 115 additions and 45 deletions

View File

@ -160,11 +160,26 @@ class UsersTable:
return None return None
def get_users( 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]: ) -> list[UserModel]:
with get_db() as db: with get_db() as db:
if not query_key:
query = db.query(User).order_by(User.created_at.desc()) 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: if skip:
query = query.offset(skip) query = query.offset(skip)

View File

@ -35,11 +35,21 @@ router = APIRouter()
@router.get("/", response_model=list[UserModel]) @router.get("/", response_model=list[UserModel])
async def get_users( async def get_users(
skip: Optional[int] = None, page: Optional[int] = None,
limit: Optional[int] = None, limit: Optional[int] = None,
q: Optional[str] = None,
user=Depends(get_admin_user), 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)
############################ ############################

View File

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

View File

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

View File

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