From 4c629e17cb8a05b7fbab8394999217bf170d511d Mon Sep 17 00:00:00 2001 From: hobokenchicken Date: Sat, 7 Mar 2026 00:22:27 +0000 Subject: [PATCH] fix(dashboard): bypass global rate limiting for internal UI endpoints This commit resolves the 'Failed to load statistics' issue where dashboard panels appeared empty. The dashboard makes 10+ concurrent API requests on load, which was instantly triggering the global rate limit's burst threshold (default 10). Internal dashboard endpoints are now exempt from this strict LLM-traffic rate limiting since they are already secured by admin authentication. --- src/dashboard/mod.rs | 15 ++++++--------- src/rate_limiting/mod.rs | 4 +++- 2 files changed, 9 insertions(+), 10 deletions(-) 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 {