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,347 @@
/**
* Error Boundary for LiveComponents
*
* Provides automatic error handling, retry mechanisms, and error recovery
* for LiveComponent operations.
*/
// Note: LiveComponentError type is defined in types/livecomponent.d.ts
// This is a runtime implementation, so we don't need to import the type
export class ErrorBoundary {
constructor(liveComponentManager) {
this.manager = liveComponentManager;
this.retryStrategies = new Map();
this.errorHandlers = new Map();
this.maxRetries = 3;
this.retryDelays = [1000, 2000, 5000]; // Progressive backoff
}
/**
* Handle error from component action
*
* @param {string} componentId - Component ID
* @param {string} action - Action method name
* @param {Error|LiveComponentError} error - Error object
* @param {Object} context - Additional context
* @returns {Promise<boolean>} True if error was handled, false otherwise
*/
async handleError(componentId, action, error, context = {}) {
console.error(`[ErrorBoundary] Error in ${componentId}.${action}:`, error);
// Convert to standardized error format
const standardizedError = this.standardizeError(error, componentId, action);
// Check for custom error handler
const handler = this.errorHandlers.get(componentId);
if (handler) {
try {
const handled = await handler(standardizedError, context);
if (handled) {
return true;
}
} catch (handlerError) {
console.error('[ErrorBoundary] Error handler failed:', handlerError);
}
}
// Check if error is retryable
if (this.isRetryable(standardizedError)) {
const retried = await this.retryOperation(componentId, action, context, standardizedError);
if (retried) {
return true;
}
}
// Show error to user
this.showError(componentId, standardizedError);
// Dispatch error event
this.dispatchErrorEvent(componentId, standardizedError);
return false;
}
/**
* Standardize error format
*
* @param {Error|LiveComponentError|Object} error - Error object
* @param {string} componentId - Component ID
* @param {string} action - Action method name
* @returns {LiveComponentError} Standardized error
*/
standardizeError(error, componentId, action) {
// If already standardized
if (error && typeof error === 'object' && 'code' in error && 'message' in error) {
return {
code: error.code,
message: error.message,
details: error.details || {},
componentId: error.componentId || componentId,
action: error.action || action,
timestamp: error.timestamp || Date.now()
};
}
// If Error object
if (error instanceof Error) {
return {
code: 'INTERNAL_ERROR',
message: error.message,
details: {
stack: error.stack,
name: error.name
},
componentId,
action,
timestamp: Date.now()
};
}
// If string
if (typeof error === 'string') {
return {
code: 'INTERNAL_ERROR',
message: error,
componentId,
action,
timestamp: Date.now()
};
}
// Default
return {
code: 'INTERNAL_ERROR',
message: 'An unknown error occurred',
details: { original: error },
componentId,
action,
timestamp: Date.now()
};
}
/**
* Check if error is retryable
*
* @param {LiveComponentError} error - Standardized error
* @returns {boolean} True if error is retryable
*/
isRetryable(error) {
const retryableCodes = [
'RATE_LIMIT_EXCEEDED',
'STATE_CONFLICT',
'INTERNAL_ERROR'
];
return retryableCodes.includes(error.code);
}
/**
* Retry operation with exponential backoff
*
* @param {string} componentId - Component ID
* @param {string} action - Action method name
* @param {Object} context - Operation context
* @param {LiveComponentError} error - Error that occurred
* @returns {Promise<boolean>} True if retry succeeded
*/
async retryOperation(componentId, action, context, error) {
const retryKey = `${componentId}:${action}`;
const retryCount = this.retryStrategies.get(retryKey) || 0;
if (retryCount >= this.maxRetries) {
console.warn(`[ErrorBoundary] Max retries exceeded for ${retryKey}`);
this.retryStrategies.delete(retryKey);
return false;
}
// Calculate delay (progressive backoff)
const delay = this.retryDelays[retryCount] || this.retryDelays[this.retryDelays.length - 1];
console.log(`[ErrorBoundary] Retrying ${retryKey} in ${delay}ms (attempt ${retryCount + 1}/${this.maxRetries})`);
// Update retry count
this.retryStrategies.set(retryKey, retryCount + 1);
// Wait before retry
await new Promise(resolve => setTimeout(resolve, delay));
try {
// Retry the operation
const result = await this.manager.executeAction(
componentId,
action,
context.params || {},
context.fragments || null
);
// Success - clear retry count
this.retryStrategies.delete(retryKey);
console.log(`[ErrorBoundary] Retry succeeded for ${retryKey}`);
return true;
} catch (retryError) {
// Retry failed - will be handled by next retry or error handler
console.warn(`[ErrorBoundary] Retry failed for ${retryKey}:`, retryError);
return false;
}
}
/**
* Show error to user
*
* @param {string} componentId - Component ID
* @param {LiveComponentError} error - Standardized error
*/
showError(componentId, error) {
const config = this.manager.components.get(componentId);
if (!config) return;
// Remove existing error
const existingError = config.element.querySelector('.livecomponent-error-boundary');
if (existingError) {
existingError.remove();
}
// Create error element
const errorEl = document.createElement('div');
errorEl.className = 'livecomponent-error-boundary';
errorEl.style.cssText = `
padding: 1rem;
background: #fee;
color: #c00;
border: 1px solid #faa;
border-radius: 4px;
margin-bottom: 1rem;
`;
// Error message
const messageEl = document.createElement('div');
messageEl.style.fontWeight = 'bold';
messageEl.textContent = error.message;
errorEl.appendChild(messageEl);
// Error code (if not generic)
if (error.code !== 'INTERNAL_ERROR') {
const codeEl = document.createElement('div');
codeEl.style.fontSize = '0.875rem';
codeEl.style.marginTop = '0.5rem';
codeEl.style.color = '#666';
codeEl.textContent = `Error Code: ${error.code}`;
errorEl.appendChild(codeEl);
}
// Retry button (if retryable)
if (this.isRetryable(error)) {
const retryBtn = document.createElement('button');
retryBtn.textContent = 'Retry';
retryBtn.style.cssText = `
margin-top: 0.5rem;
padding: 0.5rem 1rem;
background: #c00;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
`;
retryBtn.addEventListener('click', async () => {
errorEl.remove();
await this.retryOperation(componentId, error.action || '', {}, error);
});
errorEl.appendChild(retryBtn);
}
// Close button
const closeBtn = document.createElement('button');
closeBtn.textContent = '×';
closeBtn.style.cssText = `
float: right;
background: transparent;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: #c00;
`;
closeBtn.addEventListener('click', () => {
errorEl.remove();
});
errorEl.appendChild(closeBtn);
// Insert at top of component
config.element.insertAdjacentElement('afterbegin', errorEl);
// Auto-remove after 10 seconds
setTimeout(() => {
if (errorEl.parentNode) {
errorEl.remove();
}
}, 10000);
}
/**
* Dispatch error event
*
* @param {string} componentId - Component ID
* @param {LiveComponentError} error - Standardized error
*/
dispatchErrorEvent(componentId, error) {
// Dispatch custom DOM event
const event = new CustomEvent('livecomponent:error', {
detail: {
componentId,
error
},
bubbles: true
});
const config = this.manager.components.get(componentId);
if (config) {
config.element.dispatchEvent(event);
}
// Also dispatch on document
document.dispatchEvent(event);
}
/**
* Register custom error handler for component
*
* @param {string} componentId - Component ID
* @param {Function} handler - Error handler function
*/
registerErrorHandler(componentId, handler) {
if (typeof handler !== 'function') {
throw new Error('Error handler must be a function');
}
this.errorHandlers.set(componentId, handler);
}
/**
* Clear error handler for component
*
* @param {string} componentId - Component ID
*/
clearErrorHandler(componentId) {
this.errorHandlers.delete(componentId);
}
/**
* Clear retry strategy for component/action
*
* @param {string} componentId - Component ID
* @param {string} action - Action method name
*/
clearRetryStrategy(componentId, action) {
const retryKey = `${componentId}:${action}`;
this.retryStrategies.delete(retryKey);
}
/**
* Reset all retry strategies
*/
resetRetryStrategies() {
this.retryStrategies.clear();
}
}