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:
406
resources/js/modules/analytics/Analytics.js
Normal file
406
resources/js/modules/analytics/Analytics.js
Normal file
@@ -0,0 +1,406 @@
|
||||
/**
|
||||
* Analytics Module
|
||||
*
|
||||
* Provides unified analytics system for event tracking, page views, and user behavior.
|
||||
* Features:
|
||||
* - Event tracking
|
||||
* - Page view tracking
|
||||
* - User behavior tracking
|
||||
* - Custom events
|
||||
* - Integration with LiveComponents
|
||||
* - GDPR compliance
|
||||
*/
|
||||
|
||||
import { Logger } from '../../core/logger.js';
|
||||
|
||||
/**
|
||||
* AnalyticsProvider - Base class for analytics providers
|
||||
*/
|
||||
export class AnalyticsProvider {
|
||||
constructor(config = {}) {
|
||||
this.config = config;
|
||||
this.enabled = config.enabled ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Track event
|
||||
*/
|
||||
async track(eventName, properties = {}) {
|
||||
if (!this.enabled) return;
|
||||
// Override in subclasses
|
||||
}
|
||||
|
||||
/**
|
||||
* Track page view
|
||||
*/
|
||||
async pageView(path, properties = {}) {
|
||||
if (!this.enabled) return;
|
||||
// Override in subclasses
|
||||
}
|
||||
|
||||
/**
|
||||
* Identify user
|
||||
*/
|
||||
async identify(userId, traits = {}) {
|
||||
if (!this.enabled) return;
|
||||
// Override in subclasses
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GoogleAnalyticsProvider - Google Analytics integration
|
||||
*/
|
||||
export class GoogleAnalyticsProvider extends AnalyticsProvider {
|
||||
constructor(config = {}) {
|
||||
super(config);
|
||||
this.measurementId = config.measurementId || config.gaId;
|
||||
}
|
||||
|
||||
async track(eventName, properties = {}) {
|
||||
if (typeof gtag !== 'undefined') {
|
||||
gtag('event', eventName, properties);
|
||||
}
|
||||
}
|
||||
|
||||
async pageView(path, properties = {}) {
|
||||
if (typeof gtag !== 'undefined') {
|
||||
gtag('config', this.measurementId, {
|
||||
page_path: path,
|
||||
...properties
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CustomProvider - Custom analytics endpoint
|
||||
*/
|
||||
export class CustomProvider extends AnalyticsProvider {
|
||||
constructor(config = {}) {
|
||||
super(config);
|
||||
this.endpoint = config.endpoint || '/api/analytics';
|
||||
}
|
||||
|
||||
async track(eventName, properties = {}) {
|
||||
try {
|
||||
await fetch(this.endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: 'event',
|
||||
name: eventName,
|
||||
properties,
|
||||
timestamp: Date.now()
|
||||
})
|
||||
});
|
||||
} catch (error) {
|
||||
Logger.error('[Analytics] Failed to track event', error);
|
||||
}
|
||||
}
|
||||
|
||||
async pageView(path, properties = {}) {
|
||||
try {
|
||||
await fetch(this.endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: 'page_view',
|
||||
path,
|
||||
properties,
|
||||
timestamp: Date.now()
|
||||
})
|
||||
});
|
||||
} catch (error) {
|
||||
Logger.error('[Analytics] Failed to track page view', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Analytics - Unified analytics system
|
||||
*/
|
||||
export class Analytics {
|
||||
constructor(config = {}) {
|
||||
this.config = {
|
||||
enabled: config.enabled ?? true,
|
||||
providers: config.providers || [],
|
||||
gdprCompliant: config.gdprCompliant ?? true,
|
||||
requireConsent: config.requireConsent ?? false,
|
||||
consentGiven: config.consentGiven ?? false,
|
||||
anonymizeIp: config.anonymizeIp ?? true,
|
||||
...config
|
||||
};
|
||||
|
||||
this.providers = [];
|
||||
this.eventQueue = [];
|
||||
this.userId = null;
|
||||
this.userTraits = {};
|
||||
|
||||
// Initialize providers
|
||||
this.initProviders();
|
||||
|
||||
// Track initial page view
|
||||
if (this.config.enabled && this.hasConsent()) {
|
||||
this.trackPageView();
|
||||
}
|
||||
|
||||
Logger.info('[Analytics] Initialized', {
|
||||
enabled: this.config.enabled,
|
||||
providers: this.providers.length,
|
||||
gdprCompliant: this.config.gdprCompliant
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Analytics instance
|
||||
*/
|
||||
static create(config = {}) {
|
||||
return new Analytics(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize providers
|
||||
*/
|
||||
initProviders() {
|
||||
for (const providerConfig of this.config.providers) {
|
||||
let provider;
|
||||
|
||||
if (typeof providerConfig === 'string') {
|
||||
// Provider name
|
||||
if (providerConfig === 'google-analytics' || providerConfig === 'ga') {
|
||||
provider = new GoogleAnalyticsProvider({});
|
||||
} else if (providerConfig === 'custom') {
|
||||
provider = new CustomProvider({});
|
||||
}
|
||||
} else if (typeof providerConfig === 'object') {
|
||||
// Provider config
|
||||
if (providerConfig.type === 'google-analytics' || providerConfig.type === 'ga') {
|
||||
provider = new GoogleAnalyticsProvider(providerConfig);
|
||||
} else if (providerConfig.type === 'custom') {
|
||||
provider = new CustomProvider(providerConfig);
|
||||
} else if (providerConfig instanceof AnalyticsProvider) {
|
||||
provider = providerConfig;
|
||||
}
|
||||
}
|
||||
|
||||
if (provider) {
|
||||
this.providers.push(provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if consent is given (GDPR)
|
||||
*/
|
||||
hasConsent() {
|
||||
if (!this.config.requireConsent) {
|
||||
return true;
|
||||
}
|
||||
return this.config.consentGiven;
|
||||
}
|
||||
|
||||
/**
|
||||
* Give consent (GDPR)
|
||||
*/
|
||||
giveConsent() {
|
||||
this.config.consentGiven = true;
|
||||
|
||||
// Process queued events
|
||||
this.processEventQueue();
|
||||
|
||||
Logger.info('[Analytics] Consent given');
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke consent (GDPR)
|
||||
*/
|
||||
revokeConsent() {
|
||||
this.config.consentGiven = false;
|
||||
Logger.info('[Analytics] Consent revoked');
|
||||
}
|
||||
|
||||
/**
|
||||
* Track event
|
||||
*/
|
||||
async track(eventName, properties = {}) {
|
||||
if (!this.config.enabled || !this.hasConsent()) {
|
||||
// Queue event if consent required
|
||||
if (this.config.requireConsent && !this.config.consentGiven) {
|
||||
this.eventQueue.push({ type: 'event', name: eventName, properties });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const eventData = {
|
||||
name: eventName,
|
||||
properties: this.anonymizeData(properties),
|
||||
timestamp: Date.now(),
|
||||
userId: this.userId
|
||||
};
|
||||
|
||||
// Send to all providers
|
||||
for (const provider of this.providers) {
|
||||
try {
|
||||
await provider.track(eventName, eventData.properties);
|
||||
} catch (error) {
|
||||
Logger.error('[Analytics] Provider error', error);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.debug('[Analytics] Event tracked', eventData);
|
||||
|
||||
// Trigger event
|
||||
this.triggerAnalyticsEvent('track', eventData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Track page view
|
||||
*/
|
||||
async trackPageView(path = null, properties = {}) {
|
||||
if (!this.config.enabled || !this.hasConsent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pagePath = path || window.location.pathname;
|
||||
const pageData = {
|
||||
path: pagePath,
|
||||
title: document.title,
|
||||
properties: this.anonymizeData(properties),
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
// Send to all providers
|
||||
for (const provider of this.providers) {
|
||||
try {
|
||||
await provider.pageView(pagePath, pageData.properties);
|
||||
} catch (error) {
|
||||
Logger.error('[Analytics] Provider error', error);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.debug('[Analytics] Page view tracked', pageData);
|
||||
|
||||
// Trigger event
|
||||
this.triggerAnalyticsEvent('page_view', pageData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Identify user
|
||||
*/
|
||||
async identify(userId, traits = {}) {
|
||||
if (!this.config.enabled || !this.hasConsent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.userId = userId;
|
||||
this.userTraits = { ...this.userTraits, ...traits };
|
||||
|
||||
// Send to all providers
|
||||
for (const provider of this.providers) {
|
||||
try {
|
||||
if (typeof provider.identify === 'function') {
|
||||
await provider.identify(userId, this.userTraits);
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.error('[Analytics] Provider error', error);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.debug('[Analytics] User identified', { userId, traits: this.userTraits });
|
||||
}
|
||||
|
||||
/**
|
||||
* Track user behavior
|
||||
*/
|
||||
async trackBehavior(action, target, properties = {}) {
|
||||
await this.track('user_behavior', {
|
||||
action,
|
||||
target,
|
||||
...properties
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Anonymize data for GDPR compliance
|
||||
*/
|
||||
anonymizeData(data) {
|
||||
if (!this.config.gdprCompliant) {
|
||||
return data;
|
||||
}
|
||||
|
||||
const anonymized = { ...data };
|
||||
|
||||
// Anonymize IP if enabled
|
||||
if (this.config.anonymizeIp && anonymized.ip) {
|
||||
anonymized.ip = anonymized.ip.split('.').slice(0, 3).join('.') + '.0';
|
||||
}
|
||||
|
||||
// Remove PII if present
|
||||
const piiFields = ['email', 'phone', 'address', 'ssn', 'credit_card'];
|
||||
piiFields.forEach(field => {
|
||||
if (anonymized[field]) {
|
||||
delete anonymized[field];
|
||||
}
|
||||
});
|
||||
|
||||
return anonymized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process queued events
|
||||
*/
|
||||
processEventQueue() {
|
||||
while (this.eventQueue.length > 0) {
|
||||
const event = this.eventQueue.shift();
|
||||
if (event.type === 'event') {
|
||||
this.track(event.name, event.properties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger analytics event
|
||||
*/
|
||||
triggerAnalyticsEvent(type, data) {
|
||||
const event = new CustomEvent(`analytics:${type}`, {
|
||||
detail: data,
|
||||
bubbles: true
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable analytics
|
||||
*/
|
||||
enable() {
|
||||
this.config.enabled = true;
|
||||
Logger.info('[Analytics] Enabled');
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable analytics
|
||||
*/
|
||||
disable() {
|
||||
this.config.enabled = false;
|
||||
Logger.info('[Analytics] Disabled');
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy analytics
|
||||
*/
|
||||
destroy() {
|
||||
this.providers = [];
|
||||
this.eventQueue = [];
|
||||
this.userId = null;
|
||||
this.userTraits = {};
|
||||
|
||||
Logger.info('[Analytics] Destroyed');
|
||||
}
|
||||
}
|
||||
|
||||
8
resources/js/modules/analytics/AnalyticsProvider.js
Normal file
8
resources/js/modules/analytics/AnalyticsProvider.js
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Analytics Provider Base Classes
|
||||
*
|
||||
* Base classes and implementations for analytics providers.
|
||||
*/
|
||||
|
||||
export { AnalyticsProvider, GoogleAnalyticsProvider, CustomProvider } from './Analytics.js';
|
||||
|
||||
85
resources/js/modules/analytics/index.js
Normal file
85
resources/js/modules/analytics/index.js
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Analytics Module
|
||||
*
|
||||
* Provides unified analytics system for event tracking, page views, and user behavior.
|
||||
*
|
||||
* Usage:
|
||||
* - Add data-module="analytics" to enable global analytics
|
||||
* - Or import and use directly: import { Analytics } from './modules/analytics/index.js'
|
||||
*
|
||||
* Features:
|
||||
* - Event tracking
|
||||
* - Page view tracking
|
||||
* - User behavior tracking
|
||||
* - Custom events
|
||||
* - Integration with LiveComponents
|
||||
* - GDPR compliance
|
||||
*/
|
||||
|
||||
import { Logger } from '../../core/logger.js';
|
||||
import { Analytics, AnalyticsProvider, GoogleAnalyticsProvider, CustomProvider } from './Analytics.js';
|
||||
|
||||
const AnalyticsModule = {
|
||||
name: 'analytics',
|
||||
analytics: null,
|
||||
|
||||
init(config = {}, state = null) {
|
||||
Logger.info('[AnalyticsModule] Module initialized');
|
||||
|
||||
// Create analytics instance
|
||||
this.analytics = Analytics.create(config);
|
||||
|
||||
// Expose globally for easy access
|
||||
if (typeof window !== 'undefined') {
|
||||
window.Analytics = this.analytics;
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get analytics instance
|
||||
*/
|
||||
getAnalytics() {
|
||||
return this.analytics || Analytics.create();
|
||||
},
|
||||
|
||||
/**
|
||||
* Track event
|
||||
*/
|
||||
async track(eventName, properties = {}) {
|
||||
const analytics = this.getAnalytics();
|
||||
return await analytics.track(eventName, properties);
|
||||
},
|
||||
|
||||
/**
|
||||
* Track page view
|
||||
*/
|
||||
async trackPageView(path = null, properties = {}) {
|
||||
const analytics = this.getAnalytics();
|
||||
return await analytics.trackPageView(path, properties);
|
||||
},
|
||||
|
||||
destroy() {
|
||||
if (this.analytics) {
|
||||
this.analytics.destroy();
|
||||
this.analytics = null;
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined' && window.Analytics) {
|
||||
delete window.Analytics;
|
||||
}
|
||||
|
||||
Logger.info('[AnalyticsModule] Module destroyed');
|
||||
}
|
||||
};
|
||||
|
||||
// Export for direct usage
|
||||
export { Analytics, AnalyticsProvider, GoogleAnalyticsProvider, CustomProvider };
|
||||
|
||||
// Export as default for module system
|
||||
export default AnalyticsModule;
|
||||
|
||||
// Export init function for module system
|
||||
export const init = AnalyticsModule.init.bind(AnalyticsModule);
|
||||
|
||||
Reference in New Issue
Block a user