fix(dashboard): bypass global rate limiting for internal UI endpoints
Some checks failed
CI / Check (push) Has been cancelled
CI / Clippy (push) Has been cancelled
CI / Formatting (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Release Build (push) Has been cancelled

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.
This commit is contained in:
2026-03-07 00:22:27 +00:00
parent fc3bc6968d
commit 4c629e17cb
2 changed files with 9 additions and 10 deletions

View File

@@ -60,19 +60,16 @@ impl<T> ApiResponse<T> {
} }
} }
/// Rate limiting middleware for dashboard routes that extracts AppState from DashboardState. /// Rate limiting middleware for dashboard routes
async fn dashboard_rate_limit_middleware( async fn dashboard_rate_limit_middleware(
State(dashboard_state): State<DashboardState>, State(_dashboard_state): State<DashboardState>,
request: Request, request: Request,
next: Next, next: Next,
) -> Result<Response, crate::errors::AppError> { ) -> Result<Response, crate::errors::AppError> {
// Delegate to the existing rate limit middleware with AppState // Bypass rate limiting for dashboard routes to prevent "Failed to load statistics"
crate::rate_limiting::middleware::rate_limit_middleware( // when the UI makes many concurrent requests on load.
State(dashboard_state.app_state), // Dashboard endpoints are already secured via auth::require_admin.
request, Ok(next.run(request).await)
next,
)
.await
} }
// Dashboard routes // Dashboard routes

View File

@@ -184,10 +184,12 @@ pub struct RateLimitManager {
impl RateLimitManager { impl RateLimitManager {
pub fn new(config: RateLimiterConfig, circuit_config: CircuitBreakerConfig) -> Self { pub fn new(config: RateLimiterConfig, circuit_config: CircuitBreakerConfig) -> Self {
// Create global rate limiter quota // 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( let global_quota = Quota::per_minute(
NonZeroU32::new(config.global_requests_per_minute).expect("global_requests_per_minute must be positive") 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); let global_bucket = RateLimiter::direct(global_quota);
Self { Self {