fix: Gitea Traefik routing and connection pool optimization
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
This commit is contained in:
307
resources/js/modules/livecomponent/LoadingStateManager.js
Normal file
307
resources/js/modules/livecomponent/LoadingStateManager.js
Normal file
@@ -0,0 +1,307 @@
|
||||
/**
|
||||
* Loading State Manager for LiveComponents
|
||||
*
|
||||
* Manages different loading indicators (skeleton, spinner, progress) during actions.
|
||||
* Integrates with OptimisticStateManager and ActionLoadingManager.
|
||||
*/
|
||||
|
||||
export class LoadingStateManager {
|
||||
constructor(actionLoadingManager, optimisticStateManager) {
|
||||
this.actionLoadingManager = actionLoadingManager;
|
||||
this.optimisticStateManager = optimisticStateManager;
|
||||
this.loadingConfigs = new Map(); // componentId → loading config
|
||||
this.config = {
|
||||
defaultType: 'skeleton', // skeleton, spinner, progress, none
|
||||
showDelay: 150,
|
||||
hideDelay: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure loading state for component
|
||||
*
|
||||
* @param {string} componentId - Component ID
|
||||
* @param {Object} config - Loading configuration
|
||||
*/
|
||||
configure(componentId, config) {
|
||||
this.loadingConfigs.set(componentId, {
|
||||
type: config.type || this.config.defaultType,
|
||||
showDelay: config.showDelay ?? this.config.showDelay,
|
||||
hideDelay: config.hideDelay ?? this.config.hideDelay,
|
||||
template: config.template || null,
|
||||
...config
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show loading state for action
|
||||
*
|
||||
* @param {string} componentId - Component ID
|
||||
* @param {HTMLElement} element - Component element
|
||||
* @param {Object} options - Loading options
|
||||
*/
|
||||
showLoading(componentId, element, options = {}) {
|
||||
const config = this.loadingConfigs.get(componentId) || {
|
||||
type: this.config.defaultType,
|
||||
showDelay: this.config.showDelay
|
||||
};
|
||||
|
||||
const loadingType = options.type || config.type;
|
||||
|
||||
// Skip if type is 'none' or optimistic UI is enabled
|
||||
if (loadingType === 'none') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if optimistic UI is active (no loading needed)
|
||||
const pendingOps = this.optimisticStateManager.getPendingOperations(componentId);
|
||||
if (pendingOps.length > 0 && options.optimistic !== false) {
|
||||
// Optimistic UI is active - skip loading indicator
|
||||
return;
|
||||
}
|
||||
|
||||
switch (loadingType) {
|
||||
case 'skeleton':
|
||||
this.actionLoadingManager.showLoading(componentId, element, {
|
||||
...options,
|
||||
showDelay: config.showDelay
|
||||
});
|
||||
break;
|
||||
|
||||
case 'spinner':
|
||||
this.showSpinner(componentId, element, config);
|
||||
break;
|
||||
|
||||
case 'progress':
|
||||
this.showProgress(componentId, element, config);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Fallback to skeleton
|
||||
this.actionLoadingManager.showLoading(componentId, element, options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide loading state
|
||||
*
|
||||
* @param {string} componentId - Component ID
|
||||
*/
|
||||
hideLoading(componentId) {
|
||||
// Hide skeleton loading
|
||||
this.actionLoadingManager.hideLoading(componentId);
|
||||
|
||||
// Hide spinner
|
||||
this.hideSpinner(componentId);
|
||||
|
||||
// Hide progress
|
||||
this.hideProgress(componentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show spinner loading indicator
|
||||
*
|
||||
* @param {string} componentId - Component ID
|
||||
* @param {HTMLElement} element - Component element
|
||||
* @param {Object} config - Configuration
|
||||
*/
|
||||
showSpinner(componentId, element, config) {
|
||||
// Remove existing spinner
|
||||
this.hideSpinner(componentId);
|
||||
|
||||
const spinner = document.createElement('div');
|
||||
spinner.className = 'livecomponent-loading-spinner';
|
||||
spinner.setAttribute('data-component-id', componentId);
|
||||
spinner.setAttribute('aria-busy', 'true');
|
||||
spinner.setAttribute('aria-label', 'Loading...');
|
||||
|
||||
spinner.innerHTML = `
|
||||
<div class="spinner"></div>
|
||||
<span class="spinner-text">Loading...</span>
|
||||
`;
|
||||
|
||||
spinner.style.cssText = `
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
z-index: 10;
|
||||
opacity: 0;
|
||||
transition: opacity ${this.config.showDelay}ms ease;
|
||||
`;
|
||||
|
||||
// Ensure element has relative positioning
|
||||
if (getComputedStyle(element).position === 'static') {
|
||||
element.style.position = 'relative';
|
||||
}
|
||||
|
||||
element.appendChild(spinner);
|
||||
|
||||
// Animate in
|
||||
requestAnimationFrame(() => {
|
||||
spinner.style.opacity = '1';
|
||||
});
|
||||
|
||||
// Store reference
|
||||
const loadingConfig = this.loadingConfigs.get(componentId) || {};
|
||||
loadingConfig.spinner = spinner;
|
||||
this.loadingConfigs.set(componentId, loadingConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide spinner
|
||||
*
|
||||
* @param {string} componentId - Component ID
|
||||
*/
|
||||
hideSpinner(componentId) {
|
||||
const config = this.loadingConfigs.get(componentId);
|
||||
if (!config || !config.spinner) {
|
||||
return;
|
||||
}
|
||||
|
||||
const spinner = config.spinner;
|
||||
spinner.style.opacity = '0';
|
||||
|
||||
setTimeout(() => {
|
||||
if (spinner.parentNode) {
|
||||
spinner.parentNode.removeChild(spinner);
|
||||
}
|
||||
delete config.spinner;
|
||||
}, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show progress bar
|
||||
*
|
||||
* @param {string} componentId - Component ID
|
||||
* @param {HTMLElement} element - Component element
|
||||
* @param {Object} config - Configuration
|
||||
*/
|
||||
showProgress(componentId, element, config) {
|
||||
// Remove existing progress
|
||||
this.hideProgress(componentId);
|
||||
|
||||
const progress = document.createElement('div');
|
||||
progress.className = 'livecomponent-loading-progress';
|
||||
progress.setAttribute('data-component-id', componentId);
|
||||
progress.setAttribute('aria-busy', 'true');
|
||||
|
||||
progress.innerHTML = `
|
||||
<div class="progress-bar" style="width: 0%;"></div>
|
||||
`;
|
||||
|
||||
progress.style.cssText = `
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background: #e0e0e0;
|
||||
z-index: 10;
|
||||
opacity: 0;
|
||||
transition: opacity ${this.config.showDelay}ms ease;
|
||||
`;
|
||||
|
||||
const progressBar = progress.querySelector('.progress-bar');
|
||||
progressBar.style.cssText = `
|
||||
height: 100%;
|
||||
background: #2196F3;
|
||||
transition: width 0.3s ease;
|
||||
`;
|
||||
|
||||
// Ensure element has relative positioning
|
||||
if (getComputedStyle(element).position === 'static') {
|
||||
element.style.position = 'relative';
|
||||
}
|
||||
|
||||
element.appendChild(progress);
|
||||
|
||||
// Animate in
|
||||
requestAnimationFrame(() => {
|
||||
progress.style.opacity = '1';
|
||||
// Simulate progress (can be updated via updateProgress)
|
||||
this.updateProgress(componentId, 30);
|
||||
});
|
||||
|
||||
// Store reference
|
||||
const loadingConfig = this.loadingConfigs.get(componentId) || {};
|
||||
loadingConfig.progress = progress;
|
||||
this.loadingConfigs.set(componentId, loadingConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update progress bar
|
||||
*
|
||||
* @param {string} componentId - Component ID
|
||||
* @param {number} percent - Progress percentage (0-100)
|
||||
*/
|
||||
updateProgress(componentId, percent) {
|
||||
const config = this.loadingConfigs.get(componentId);
|
||||
if (!config || !config.progress) {
|
||||
return;
|
||||
}
|
||||
|
||||
const progressBar = config.progress.querySelector('.progress-bar');
|
||||
if (progressBar) {
|
||||
progressBar.style.width = `${Math.min(100, Math.max(0, percent))}%`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide progress bar
|
||||
*
|
||||
* @param {string} componentId - Component ID
|
||||
*/
|
||||
hideProgress(componentId) {
|
||||
const config = this.loadingConfigs.get(componentId);
|
||||
if (!config || !config.progress) {
|
||||
return;
|
||||
}
|
||||
|
||||
const progress = config.progress;
|
||||
|
||||
// Complete progress bar
|
||||
this.updateProgress(componentId, 100);
|
||||
|
||||
setTimeout(() => {
|
||||
progress.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
if (progress.parentNode) {
|
||||
progress.parentNode.removeChild(progress);
|
||||
}
|
||||
delete config.progress;
|
||||
}, 200);
|
||||
}, 300);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get loading configuration for component
|
||||
*
|
||||
* @param {string} componentId - Component ID
|
||||
* @returns {Object} Loading configuration
|
||||
*/
|
||||
getConfig(componentId) {
|
||||
return this.loadingConfigs.get(componentId) || {
|
||||
type: this.config.defaultType,
|
||||
showDelay: this.config.showDelay
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear configuration for component
|
||||
*
|
||||
* @param {string} componentId - Component ID
|
||||
*/
|
||||
clearConfig(componentId) {
|
||||
this.hideLoading(componentId);
|
||||
this.loadingConfigs.delete(componentId);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user