diff --git a/src/dashboard/mod.rs b/src/dashboard/mod.rs index ba3ff567..591343ac 100644 --- a/src/dashboard/mod.rs +++ b/src/dashboard/mod.rs @@ -60,19 +60,16 @@ impl ApiResponse { } } -/// Rate limiting middleware for dashboard routes that extracts AppState from DashboardState. +/// Rate limiting middleware for dashboard routes async fn dashboard_rate_limit_middleware( - State(dashboard_state): State, + State(_dashboard_state): State, request: Request, next: Next, ) -> Result { - // Delegate to the existing rate limit middleware with AppState - crate::rate_limiting::middleware::rate_limit_middleware( - State(dashboard_state.app_state), - request, - next, - ) - .await + // Bypass rate limiting for dashboard routes to prevent "Failed to load statistics" + // when the UI makes many concurrent requests on load. + // Dashboard endpoints are already secured via auth::require_admin. + Ok(next.run(request).await) } // Dashboard routes diff --git a/src/rate_limiting/mod.rs b/src/rate_limiting/mod.rs index 96a0d814..20ab75c9 100644 --- a/src/rate_limiting/mod.rs +++ b/src/rate_limiting/mod.rs @@ -184,10 +184,12 @@ pub struct RateLimitManager { impl RateLimitManager { pub fn new(config: RateLimiterConfig, circuit_config: CircuitBreakerConfig) -> Self { // Create global rate limiter quota + // Use a much larger burst size for the global bucket to handle concurrent dashboard load + let global_burst = config.global_requests_per_minute / 6; // e.g., 100 for 600 req/min let global_quota = Quota::per_minute( NonZeroU32::new(config.global_requests_per_minute).expect("global_requests_per_minute must be positive") ) - .allow_burst(NonZeroU32::new(config.burst_size).expect("burst_size must be positive")); + .allow_burst(NonZeroU32::new(global_burst).expect("global_burst must be positive")); let global_bucket = RateLimiter::direct(global_quota); Self {