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:
113
resources/js/modules/router/RouteGuard.js
Normal file
113
resources/js/modules/router/RouteGuard.js
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Route Guard
|
||||
*
|
||||
* Provides route-level access control and guards.
|
||||
*/
|
||||
|
||||
import { Logger } from '../../core/logger.js';
|
||||
|
||||
/**
|
||||
* RouteGuard - Route access control
|
||||
*/
|
||||
export class RouteGuard {
|
||||
constructor(name, guardFn) {
|
||||
this.name = name;
|
||||
this.guardFn = guardFn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new RouteGuard
|
||||
*/
|
||||
static create(name, guardFn) {
|
||||
return new RouteGuard(name, guardFn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute guard
|
||||
*/
|
||||
async execute(to, from, context = {}) {
|
||||
try {
|
||||
const result = await this.guardFn(to, from, context);
|
||||
return {
|
||||
allowed: result !== false && result !== null,
|
||||
redirect: typeof result === 'string' ? result : null,
|
||||
reason: typeof result === 'object' && result.reason ? result.reason : null
|
||||
};
|
||||
} catch (error) {
|
||||
Logger.error(`[RouteGuard] Guard "${this.name}" error:`, error);
|
||||
return {
|
||||
allowed: false,
|
||||
redirect: null,
|
||||
reason: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Built-in guards
|
||||
*/
|
||||
export const BuiltInGuards = {
|
||||
/**
|
||||
* Require authentication
|
||||
*/
|
||||
auth: RouteGuard.create('auth', async (to, from) => {
|
||||
// Check if user is authenticated
|
||||
// This would need to be implemented based on your auth system
|
||||
const isAuthenticated = checkAuth(); // Placeholder
|
||||
if (!isAuthenticated) {
|
||||
return '/login';
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Require guest (not authenticated)
|
||||
*/
|
||||
guest: RouteGuard.create('guest', async (to, from) => {
|
||||
const isAuthenticated = checkAuth(); // Placeholder
|
||||
if (isAuthenticated) {
|
||||
return '/';
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Require specific role
|
||||
*/
|
||||
role: (requiredRole) => RouteGuard.create('role', async (to, from) => {
|
||||
const userRole = getUserRole(); // Placeholder
|
||||
if (userRole !== requiredRole) {
|
||||
return '/unauthorized';
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Require permission
|
||||
*/
|
||||
permission: (requiredPermission) => RouteGuard.create('permission', async (to, from) => {
|
||||
const hasPermission = checkPermission(requiredPermission); // Placeholder
|
||||
if (!hasPermission) {
|
||||
return '/unauthorized';
|
||||
}
|
||||
return true;
|
||||
})
|
||||
};
|
||||
|
||||
// Placeholder functions (would be implemented based on auth system)
|
||||
function checkAuth() {
|
||||
// Implementation depends on auth system
|
||||
return false;
|
||||
}
|
||||
|
||||
function getUserRole() {
|
||||
// Implementation depends on auth system
|
||||
return null;
|
||||
}
|
||||
|
||||
function checkPermission(permission) {
|
||||
// Implementation depends on auth system
|
||||
return false;
|
||||
}
|
||||
|
||||
78
resources/js/modules/router/RouteMiddleware.js
Normal file
78
resources/js/modules/router/RouteMiddleware.js
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Route Middleware
|
||||
*
|
||||
* Provides route-level middleware for cross-cutting concerns.
|
||||
*/
|
||||
|
||||
import { Logger } from '../../core/logger.js';
|
||||
|
||||
/**
|
||||
* RouteMiddleware - Route middleware
|
||||
*/
|
||||
export class RouteMiddleware {
|
||||
constructor(name, middlewareFn) {
|
||||
this.name = name;
|
||||
this.middlewareFn = middlewareFn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new RouteMiddleware
|
||||
*/
|
||||
static create(name, middlewareFn) {
|
||||
return new RouteMiddleware(name, middlewareFn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute middleware
|
||||
*/
|
||||
async execute(to, from, next, context = {}) {
|
||||
try {
|
||||
await this.middlewareFn(to, from, next, context);
|
||||
} catch (error) {
|
||||
Logger.error(`[RouteMiddleware] Middleware "${this.name}" error:`, error);
|
||||
next(false); // Block navigation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Built-in middleware
|
||||
*/
|
||||
export const BuiltInMiddleware = {
|
||||
/**
|
||||
* Analytics middleware
|
||||
*/
|
||||
analytics: RouteMiddleware.create('analytics', async (to, from, next) => {
|
||||
// Track page view
|
||||
if (typeof window !== 'undefined' && window.analytics) {
|
||||
window.analytics.track('page_view', {
|
||||
path: to.path,
|
||||
title: to.title
|
||||
});
|
||||
}
|
||||
next();
|
||||
}),
|
||||
|
||||
/**
|
||||
* Loading middleware
|
||||
*/
|
||||
loading: RouteMiddleware.create('loading', async (to, from, next) => {
|
||||
// Show loading indicator
|
||||
document.body.classList.add('route-loading');
|
||||
next();
|
||||
|
||||
// Hide loading indicator after navigation
|
||||
setTimeout(() => {
|
||||
document.body.classList.remove('route-loading');
|
||||
}, 100);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Scroll to top middleware
|
||||
*/
|
||||
scrollToTop: RouteMiddleware.create('scroll-to-top', async (to, from, next) => {
|
||||
next();
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
})
|
||||
};
|
||||
|
||||
378
resources/js/modules/router/Router.js
Normal file
378
resources/js/modules/router/Router.js
Normal file
@@ -0,0 +1,378 @@
|
||||
/**
|
||||
* Enhanced Router
|
||||
*
|
||||
* Provides enhanced routing with guards, middleware, lazy loading, and analytics.
|
||||
*/
|
||||
|
||||
import { Logger } from '../../core/logger.js';
|
||||
import { RouteGuard, BuiltInGuards } from './RouteGuard.js';
|
||||
import { RouteMiddleware, BuiltInMiddleware } from './RouteMiddleware.js';
|
||||
|
||||
/**
|
||||
* Router - Enhanced routing system
|
||||
*/
|
||||
export class Router {
|
||||
constructor(config = {}) {
|
||||
this.config = {
|
||||
mode: config.mode || 'history', // 'history' | 'hash'
|
||||
base: config.base || '/',
|
||||
enableAnalytics: config.enableAnalytics ?? true,
|
||||
...config
|
||||
};
|
||||
|
||||
this.routes = new Map();
|
||||
this.guards = new Map();
|
||||
this.middleware = [];
|
||||
this.beforeEachHooks = [];
|
||||
this.afterEachHooks = [];
|
||||
this.currentRoute = null;
|
||||
this.analytics = {
|
||||
navigations: [],
|
||||
errors: []
|
||||
};
|
||||
|
||||
// Initialize
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Router instance
|
||||
*/
|
||||
static create(config = {}) {
|
||||
return new Router(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize router
|
||||
*/
|
||||
init() {
|
||||
// Handle browser navigation
|
||||
window.addEventListener('popstate', (event) => {
|
||||
this.handlePopState(event);
|
||||
});
|
||||
|
||||
Logger.info('[Router] Initialized', {
|
||||
mode: this.config.mode,
|
||||
base: this.config.base
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a route
|
||||
*/
|
||||
route(path, config) {
|
||||
const route = {
|
||||
path: this.normalizePath(path),
|
||||
component: config.component,
|
||||
name: config.name || path,
|
||||
title: config.title || null,
|
||||
meta: config.meta || {},
|
||||
guards: config.guards || [],
|
||||
middleware: config.middleware || [],
|
||||
lazy: config.lazy ?? false,
|
||||
...config
|
||||
};
|
||||
|
||||
this.routes.set(route.path, route);
|
||||
|
||||
Logger.debug('[Router] Route registered', route);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register multiple routes
|
||||
*/
|
||||
routes(routesConfig) {
|
||||
routesConfig.forEach(route => {
|
||||
this.route(route.path, route);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a guard
|
||||
*/
|
||||
guard(name, guardFn) {
|
||||
const guard = RouteGuard.create(name, guardFn);
|
||||
this.guards.set(name, guard);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register middleware
|
||||
*/
|
||||
use(middleware) {
|
||||
if (typeof middleware === 'function') {
|
||||
this.middleware.push(RouteMiddleware.create('anonymous', middleware));
|
||||
} else if (middleware instanceof RouteMiddleware) {
|
||||
this.middleware.push(middleware);
|
||||
} else if (typeof middleware === 'string' && BuiltInMiddleware[middleware]) {
|
||||
this.middleware.push(BuiltInMiddleware[middleware]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add beforeEach hook
|
||||
*/
|
||||
beforeEach(hook) {
|
||||
this.beforeEachHooks.push(hook);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add afterEach hook
|
||||
*/
|
||||
afterEach(hook) {
|
||||
this.afterEachHooks.push(hook);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to a route
|
||||
*/
|
||||
async navigate(path, options = {}) {
|
||||
const normalizedPath = this.normalizePath(path);
|
||||
const route = this.routes.get(normalizedPath);
|
||||
|
||||
if (!route) {
|
||||
Logger.warn('[Router] Route not found', normalizedPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
const from = this.currentRoute;
|
||||
const to = {
|
||||
path: normalizedPath,
|
||||
name: route.name,
|
||||
title: route.title,
|
||||
meta: route.meta,
|
||||
component: route.component
|
||||
};
|
||||
|
||||
// Execute beforeEach hooks
|
||||
for (const hook of this.beforeEachHooks) {
|
||||
const result = await hook(to, from);
|
||||
if (result === false || typeof result === 'string') {
|
||||
if (typeof result === 'string') {
|
||||
return await this.navigate(result);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Execute guards
|
||||
for (const guardName of route.guards) {
|
||||
const guard = this.guards.get(guardName) || BuiltInGuards[guardName];
|
||||
if (!guard) {
|
||||
Logger.warn('[Router] Guard not found', guardName);
|
||||
continue;
|
||||
}
|
||||
|
||||
const result = await guard.execute(to, from);
|
||||
if (!result.allowed) {
|
||||
if (result.redirect) {
|
||||
return await this.navigate(result.redirect);
|
||||
}
|
||||
Logger.warn('[Router] Navigation blocked by guard', guardName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Execute middleware
|
||||
let middlewareBlocked = false;
|
||||
for (const middleware of [...this.middleware, ...route.middleware]) {
|
||||
await new Promise((resolve) => {
|
||||
middleware.execute(to, from, (allowed) => {
|
||||
if (allowed === false) {
|
||||
middlewareBlocked = true;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
if (middlewareBlocked) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Load component if lazy
|
||||
if (route.lazy && typeof route.component === 'function') {
|
||||
try {
|
||||
route.component = await route.component();
|
||||
} catch (error) {
|
||||
Logger.error('[Router] Failed to load lazy component', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Update current route
|
||||
this.currentRoute = to;
|
||||
|
||||
// Update browser history
|
||||
if (this.config.mode === 'history') {
|
||||
history.pushState({ route: to }, route.title || '', normalizedPath);
|
||||
} else {
|
||||
window.location.hash = normalizedPath;
|
||||
}
|
||||
|
||||
// Update page title
|
||||
if (route.title) {
|
||||
document.title = route.title;
|
||||
}
|
||||
|
||||
// Track analytics
|
||||
if (this.config.enableAnalytics) {
|
||||
this.trackNavigation(to, from);
|
||||
}
|
||||
|
||||
// Execute afterEach hooks
|
||||
for (const hook of this.afterEachHooks) {
|
||||
await hook(to, from);
|
||||
}
|
||||
|
||||
// Render component
|
||||
await this.renderComponent(route, options);
|
||||
|
||||
Logger.info('[Router] Navigated', { to: normalizedPath });
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render component
|
||||
*/
|
||||
async renderComponent(route, options = {}) {
|
||||
const container = options.container || document.querySelector('main');
|
||||
|
||||
if (!container) {
|
||||
Logger.error('[Router] Container not found');
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof route.component === 'function') {
|
||||
// Component is a function (render function)
|
||||
const content = await route.component(route, options);
|
||||
if (typeof content === 'string') {
|
||||
container.innerHTML = content;
|
||||
} else if (content instanceof HTMLElement) {
|
||||
container.innerHTML = '';
|
||||
container.appendChild(content);
|
||||
}
|
||||
} else if (typeof route.component === 'string') {
|
||||
// Component is a selector or HTML
|
||||
const element = document.querySelector(route.component);
|
||||
if (element) {
|
||||
container.innerHTML = '';
|
||||
container.appendChild(element.cloneNode(true));
|
||||
} else {
|
||||
container.innerHTML = route.component;
|
||||
}
|
||||
}
|
||||
|
||||
// Re-initialize modules in new content
|
||||
this.reinitializeModules(container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-initialize modules in container
|
||||
*/
|
||||
reinitializeModules(container) {
|
||||
// Trigger module re-initialization
|
||||
const event = new CustomEvent('router:content-updated', {
|
||||
detail: { container },
|
||||
bubbles: true
|
||||
});
|
||||
document.dispatchEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle popstate event
|
||||
*/
|
||||
handlePopState(event) {
|
||||
const path = this.config.mode === 'history'
|
||||
? window.location.pathname
|
||||
: window.location.hash.slice(1);
|
||||
|
||||
this.navigate(path, { updateHistory: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize path
|
||||
*/
|
||||
normalizePath(path) {
|
||||
// Remove base if present
|
||||
if (path.startsWith(this.config.base)) {
|
||||
path = path.slice(this.config.base.length);
|
||||
}
|
||||
|
||||
// Ensure leading slash
|
||||
if (!path.startsWith('/')) {
|
||||
path = '/' + path;
|
||||
}
|
||||
|
||||
// Remove trailing slash (except root)
|
||||
if (path !== '/' && path.endsWith('/')) {
|
||||
path = path.slice(0, -1);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Track navigation for analytics
|
||||
*/
|
||||
trackNavigation(to, from) {
|
||||
const navigation = {
|
||||
to: to.path,
|
||||
from: from?.path || null,
|
||||
timestamp: Date.now(),
|
||||
duration: from ? Date.now() - (from.timestamp || Date.now()) : 0
|
||||
};
|
||||
|
||||
this.analytics.navigations.push(navigation);
|
||||
|
||||
// Limit analytics size
|
||||
if (this.analytics.navigations.length > 100) {
|
||||
this.analytics.navigations.shift();
|
||||
}
|
||||
|
||||
// Trigger analytics event
|
||||
const event = new CustomEvent('router:navigation', {
|
||||
detail: navigation,
|
||||
bubbles: true
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current route
|
||||
*/
|
||||
getCurrentRoute() {
|
||||
return this.currentRoute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get analytics
|
||||
*/
|
||||
getAnalytics() {
|
||||
return {
|
||||
...this.analytics,
|
||||
totalNavigations: this.analytics.navigations.length,
|
||||
totalErrors: this.analytics.errors.length
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy router
|
||||
*/
|
||||
destroy() {
|
||||
this.routes.clear();
|
||||
this.guards.clear();
|
||||
this.middleware = [];
|
||||
this.beforeEachHooks = [];
|
||||
this.afterEachHooks = [];
|
||||
this.currentRoute = null;
|
||||
|
||||
Logger.info('[Router] Destroyed');
|
||||
}
|
||||
}
|
||||
|
||||
87
resources/js/modules/router/index.js
Normal file
87
resources/js/modules/router/index.js
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Router Enhancement Module
|
||||
*
|
||||
* Provides enhanced routing with guards, middleware, lazy loading, and analytics.
|
||||
*
|
||||
* Usage:
|
||||
* - Add data-module="router" to enable enhanced router
|
||||
* - Or import and use directly: import { Router } from './modules/router/index.js'
|
||||
*
|
||||
* Features:
|
||||
* - Route guards (auth, permissions)
|
||||
* - Route middleware
|
||||
* - Lazy route loading
|
||||
* - Route analytics
|
||||
* - Integration with LiveComponents
|
||||
*/
|
||||
|
||||
import { Logger } from '../../core/logger.js';
|
||||
import { Router } from './Router.js';
|
||||
import { RouteGuard, BuiltInGuards } from './RouteGuard.js';
|
||||
import { RouteMiddleware, BuiltInMiddleware } from './RouteMiddleware.js';
|
||||
|
||||
const RouterModule = {
|
||||
name: 'router',
|
||||
router: null,
|
||||
|
||||
init(config = {}, state = null) {
|
||||
Logger.info('[RouterModule] Module initialized');
|
||||
|
||||
// Create router
|
||||
this.router = Router.create(config);
|
||||
|
||||
// Expose globally for easy access
|
||||
if (typeof window !== 'undefined') {
|
||||
window.Router = this.router;
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get router instance
|
||||
*/
|
||||
getRouter() {
|
||||
return this.router || Router.create();
|
||||
},
|
||||
|
||||
/**
|
||||
* Register routes
|
||||
*/
|
||||
routes(routesConfig) {
|
||||
const router = this.getRouter();
|
||||
router.routes(routesConfig);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Navigate to route
|
||||
*/
|
||||
async navigate(path, options = {}) {
|
||||
const router = this.getRouter();
|
||||
return await router.navigate(path, options);
|
||||
},
|
||||
|
||||
destroy() {
|
||||
if (this.router) {
|
||||
this.router.destroy();
|
||||
this.router = null;
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined' && window.Router) {
|
||||
delete window.Router;
|
||||
}
|
||||
|
||||
Logger.info('[RouterModule] Module destroyed');
|
||||
}
|
||||
};
|
||||
|
||||
// Export for direct usage
|
||||
export { Router, RouteGuard, RouteMiddleware, BuiltInGuards, BuiltInMiddleware };
|
||||
|
||||
// Export as default for module system
|
||||
export default RouterModule;
|
||||
|
||||
// Export init function for module system
|
||||
export const init = RouterModule.init.bind(RouterModule);
|
||||
|
||||
Reference in New Issue
Block a user