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
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)

View File

@ -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)
############################

View File

@ -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 : [];
};

View File

@ -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}

View File

@ -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={`