feat: Enhance frontend loading experience and service status handling
Some checks failed
Integration Tests / integration-tests (push) Failing after 20s
Integration Tests / performance-tests (push) Has been skipped
Service Adapters (Python FastAPI) / test (3.11) (push) Failing after 23s
Frontend (React) / test (20) (push) Failing after 1m3s
Frontend (React) / build (push) Has been skipped
Frontend (React) / lighthouse (push) Has been skipped
Service Adapters (Python FastAPI) / test (3.12) (push) Failing after 23s
Service Adapters (Python FastAPI) / test (3.13) (push) Failing after 20s
Service Adapters (Python FastAPI) / build (push) Has been skipped

### Summary of Changes
- Removed proxy configuration in `rsbuild.config.js` as the API Gateway is not running.
- Added smooth transitions and gentle loading overlays in CSS for improved user experience during data loading.
- Updated `Dashboard` component to conditionally display loading spinner and gentle loading overlay based on data fetching state.
- Enhanced `useOfflineAwareServiceStatus` and `useOfflineAwareSystemData` hooks to manage loading states and service status more effectively.
- Increased refresh intervals for service status and system data to reduce API call frequency.

### Expected Results
- Improved user experience with smoother loading transitions and better feedback during data refreshes.
- Enhanced handling of service status checks, providing clearer information when services are unavailable.
- Streamlined code for managing loading states, making it easier to maintain and extend in the future.
This commit is contained in:
GSRN
2025-09-18 11:09:51 +02:00
parent 48c755dff3
commit 7373ccfa1d
30 changed files with 2402 additions and 89 deletions

View File

@@ -0,0 +1,104 @@
import { serviceAdapters, apiDocs } from '../services/api';
class RequestManager {
constructor() {
this.pendingRequests = new Map();
this.requestTimeouts = new Map();
}
/**
* Debounced request function that cancels previous requests of the same type
* @param {string} requestType - Type of request (e.g., 'serviceStatus', 'systemData')
* @param {Function} requestFunction - The actual request function to execute
* @param {number} debounceMs - Debounce delay in milliseconds
* @returns {Promise} - Promise that resolves with the request result
*/
async debouncedRequest(requestType, requestFunction, _debounceMs = 1000) {
// Cancel any pending request of the same type
if (this.pendingRequests.has(requestType)) {
const { controller, timeoutId } = this.pendingRequests.get(requestType);
controller.abort();
clearTimeout(timeoutId);
}
// Create new abort controller for this request
const controller = new AbortController();
const timeoutId = setTimeout(() => {
controller.abort();
}, 30000); // 30 second timeout
// Store the request info
this.pendingRequests.set(requestType, { controller, timeoutId });
try {
const result = await requestFunction(controller.signal);
this.pendingRequests.delete(requestType);
clearTimeout(timeoutId);
return result;
} catch (error) {
this.pendingRequests.delete(requestType);
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('Request was cancelled');
}
throw error;
}
}
/**
* Get service status with debouncing
*/
async getServiceStatus(_signal) {
const [adaptersResult, docsResult] = await Promise.allSettled([
serviceAdapters.health(),
apiDocs.health()
]);
return {
adapters: adaptersResult,
docs: docsResult
};
}
/**
* Get system data with debouncing
*/
async getSystemData(_signal) {
const [servicesResult, eventsResult] = await Promise.allSettled([
serviceAdapters.getServices(),
serviceAdapters.getEvents(10)
]);
return {
services: servicesResult,
events: eventsResult
};
}
/**
* Cancel all pending requests
*/
cancelAllRequests() {
this.pendingRequests.forEach(({ controller, timeoutId }) => {
controller.abort();
clearTimeout(timeoutId);
});
this.pendingRequests.clear();
}
/**
* Cancel specific request type
*/
cancelRequest(requestType) {
if (this.pendingRequests.has(requestType)) {
const { controller, timeoutId } = this.pendingRequests.get(requestType);
controller.abort();
clearTimeout(timeoutId);
this.pendingRequests.delete(requestType);
}
}
}
// Export singleton instance
export const requestManager = new RequestManager();