diff --git a/frontend-modern/src/components/Settings/Settings.tsx b/frontend-modern/src/components/Settings/Settings.tsx index 9ca28f787..1861b644c 100644 --- a/frontend-modern/src/components/Settings/Settings.tsx +++ b/frontend-modern/src/components/Settings/Settings.tsx @@ -344,12 +344,19 @@ const Settings: Component = () => { }); try { - // Load security status + // Load data with small delays to prevent rate limit bursts + // Load security status first as it's lightweight await loadSecurityStatus(); + // Small delay to prevent burst + await new Promise(resolve => setTimeout(resolve, 50)); + // Load nodes await loadNodes(); + // Another small delay + await new Promise(resolve => setTimeout(resolve, 50)); + // Load discovered nodes await loadDiscoveredNodes(); diff --git a/frontend-modern/src/utils/apiClient.ts b/frontend-modern/src/utils/apiClient.ts index 7f28fc41b..d19211b27 100644 --- a/frontend-modern/src/utils/apiClient.ts +++ b/frontend-modern/src/utils/apiClient.ts @@ -147,9 +147,23 @@ class ApiClient { } } - // Handle rate limiting + // Handle rate limiting with automatic retry if (response.status === 429) { - console.error('Rate limit exceeded - please wait before retrying'); + const retryAfter = response.headers.get('Retry-After'); + const waitTime = retryAfter ? parseInt(retryAfter) * 1000 : 2000; // Default 2 seconds + + console.warn(`Rate limit hit, retrying after ${waitTime}ms`); + + // Wait and retry once + await new Promise(resolve => setTimeout(resolve, waitTime)); + + const retryResponse = await fetch(url, { + ...fetchOptions, + headers: finalHeaders, + credentials: 'include' + }); + + return retryResponse; } return response; diff --git a/internal/api/rate_limit_config.go b/internal/api/rate_limit_config.go index 8a3b813f2..b9573bde6 100644 --- a/internal/api/rate_limit_config.go +++ b/internal/api/rate_limit_config.go @@ -85,6 +85,13 @@ func GetRateLimiterForEndpoint(path string, method string) *RateLimiter { return globalRateLimitConfig.ConfigEndpoints } + // Configuration read endpoints get higher limits to prevent UI issues + if method == "GET" && (strings.Contains(path, "/api/config/") || + strings.Contains(path, "/api/discover") || + strings.Contains(path, "/api/security/status")) { + return globalRateLimitConfig.PublicEndpoints // Use higher limit for reads + } + // Update endpoints if strings.Contains(path, "/api/updates") { return globalRateLimitConfig.UpdateEndpoints