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

@@ -6,13 +6,31 @@ import { Logger } from '../../core/logger.js';
*/
export class StorageManager {
constructor(config = {}) {
this.config = config;
this.dbName = config.dbName || 'AppDatabase';
this.dbVersion = config.dbVersion || 1;
this.config = {
dbName: config.dbName || 'AppDatabase',
dbVersion: config.dbVersion || 1,
enableAnalytics: config.enableAnalytics ?? true,
enableQuotaMonitoring: config.enableQuotaMonitoring ?? true,
quotaWarningThreshold: config.quotaWarningThreshold || 0.8, // 80%
...config
};
this.db = null;
this.cache = null;
this.channels = new Map();
// Analytics
this.analytics = {
operations: {
get: 0,
set: 0,
delete: 0,
clear: 0
},
errors: 0,
quotaWarnings: 0
};
// Check API support
this.support = {
indexedDB: 'indexedDB' in window,
@@ -20,7 +38,8 @@ export class StorageManager {
webLocks: 'locks' in navigator,
broadcastChannel: 'BroadcastChannel' in window,
localStorage: 'localStorage' in window,
sessionStorage: 'sessionStorage' in window
sessionStorage: 'sessionStorage' in window,
storageEstimate: 'storage' in navigator && 'estimate' in navigator.storage
};
Logger.info('[StorageManager] Initialized with support:', this.support);
@@ -28,6 +47,11 @@ export class StorageManager {
// Initialize databases
this.initializeDB();
this.initializeCache();
// Start quota monitoring if enabled
if (this.config.enableQuotaMonitoring) {
this.startQuotaMonitoring();
}
}
/**
@@ -755,7 +779,365 @@ export class StorageManager {
activeChannels: this.channels.size,
dbConnected: !!this.db,
cacheConnected: !!this.cache,
channelNames: Array.from(this.channels.keys())
channelNames: Array.from(this.channels.keys()),
analytics: this.config.enableAnalytics ? this.getAnalytics() : null
};
}
/**
* Unified storage API - works with any storage type
*/
storage = {
/**
* Set value in storage
*/
set: async (key, value, options = {}) => {
const {
type = 'auto', // 'auto' | 'localStorage' | 'sessionStorage' | 'indexedDB'
expiration = null,
...rest
} = options;
try {
// Auto-select storage type based on size
let storageType = type;
if (type === 'auto') {
const valueSize = JSON.stringify(value).length;
storageType = valueSize > 5 * 1024 * 1024 ? 'indexedDB' : 'localStorage'; // >5MB use IndexedDB
}
switch (storageType) {
case 'localStorage':
return this.local.set(key, value, expiration);
case 'sessionStorage':
return this.session.set(key, value);
case 'indexedDB':
return await this.db.set(key, value, expiration);
default:
throw new Error(`Unknown storage type: ${storageType}`);
}
} catch (error) {
// Fallback to localStorage if IndexedDB fails
if (type === 'indexedDB' || type === 'auto') {
Logger.warn('[StorageManager] Falling back to localStorage', error);
return this.local.set(key, value, expiration);
}
throw error;
} finally {
if (this.config.enableAnalytics) {
this.analytics.operations.set++;
}
}
},
/**
* Get value from storage
*/
get: async (key, options = {}) => {
const { type = 'auto', ...rest } = options;
try {
// Try different storage types in order
if (type === 'auto') {
// Try localStorage first (fastest)
const localValue = this.local.get(key);
if (localValue !== null) {
if (this.config.enableAnalytics) {
this.analytics.operations.get++;
}
return localValue;
}
// Try sessionStorage
const sessionValue = this.session.get(key);
if (sessionValue !== null) {
if (this.config.enableAnalytics) {
this.analytics.operations.get++;
}
return sessionValue;
}
// Try IndexedDB
if (this.db) {
const dbValue = await this.db.get(key);
if (dbValue !== null) {
if (this.config.enableAnalytics) {
this.analytics.operations.get++;
}
return dbValue;
}
}
return null;
} else {
switch (type) {
case 'localStorage':
const localValue = this.local.get(key);
if (this.config.enableAnalytics) {
this.analytics.operations.get++;
}
return localValue;
case 'sessionStorage':
const sessionValue = this.session.get(key);
if (this.config.enableAnalytics) {
this.analytics.operations.get++;
}
return sessionValue;
case 'indexedDB':
const dbValue = await this.db.get(key);
if (this.config.enableAnalytics) {
this.analytics.operations.get++;
}
return dbValue;
default:
throw new Error(`Unknown storage type: ${type}`);
}
}
} catch (error) {
Logger.error('[StorageManager] Storage get failed', error);
if (this.config.enableAnalytics) {
this.analytics.errors++;
}
throw error;
}
},
/**
* Delete value from storage
*/
delete: async (key, options = {}) => {
const { type = 'all', ...rest } = options;
try {
if (type === 'all') {
// Delete from all storage types
this.local.delete(key);
this.session.delete(key);
if (this.db) {
await this.db.delete(key);
}
} else {
switch (type) {
case 'localStorage':
this.local.delete(key);
break;
case 'sessionStorage':
this.session.delete(key);
break;
case 'indexedDB':
if (this.db) {
await this.db.delete(key);
}
break;
}
}
if (this.config.enableAnalytics) {
this.analytics.operations.delete++;
}
} catch (error) {
Logger.error('[StorageManager] Storage delete failed', error);
if (this.config.enableAnalytics) {
this.analytics.errors++;
}
throw error;
}
},
/**
* Clear all storage
*/
clear: async (options = {}) => {
const { type = 'all', ...rest } = options;
try {
if (type === 'all') {
this.local.clear();
this.session.clear();
if (this.db) {
await this.db.clear();
}
if (this.cache) {
await this.cache.clear();
}
} else {
switch (type) {
case 'localStorage':
this.local.clear();
break;
case 'sessionStorage':
this.session.clear();
break;
case 'indexedDB':
if (this.db) {
await this.db.clear();
}
break;
case 'cache':
if (this.cache) {
await this.cache.clear();
}
break;
}
}
if (this.config.enableAnalytics) {
this.analytics.operations.clear++;
}
} catch (error) {
Logger.error('[StorageManager] Storage clear failed', error);
if (this.config.enableAnalytics) {
this.analytics.errors++;
}
throw error;
}
}
};
/**
* Start quota monitoring
*/
startQuotaMonitoring() {
// Check quota periodically
setInterval(async () => {
try {
const usage = await this.getStorageUsage();
if (usage.percentage >= this.config.quotaWarningThreshold * 100) {
this.analytics.quotaWarnings++;
Logger.warn('[StorageManager] Storage quota warning', {
usage: usage.percentage + '%',
available: this.formatBytes(usage.available)
});
// Trigger event
const event = new CustomEvent('storage:quota-warning', {
detail: usage,
bubbles: true
});
window.dispatchEvent(event);
}
} catch (error) {
Logger.error('[StorageManager] Quota monitoring error', error);
}
}, 60000); // Check every minute
}
/**
* Migrate data between storage types
*/
async migrate(fromType, toType, keys = null) {
Logger.info('[StorageManager] Starting migration', { fromType, toType });
let sourceKeys = keys;
// Get keys from source
if (!sourceKeys) {
switch (fromType) {
case 'localStorage':
sourceKeys = this.local.keys();
break;
case 'sessionStorage':
sourceKeys = Object.keys(sessionStorage);
break;
case 'indexedDB':
if (this.db) {
sourceKeys = await this.db.keys();
}
break;
}
}
if (!sourceKeys || sourceKeys.length === 0) {
Logger.info('[StorageManager] No keys to migrate');
return;
}
let migrated = 0;
let failed = 0;
for (const key of sourceKeys) {
try {
// Get value from source
let value = null;
switch (fromType) {
case 'localStorage':
value = this.local.get(key);
break;
case 'sessionStorage':
value = this.session.get(key);
break;
case 'indexedDB':
if (this.db) {
value = await this.db.get(key);
}
break;
}
if (value === null) {
continue;
}
// Set value in destination
switch (toType) {
case 'localStorage':
this.local.set(key, value);
break;
case 'sessionStorage':
this.session.set(key, value);
break;
case 'indexedDB':
if (this.db) {
await this.db.set(key, value);
}
break;
}
migrated++;
} catch (error) {
Logger.error(`[StorageManager] Failed to migrate key: ${key}`, error);
failed++;
}
}
Logger.info('[StorageManager] Migration completed', { migrated, failed });
return { migrated, failed };
}
/**
* Get storage analytics
*/
getAnalytics() {
return {
...this.analytics,
totalOperations: Object.values(this.analytics.operations).reduce((sum, count) => sum + count, 0),
errorRate: this.analytics.errors / (this.analytics.operations.get + this.analytics.operations.set || 1)
};
}
/**
* Reset analytics
*/
resetAnalytics() {
this.analytics = {
operations: {
get: 0,
set: 0,
delete: 0,
clear: 0
},
errors: 0,
quotaWarnings: 0
};
}
/**
* Format bytes for human reading
*/
formatBytes(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
}
}