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

- 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:
2025-11-09 14:46:15 +01:00
parent 85c369e846
commit 36ef2a1e2c
1366 changed files with 104925 additions and 28719 deletions

View File

@@ -0,0 +1,256 @@
/**
* Action Loading Manager for LiveComponents
*
* Provides skeleton loading states during component actions with:
* - Automatic skeleton overlay during actions
* - Configurable skeleton templates per component
* - Smooth transitions
* - Fragment-aware loading
*/
export class ActionLoadingManager {
constructor() {
this.loadingStates = new Map(); // componentId → loading state
this.skeletonTemplates = new Map(); // componentId → template
this.config = {
showDelay: 150, // ms before showing skeleton
transitionDuration: 200, // ms for transitions
preserveContent: true, // Keep content visible under skeleton
opacity: 0.6 // Skeleton overlay opacity
};
}
/**
* Show loading state for component
*
* @param {string} componentId - Component ID
* @param {HTMLElement} element - Component element
* @param {Object} options - Loading options
*/
showLoading(componentId, element, options = {}) {
// Check if already loading
if (this.loadingStates.has(componentId)) {
return; // Already showing loading state
}
const showDelay = options.showDelay ?? this.config.showDelay;
const fragments = options.fragments || null;
// Delay showing skeleton (for fast responses)
const timeoutId = setTimeout(() => {
this.createSkeletonOverlay(componentId, element, fragments, options);
}, showDelay);
// Store loading state
this.loadingStates.set(componentId, {
timeoutId,
element,
fragments,
options
});
}
/**
* Create skeleton overlay
*
* @param {string} componentId - Component ID
* @param {HTMLElement} element - Component element
* @param {Array<string>|null} fragments - Fragment names to show skeleton for
* @param {Object} options - Options
*/
createSkeletonOverlay(componentId, element, fragments, options) {
// Get skeleton template
const template = this.getSkeletonTemplate(componentId, element, fragments);
// Create overlay container
const overlay = document.createElement('div');
overlay.className = 'livecomponent-loading-overlay';
overlay.setAttribute('data-component-id', componentId);
overlay.setAttribute('aria-busy', 'true');
overlay.setAttribute('aria-label', 'Loading...');
overlay.style.cssText = `
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, ${this.config.opacity});
z-index: 10;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity ${this.config.transitionDuration}ms ease;
pointer-events: none;
`;
// Add skeleton content
overlay.innerHTML = template;
// Ensure element has relative positioning
const originalPosition = element.style.position;
if (getComputedStyle(element).position === 'static') {
element.style.position = 'relative';
}
// Append overlay
element.appendChild(overlay);
// Animate in
requestAnimationFrame(() => {
overlay.style.opacity = '1';
});
// Update loading state
const state = this.loadingStates.get(componentId);
if (state) {
state.overlay = overlay;
state.originalPosition = originalPosition;
}
}
/**
* Get skeleton template for component
*
* @param {string} componentId - Component ID
* @param {HTMLElement} element - Component element
* @param {Array<string>|null} fragments - Fragment names
* @returns {string} Skeleton HTML
*/
getSkeletonTemplate(componentId, element, fragments) {
// Check for custom template
const customTemplate = this.skeletonTemplates.get(componentId);
if (customTemplate) {
return typeof customTemplate === 'function'
? customTemplate(element, fragments)
: customTemplate;
}
// If fragments specified, create fragment-specific skeletons
if (fragments && fragments.length > 0) {
return this.createFragmentSkeletons(fragments);
}
// Default skeleton template
return this.createDefaultSkeleton(element);
}
/**
* Create default skeleton template
*
* @param {HTMLElement} element - Component element
* @returns {string} Skeleton HTML
*/
createDefaultSkeleton(element) {
const height = element.offsetHeight || 200;
const width = element.offsetWidth || '100%';
return `
<div class="skeleton-container" style="width: ${width}; height: ${height}px; padding: 1.5rem;">
<div class="skeleton skeleton-text skeleton-text--full" style="margin-bottom: 1rem;"></div>
<div class="skeleton skeleton-text skeleton-text--80" style="margin-bottom: 0.75rem;"></div>
<div class="skeleton skeleton-text skeleton-text--60" style="margin-bottom: 0.75rem;"></div>
<div class="skeleton skeleton-text skeleton-text--full" style="margin-bottom: 1rem;"></div>
<div class="skeleton skeleton-text skeleton-text--80"></div>
</div>
`;
}
/**
* Create fragment-specific skeletons
*
* @param {Array<string>} fragments - Fragment names
* @returns {string} Skeleton HTML
*/
createFragmentSkeletons(fragments) {
return fragments.map(fragmentName => `
<div class="skeleton-fragment" data-fragment="${fragmentName}">
<div class="skeleton skeleton-text skeleton-text--full" style="margin-bottom: 0.75rem;"></div>
<div class="skeleton skeleton-text skeleton-text--80"></div>
</div>
`).join('');
}
/**
* Hide loading state
*
* @param {string} componentId - Component ID
*/
hideLoading(componentId) {
const state = this.loadingStates.get(componentId);
if (!state) {
return;
}
// Clear timeout if not yet shown
if (state.timeoutId) {
clearTimeout(state.timeoutId);
}
// Remove overlay if exists
if (state.overlay) {
// Animate out
state.overlay.style.opacity = '0';
setTimeout(() => {
if (state.overlay && state.overlay.parentNode) {
state.overlay.parentNode.removeChild(state.overlay);
}
}, this.config.transitionDuration);
}
// Restore original position
if (state.originalPosition !== undefined) {
state.element.style.position = state.originalPosition;
}
// Remove from map
this.loadingStates.delete(componentId);
}
/**
* Register custom skeleton template for component
*
* @param {string} componentId - Component ID
* @param {string|Function} template - Template HTML or function that returns HTML
*/
registerTemplate(componentId, template) {
this.skeletonTemplates.set(componentId, template);
}
/**
* Unregister skeleton template
*
* @param {string} componentId - Component ID
*/
unregisterTemplate(componentId) {
this.skeletonTemplates.delete(componentId);
}
/**
* Check if component is loading
*
* @param {string} componentId - Component ID
* @returns {boolean} True if loading
*/
isLoading(componentId) {
return this.loadingStates.has(componentId);
}
/**
* Update configuration
*
* @param {Object} newConfig - New configuration
*/
updateConfig(newConfig) {
this.config = {
...this.config,
...newConfig
};
}
}
// Create singleton instance
export const actionLoadingManager = new ActionLoadingManager();