mirror of
https://github.com/whit3rabbit/anyllm-proxy.git
synced 2026-09-22 00:00:50 +00:00
feat: add has_more to pagination responses, disable Next when no more results
Fetch limit+1 rows in get_requests and get_audit_log; if the extra row exists set has_more=true and truncate back to limit. Admin UI disables the Next button when has_more is false. Two new integration tests verify the field is present and false on an empty DB. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
01e04fa6b3
commit
0593d1ae1b
@@ -1078,6 +1078,7 @@ input:focus,select:focus{outline:none;border-color:#4a9eff}
|
||||
requests.forEach(function(r) { feed.appendChild(makeRequestRow(r)); });
|
||||
}
|
||||
document.getElementById('page-info').textContent = 'Page ' + (currentPage + 1);
|
||||
document.getElementById('next-page').disabled = !data.has_more;
|
||||
}).catch(function(e) { console.error('Requests load failed:', e); });
|
||||
}
|
||||
|
||||
@@ -1682,6 +1683,7 @@ input:focus,select:focus{outline:none;border-color:#4a9eff}
|
||||
clearChildren(container, false);
|
||||
var entries = data.entries || [];
|
||||
document.getElementById('audit-page-info').textContent = 'Page ' + (auditPage + 1);
|
||||
document.getElementById('audit-next-page').disabled = !data.has_more;
|
||||
if (entries.length === 0) {
|
||||
var auditMsg = auditPage === 0 ? 'No audit entries yet.' : 'No results on this page';
|
||||
container.appendChild(el('div', {className: 'empty', textContent: auditMsg}));
|
||||
|
||||
@@ -1046,7 +1046,7 @@ async fn get_requests(
|
||||
match crate::admin::state::with_db(&shared.db, move |conn| {
|
||||
crate::admin::db::query_request_log(
|
||||
conn,
|
||||
limit,
|
||||
limit + 1,
|
||||
offset,
|
||||
backend.as_deref(),
|
||||
since.as_deref(),
|
||||
@@ -1057,11 +1057,18 @@ async fn get_requests(
|
||||
})
|
||||
.await
|
||||
{
|
||||
Some(Ok(entries)) => Json(serde_json::json!({
|
||||
"requests": entries,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
})),
|
||||
Some(Ok(mut entries)) => {
|
||||
let has_more = entries.len() > limit as usize;
|
||||
if has_more {
|
||||
entries.truncate(limit as usize);
|
||||
}
|
||||
Json(serde_json::json!({
|
||||
"requests": entries,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
"has_more": has_more,
|
||||
}))
|
||||
}
|
||||
Some(Err(e)) => {
|
||||
tracing::error!(error = %e, "query_request_log failed");
|
||||
Json(serde_json::json!({
|
||||
@@ -1696,7 +1703,7 @@ async fn get_audit_log(
|
||||
match crate::admin::state::with_db(&shared.db, move |conn| {
|
||||
crate::admin::db::query_audit_log(
|
||||
conn,
|
||||
limit,
|
||||
limit + 1,
|
||||
offset,
|
||||
action.as_deref(),
|
||||
target_type.as_deref(),
|
||||
@@ -1706,11 +1713,18 @@ async fn get_audit_log(
|
||||
})
|
||||
.await
|
||||
{
|
||||
Some(Ok(entries)) => Json(serde_json::json!({
|
||||
"entries": entries,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
})),
|
||||
Some(Ok(mut entries)) => {
|
||||
let has_more = entries.len() > limit as usize;
|
||||
if has_more {
|
||||
entries.truncate(limit as usize);
|
||||
}
|
||||
Json(serde_json::json!({
|
||||
"entries": entries,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
"has_more": has_more,
|
||||
}))
|
||||
}
|
||||
Some(Err(e)) => {
|
||||
tracing::error!(error = %e, "query_audit_log failed");
|
||||
Json(serde_json::json!({
|
||||
|
||||
@@ -1095,3 +1095,55 @@ async fn add_model_rejects_unknown_backend() {
|
||||
body
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Pagination has_more field tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_requests_response_has_has_more_field() {
|
||||
let (app, _state) = test_admin_router();
|
||||
let req = Request::get("/admin/api/requests?limit=10&offset=0")
|
||||
.header("host", "localhost:9090")
|
||||
.header("authorization", "Bearer test-admin-token")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let resp = app.oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), 200);
|
||||
let body: serde_json::Value = serde_json::from_slice(
|
||||
&axum::body::to_bytes(resp.into_body(), 1 << 20)
|
||||
.await
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(
|
||||
body.get("has_more").is_some(),
|
||||
"response should include has_more field"
|
||||
);
|
||||
assert_eq!(body["has_more"], serde_json::Value::Bool(false));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_audit_response_has_has_more_field() {
|
||||
let (app, _state) = test_admin_router();
|
||||
let req = Request::get("/admin/api/audit?limit=10&offset=0")
|
||||
.header("host", "localhost:9090")
|
||||
.header("authorization", "Bearer test-admin-token")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let resp = app.oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), 200);
|
||||
let body: serde_json::Value = serde_json::from_slice(
|
||||
&axum::body::to_bytes(resp.into_body(), 1 << 20)
|
||||
.await
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(
|
||||
body.get("has_more").is_some(),
|
||||
"response should include has_more field"
|
||||
);
|
||||
assert_eq!(body["has_more"], serde_json::Value::Bool(false));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user