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:
@@ -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];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user