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:
347
resources/js/modules/livecomponent/LiveComponentUIHelper.js
Normal file
347
resources/js/modules/livecomponent/LiveComponentUIHelper.js
Normal file
@@ -0,0 +1,347 @@
|
||||
/**
|
||||
* LiveComponent UI Helper
|
||||
*
|
||||
* Provides unified API for UI components (Dialogs, Modals, Notifications)
|
||||
* integrated with LiveComponents.
|
||||
*/
|
||||
|
||||
import { UIManager } from '../ui/UIManager.js';
|
||||
|
||||
export class LiveComponentUIHelper {
|
||||
constructor(liveComponentManager) {
|
||||
this.manager = liveComponentManager;
|
||||
this.uiManager = UIManager;
|
||||
this.activeDialogs = new Map(); // componentId → dialog instances
|
||||
this.activeNotifications = new Map(); // componentId → notification instances
|
||||
}
|
||||
|
||||
/**
|
||||
* Show dialog from LiveComponent action
|
||||
*
|
||||
* @param {string} componentId - Component ID
|
||||
* @param {Object} options - Dialog options
|
||||
* @returns {Object} Dialog instance
|
||||
*/
|
||||
showDialog(componentId, options = {}) {
|
||||
const {
|
||||
title = '',
|
||||
content = '',
|
||||
size = 'medium',
|
||||
buttons = [],
|
||||
closeOnBackdrop = true,
|
||||
closeOnEscape = true,
|
||||
onClose = null,
|
||||
onConfirm = null
|
||||
} = options;
|
||||
|
||||
// Create dialog content
|
||||
const dialogContent = this.createDialogContent(title, content, buttons, {
|
||||
onClose,
|
||||
onConfirm
|
||||
});
|
||||
|
||||
// Show modal via UIManager
|
||||
const modal = this.uiManager.open('modal', {
|
||||
content: dialogContent,
|
||||
className: `livecomponent-dialog livecomponent-dialog--${size}`,
|
||||
onClose: () => {
|
||||
if (onClose) {
|
||||
onClose();
|
||||
}
|
||||
this.activeDialogs.delete(componentId);
|
||||
}
|
||||
});
|
||||
|
||||
// Store reference
|
||||
this.activeDialogs.set(componentId, modal);
|
||||
|
||||
return modal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show confirmation dialog
|
||||
*
|
||||
* @param {string} componentId - Component ID
|
||||
* @param {Object} options - Confirmation options
|
||||
* @returns {Promise<boolean>} Promise resolving to true if confirmed
|
||||
*/
|
||||
showConfirm(componentId, options = {}) {
|
||||
return new Promise((resolve) => {
|
||||
const {
|
||||
title = 'Confirm',
|
||||
message = 'Are you sure?',
|
||||
confirmText = 'Confirm',
|
||||
cancelText = 'Cancel',
|
||||
confirmClass = 'btn-primary',
|
||||
cancelClass = 'btn-secondary'
|
||||
} = options;
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
text: cancelText,
|
||||
class: cancelClass,
|
||||
action: () => {
|
||||
this.closeDialog(componentId);
|
||||
resolve(false);
|
||||
}
|
||||
},
|
||||
{
|
||||
text: confirmText,
|
||||
class: confirmClass,
|
||||
action: () => {
|
||||
this.closeDialog(componentId);
|
||||
resolve(true);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
this.showDialog(componentId, {
|
||||
title,
|
||||
content: `<p>${message}</p>`,
|
||||
buttons,
|
||||
size: 'small'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show alert dialog
|
||||
*
|
||||
* @param {string} componentId - Component ID
|
||||
* @param {Object} options - Alert options
|
||||
*/
|
||||
showAlert(componentId, options = {}) {
|
||||
const {
|
||||
title = 'Alert',
|
||||
message = '',
|
||||
buttonText = 'OK',
|
||||
type = 'info' // info, success, warning, error
|
||||
} = options;
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
text: buttonText,
|
||||
class: `btn-${type}`,
|
||||
action: () => {
|
||||
this.closeDialog(componentId);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
return this.showDialog(componentId, {
|
||||
title,
|
||||
content: `<p class="alert-message alert-message--${type}">${message}</p>`,
|
||||
buttons,
|
||||
size: 'small'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Close dialog
|
||||
*
|
||||
* @param {string} componentId - Component ID
|
||||
*/
|
||||
closeDialog(componentId) {
|
||||
const dialog = this.activeDialogs.get(componentId);
|
||||
if (dialog && typeof dialog.close === 'function') {
|
||||
dialog.close();
|
||||
this.activeDialogs.delete(componentId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show notification
|
||||
*
|
||||
* @param {string} componentId - Component ID
|
||||
* @param {Object} options - Notification options
|
||||
*/
|
||||
showNotification(componentId, options = {}) {
|
||||
const {
|
||||
message = '',
|
||||
type = 'info', // info, success, warning, error
|
||||
duration = 5000,
|
||||
position = 'top-right',
|
||||
action = null
|
||||
} = options;
|
||||
|
||||
// Create notification element
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `livecomponent-notification livecomponent-notification--${type} livecomponent-notification--${position}`;
|
||||
notification.setAttribute('role', 'alert');
|
||||
notification.setAttribute('aria-live', 'polite');
|
||||
|
||||
notification.innerHTML = `
|
||||
<div class="notification-content">
|
||||
<span class="notification-message">${message}</span>
|
||||
${action ? `<button class="notification-action">${action.text}</button>` : ''}
|
||||
<button class="notification-close" aria-label="Close">×</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add styles
|
||||
notification.style.cssText = `
|
||||
position: fixed;
|
||||
${position.includes('top') ? 'top' : 'bottom'}: 1rem;
|
||||
${position.includes('left') ? 'left' : 'right'}: 1rem;
|
||||
max-width: 400px;
|
||||
padding: 1rem;
|
||||
background: ${this.getNotificationColor(type)};
|
||||
color: white;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
z-index: 10000;
|
||||
opacity: 0;
|
||||
transform: translateY(${position.includes('top') ? '-20px' : '20px'});
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
`;
|
||||
|
||||
// Add to DOM
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// Animate in
|
||||
requestAnimationFrame(() => {
|
||||
notification.style.opacity = '1';
|
||||
notification.style.transform = 'translateY(0)';
|
||||
});
|
||||
|
||||
// Setup close button
|
||||
const closeBtn = notification.querySelector('.notification-close');
|
||||
closeBtn.addEventListener('click', () => {
|
||||
this.hideNotification(notification);
|
||||
});
|
||||
|
||||
// Setup action button
|
||||
if (action) {
|
||||
const actionBtn = notification.querySelector('.notification-action');
|
||||
actionBtn.addEventListener('click', () => {
|
||||
if (action.handler) {
|
||||
action.handler();
|
||||
}
|
||||
this.hideNotification(notification);
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-hide after duration
|
||||
if (duration > 0) {
|
||||
setTimeout(() => {
|
||||
this.hideNotification(notification);
|
||||
}, duration);
|
||||
}
|
||||
|
||||
// Store reference
|
||||
this.activeNotifications.set(componentId, notification);
|
||||
|
||||
return notification;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide notification
|
||||
*
|
||||
* @param {HTMLElement|string} notificationOrComponentId - Notification element or component ID
|
||||
*/
|
||||
hideNotification(notificationOrComponentId) {
|
||||
const notification = typeof notificationOrComponentId === 'string'
|
||||
? this.activeNotifications.get(notificationOrComponentId)
|
||||
: notificationOrComponentId;
|
||||
|
||||
if (!notification) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Animate out
|
||||
notification.style.opacity = '0';
|
||||
notification.style.transform = `translateY(${notification.classList.contains('livecomponent-notification--top') ? '-20px' : '20px'})`;
|
||||
|
||||
setTimeout(() => {
|
||||
if (notification.parentNode) {
|
||||
notification.parentNode.removeChild(notification);
|
||||
}
|
||||
|
||||
// Remove from map
|
||||
for (const [componentId, notif] of this.activeNotifications.entries()) {
|
||||
if (notif === notification) {
|
||||
this.activeNotifications.delete(componentId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create dialog content HTML
|
||||
*
|
||||
* @param {string} title - Dialog title
|
||||
* @param {string} content - Dialog content
|
||||
* @param {Array} buttons - Button configurations
|
||||
* @param {Object} callbacks - Callback functions
|
||||
* @returns {string} HTML content
|
||||
*/
|
||||
createDialogContent(title, content, buttons, callbacks) {
|
||||
const buttonsHtml = buttons.map((btn, index) => {
|
||||
const btnClass = btn.class || 'btn-secondary';
|
||||
const btnAction = btn.action || (() => {});
|
||||
return `<button class="btn ${btnClass}" data-dialog-action="${index}">${btn.text || 'Button'}</button>`;
|
||||
}).join('');
|
||||
|
||||
return `
|
||||
<div class="livecomponent-dialog-content">
|
||||
${title ? `<div class="dialog-header"><h3>${title}</h3></div>` : ''}
|
||||
<div class="dialog-body">${content}</div>
|
||||
${buttons.length > 0 ? `<div class="dialog-footer">${buttonsHtml}</div>` : ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get notification color by type
|
||||
*
|
||||
* @param {string} type - Notification type
|
||||
* @returns {string} Color value
|
||||
*/
|
||||
getNotificationColor(type) {
|
||||
const colors = {
|
||||
info: '#3b82f6',
|
||||
success: '#10b981',
|
||||
warning: '#f59e0b',
|
||||
error: '#ef4444'
|
||||
};
|
||||
return colors[type] || colors.info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show loading dialog
|
||||
*
|
||||
* @param {string} componentId - Component ID
|
||||
* @param {string} message - Loading message
|
||||
* @returns {Object} Dialog instance
|
||||
*/
|
||||
showLoadingDialog(componentId, message = 'Loading...') {
|
||||
return this.showDialog(componentId, {
|
||||
title: '',
|
||||
content: `
|
||||
<div class="loading-dialog">
|
||||
<div class="spinner"></div>
|
||||
<p>${message}</p>
|
||||
</div>
|
||||
`,
|
||||
size: 'small',
|
||||
buttons: [],
|
||||
closeOnBackdrop: false,
|
||||
closeOnEscape: false
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup all UI components for component
|
||||
*
|
||||
* @param {string} componentId - Component ID
|
||||
*/
|
||||
cleanup(componentId) {
|
||||
// Close dialogs
|
||||
this.closeDialog(componentId);
|
||||
|
||||
// Hide notifications
|
||||
this.hideNotification(componentId);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user