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

@@ -13,6 +13,27 @@ export class FormHandler {
preventSubmitOnError: true,
submitMethod: 'POST',
ajaxSubmit: true,
// Autosave options
enableAutosave: options.enableAutosave ?? form.hasAttribute('data-autosave'),
autosaveInterval: options.autosaveInterval || 30000, // 30 seconds
autosaveRetentionPeriod: options.autosaveRetentionPeriod || 24 * 60 * 60 * 1000, // 24 hours
autosaveStorageKey: options.autosaveStorageKey || null,
autosaveStoragePrefix: options.autosaveStoragePrefix || 'form_draft_',
autosaveVisualFeedback: options.autosaveVisualFeedback ?? true,
autosaveExcludeFields: options.autosaveExcludeFields || [
'input[type="password"]',
'input[type="hidden"][name="_token"]',
'input[type="hidden"][name="_form_id"]',
'input[name*="password"]',
'input[name*="confirm"]',
'input[name*="honeypot"]'
],
autosaveImmediateFields: options.autosaveImmediateFields || [
'textarea',
'input[type="email"]',
'input[type="text"]',
'select'
],
...options
};
@@ -20,6 +41,11 @@ export class FormHandler {
this.state = FormState.create(form);
this.isSubmitting = false;
// Autosave state
this.autosaveTimer = null;
this.lastAutosaveTime = null;
this.autosaveStorageKey = null;
this.init();
}
@@ -31,6 +57,11 @@ export class FormHandler {
this.bindEvents();
this.setupErrorDisplay();
// Initialize autosave if enabled
if (this.options.enableAutosave) {
this.initAutosave();
}
// Mark form as enhanced
this.form.setAttribute('data-enhanced', 'true');
@@ -55,6 +86,26 @@ export class FormHandler {
}
});
}
// Autosave events
if (this.options.enableAutosave) {
this.form.addEventListener('input', (e) => this.onAutosaveFieldChange(e));
this.form.addEventListener('change', (e) => this.onAutosaveFieldChange(e));
// Save on page unload
window.addEventListener('beforeunload', () => {
if (this.state.isDirty()) {
this.saveAutosaveDraft();
}
});
// Handle page visibility changes
document.addEventListener('visibilitychange', () => {
if (document.hidden && this.state.isDirty()) {
this.saveAutosaveDraft();
}
});
}
}
async handleSubmit(event) {
@@ -139,6 +190,11 @@ export class FormHandler {
handleSuccess(data) {
Logger.info('[FormHandler] Form submitted successfully');
// Clear autosave draft on successful submission
if (this.options.enableAutosave) {
this.clearAutosaveDraft();
}
// Clear form if configured
if (data.clearForm !== false) {
this.form.reset();
@@ -345,7 +401,351 @@ export class FormHandler {
this.form.dispatchEvent(event);
}
/**
* Initialize autosave functionality
*/
initAutosave() {
// Generate storage key
this.autosaveStorageKey = this.options.autosaveStorageKey ||
this.options.autosaveStoragePrefix + this.generateFormId();
// Restore draft on init
this.restoreAutosaveDraft();
// Start periodic autosave
this.startAutosave();
Logger.info('[FormHandler] Autosave initialized', { storageKey: this.autosaveStorageKey });
}
/**
* Generate unique form identifier
*/
generateFormId() {
const formId = this.form.id ||
this.form.getAttribute('data-form-id') ||
this.form.querySelector('input[name="_form_id"]')?.value ||
'form_' + Date.now();
return formId.replace(/[^a-zA-Z0-9_-]/g, '_');
}
/**
* Handle field change for autosave
*/
onAutosaveFieldChange(event) {
const field = event.target;
// Skip excluded fields
if (this.isAutosaveFieldExcluded(field)) {
return;
}
// Save immediately for important fields
if (this.isAutosaveImmediateField(field)) {
this.saveAutosaveDraft();
}
}
/**
* Check if field should be excluded from autosave
*/
isAutosaveFieldExcluded(field) {
return this.options.autosaveExcludeFields.some(selector =>
field.matches(selector)
);
}
/**
* Check if field should trigger immediate save
*/
isAutosaveImmediateField(field) {
return this.options.autosaveImmediateFields.some(selector =>
field.matches(selector)
);
}
/**
* Start periodic autosave
*/
startAutosave() {
if (this.autosaveTimer) {
return;
}
this.autosaveTimer = setInterval(() => {
if (this.state.isDirty()) {
this.saveAutosaveDraft();
}
}, this.options.autosaveInterval);
Logger.debug('[FormHandler] Autosave started', { interval: this.options.autosaveInterval });
}
/**
* Stop periodic autosave
*/
stopAutosave() {
if (this.autosaveTimer) {
clearInterval(this.autosaveTimer);
this.autosaveTimer = null;
}
}
/**
* Save form data as draft
*/
saveAutosaveDraft() {
if (!this.form || !this.state.isDirty()) {
return;
}
try {
const formData = this.extractAutosaveFormData();
const draft = {
data: formData,
timestamp: Date.now(),
formId: this.generateFormId(),
url: window.location.href,
version: '1.0'
};
localStorage.setItem(this.autosaveStorageKey, JSON.stringify(draft));
this.lastAutosaveTime = new Date();
Logger.debug('[FormHandler] Draft saved', {
fields: Object.keys(formData).length,
storageKey: this.autosaveStorageKey
});
if (this.options.autosaveVisualFeedback) {
this.showAutosaveStatus('Draft saved', 'success', 1500);
}
// Trigger event
this.triggerEvent('form:autosave', { draft });
} catch (error) {
Logger.error('[FormHandler] Failed to save draft', error);
if (this.options.autosaveVisualFeedback) {
this.showAutosaveStatus('Failed to save draft', 'error', 3000);
}
}
}
/**
* Restore draft data to form
*/
restoreAutosaveDraft() {
try {
const draftJson = localStorage.getItem(this.autosaveStorageKey);
if (!draftJson) {
return;
}
const draft = JSON.parse(draftJson);
// Check if draft is expired
const age = Date.now() - draft.timestamp;
if (age > this.options.autosaveRetentionPeriod) {
localStorage.removeItem(this.autosaveStorageKey);
return;
}
// Restore form data
this.restoreAutosaveFormData(draft.data);
Logger.info('[FormHandler] Draft restored', {
age: Math.floor(age / 1000) + 's',
fields: Object.keys(draft.data).length
});
if (this.options.autosaveVisualFeedback) {
const ageText = this.formatDuration(age);
this.showAutosaveStatus(`Draft restored from ${ageText} ago`, 'info', 4000);
}
// Trigger event
this.triggerEvent('form:autosave-restored', { draft });
} catch (error) {
Logger.error('[FormHandler] Failed to restore draft', error);
localStorage.removeItem(this.autosaveStorageKey);
}
}
/**
* Extract form data for autosave (excluding sensitive fields)
*/
extractAutosaveFormData() {
const fields = this.getFormFields();
const data = {};
fields.forEach(field => {
if (!this.isAutosaveFieldExcluded(field)) {
const key = field.name || field.id;
const value = this.getAutosaveFieldValue(field);
if (key && value !== null && value !== undefined && value !== '') {
data[key] = value;
}
}
});
return data;
}
/**
* Restore data to form fields
*/
restoreAutosaveFormData(data) {
let restoredCount = 0;
Object.entries(data).forEach(([key, value]) => {
const field = this.form.querySelector(`[name="${key}"], #${key}`);
if (field && !this.isAutosaveFieldExcluded(field)) {
this.setAutosaveFieldValue(field, value);
restoredCount++;
}
});
// Update form state
this.state.captureInitialValues();
return restoredCount;
}
/**
* Get form fields
*/
getFormFields() {
return Array.from(this.form.querySelectorAll(
'input:not([type="submit"]):not([type="button"]):not([type="reset"]), ' +
'textarea, select'
));
}
/**
* Get field value for autosave
*/
getAutosaveFieldValue(field) {
switch (field.type) {
case 'checkbox':
return field.checked;
case 'radio':
return field.checked ? field.value : null;
case 'file':
return null; // Don't save file inputs
default:
return field.value;
}
}
/**
* Set field value for autosave
*/
setAutosaveFieldValue(field, value) {
switch (field.type) {
case 'checkbox':
field.checked = Boolean(value);
break;
case 'radio':
field.checked = (field.value === value);
break;
case 'file':
// Can't restore file inputs
break;
default:
field.value = value;
break;
}
}
/**
* Clear autosave draft
*/
clearAutosaveDraft() {
if (this.autosaveStorageKey) {
localStorage.removeItem(this.autosaveStorageKey);
}
Logger.debug('[FormHandler] Draft cleared');
}
/**
* Format duration for human reading
*/
formatDuration(ms) {
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
if (hours > 0) {
return `${hours}h ${minutes % 60}m`;
} else if (minutes > 0) {
return `${minutes}m`;
} else {
return `${seconds}s`;
}
}
/**
* Show autosave status message
*/
showAutosaveStatus(message, type = 'info', duration = 3000) {
// Create or update status element
let statusEl = document.getElementById('form-autosave-status');
if (!statusEl) {
statusEl = document.createElement('div');
statusEl.id = 'form-autosave-status';
statusEl.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
padding: 8px 12px;
border-radius: 4px;
font-size: 12px;
z-index: 9999;
max-width: 250px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
transition: opacity 0.3s ease;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
`;
document.body.appendChild(statusEl);
}
statusEl.textContent = message;
const styles = {
info: 'background: #e3f2fd; color: #1565c0; border: 1px solid #bbdefb;',
success: 'background: #e8f5e8; color: #2e7d32; border: 1px solid #c8e6c9;',
error: 'background: #ffebee; color: #c62828; border: 1px solid #ffcdd2;'
};
statusEl.style.cssText += styles[type] || styles.info;
statusEl.style.opacity = '1';
setTimeout(() => {
if (statusEl) {
statusEl.style.opacity = '0';
setTimeout(() => {
if (statusEl && statusEl.parentNode) {
statusEl.parentNode.removeChild(statusEl);
}
}, 300);
}
}, duration);
}
destroy() {
// Stop autosave
if (this.options.enableAutosave) {
this.stopAutosave();
}
// Remove event listeners and clean up
this.form.removeAttribute('data-enhanced');
Logger.info('[FormHandler] Destroyed');