Microservice to bring 2FA to self hosted PDSes

accounts pagination

+68 -6
+12
html_templates/admin/accounts.hbs
··· 49 49 </tbody> 50 50 </table> 51 51 </div> 52 + 53 + {{#if (or has_prev has_next)}} 54 + <div class="pagination"> 55 + {{#if has_prev}} 56 + <a href="{{prev_url}}" class="btn btn-small">&larr; Previous</a> 57 + {{/if}} 58 + <span class="pagination-info">Showing {{account_count}} accounts</span> 59 + {{#if has_next}} 60 + <a href="{{next_url}}" class="btn btn-small">Next &rarr;</a> 61 + {{/if}} 62 + </div> 63 + {{/if}} 52 64 {{else}} 53 65 <div class="empty-state"> 54 66 {{#if search_query}}
+43 -6
src/admin/routes.rs
··· 38 38 } 39 39 40 40 #[derive(Debug, Deserialize)] 41 + pub struct AccountsParams { 42 + pub cursor: Option<String>, 43 + pub flash_success: Option<String>, 44 + pub flash_error: Option<String>, 45 + } 46 + 47 + #[derive(Debug, Deserialize)] 41 48 pub struct InviteCodesParams { 42 49 pub cursor: Option<String>, 43 50 pub flash_success: Option<String>, ··· 278 285 render_template(&state, "admin/dashboard.hbs", data) 279 286 } 280 287 281 - /// GET /admin/accounts — Account list 288 + /// GET /admin/accounts — Account list (paginated, 100 per page) 282 289 pub async fn accounts_list( 283 290 State(state): State<AppState>, 284 291 Extension(session): Extension<AdminSession>, 285 292 Extension(permissions): Extension<AdminPermissions>, 286 - Query(flash): Query<FlashParams>, 293 + Query(params): Query<AccountsParams>, 287 294 ) -> Response { 288 295 if !permissions.can_view_accounts { 289 296 return flash_redirect("/admin/", None, Some("Access denied")); ··· 292 299 let pds = pds_url(&state); 293 300 let password = admin_password(&state); 294 301 295 - // Get all repos first 302 + // Build query params for listRepos with pagination 303 + let limit = 5; 304 + // Yeah I know this looks bad, but I'd like to have the limit in one spot instead of two 305 + let limit_as_string = limit.to_string(); 306 + let limit_as_str = limit_as_string.as_str(); 307 + let mut query_params: Vec<(&str, &str)> = vec![("limit", limit_as_str)]; 308 + let cursor_val; 309 + if let Some(ref c) = params.cursor { 310 + cursor_val = c.clone(); 311 + query_params.push(("cursor", &cursor_val)); 312 + } 313 + 296 314 let repos = match pds_proxy::public_xrpc_get::<serde_json::Value>( 297 315 pds, 298 316 "com.atproto.sync.listRepos", 299 - &[("limit", "1000")], 317 + &query_params, 300 318 ) 301 319 .await 302 320 { ··· 311 329 } 312 330 }; 313 331 332 + // Extract the next-page cursor from the response 333 + let next_cursor = repos["cursor"].as_str().map(|s| s.to_string()); 334 + 314 335 let dids: Vec<String> = repos["repos"] 315 336 .as_array() 316 337 .map(|arr| { ··· 349 370 "active_page": "accounts", 350 371 }); 351 372 352 - if let Some(msg) = flash.flash_success { 373 + // Pagination: "Next" link 374 + if let Some(ref cursor) = next_cursor { 375 + // If the count returned is not the same as the limit, then we are at the end of the list 376 + if dids.len() == limit { 377 + data["has_next"] = true.into(); 378 + let next_url = format!("/admin/accounts?cursor={}", urlencoding::encode(cursor)); 379 + data["next_url"] = next_url.into(); 380 + } 381 + } 382 + 383 + // Pagination: "Previous" link (visible when not on page 1) 384 + if params.cursor.is_some() { 385 + data["has_prev"] = true.into(); 386 + data["prev_url"] = "/admin/accounts".to_string().into(); 387 + } 388 + 389 + if let Some(msg) = params.flash_success { 353 390 data["flash_success"] = msg.into(); 354 391 } 355 - if let Some(msg) = flash.flash_error { 392 + if let Some(msg) = params.flash_error { 356 393 data["flash_error"] = msg.into(); 357 394 } 358 395
+13
static/css/admin.css
··· 499 499 text-decoration: underline; 500 500 } 501 501 502 + .pagination { 503 + display: flex; 504 + align-items: center; 505 + justify-content: center; 506 + gap: 16px; 507 + padding: 16px 0; 508 + } 509 + 510 + .pagination-info { 511 + font-size: 0.8125rem; 512 + color: var(--secondary-color); 513 + } 514 + 502 515 /* --- Dashboard: Cards --------------------------------------- */ 503 516 504 517 .cards {