Some checks failed
- Add redis-exporter container to docker-compose (oliver006/redis_exporter, 32MB) - Add Redis scrape target to Prometheus config - Add 4 Redis alert rules: RedisDown, HighMemory, HighConnections, RejectedConnections - Document Step 19b (Sentry Error Tracking) in Hetzner deployment guide - Document Step 19c (Redis Monitoring) in Hetzner deployment guide - Update resource budget and port reference tables Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
202 lines
6.3 KiB
JavaScript
202 lines
6.3 KiB
JavaScript
// static/shared/js/utils.js
|
|
// noqa: js-001 - Core utilities, may log before logger is available
|
|
/**
|
|
* Utility functions for the application
|
|
*/
|
|
|
|
const Utils = {
|
|
/**
|
|
* Format date for display
|
|
* @param {string} dateString - ISO date string
|
|
* @returns {string} Formatted date
|
|
*/
|
|
formatDate(dateString) {
|
|
if (!dateString) return '-';
|
|
|
|
try {
|
|
const date = new Date(dateString);
|
|
return date.toLocaleDateString('en-US', {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric'
|
|
});
|
|
} catch (error) {
|
|
console.error('Error formatting date:', error);
|
|
return dateString;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Format date with time
|
|
* @param {string} dateString - ISO date string
|
|
* @returns {string} Formatted date with time
|
|
*/
|
|
formatDateTime(dateString) {
|
|
if (!dateString) return '-';
|
|
|
|
try {
|
|
const date = new Date(dateString);
|
|
return date.toLocaleString('en-US', {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
});
|
|
} catch (error) {
|
|
console.error('Error formatting datetime:', error);
|
|
return dateString;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Format currency
|
|
* @param {number} amount - Amount to format
|
|
* @param {string} currency - Currency code (default: USD)
|
|
* @returns {string} Formatted currency
|
|
*/
|
|
formatCurrency(amount, currency = 'USD') {
|
|
if (amount === null || amount === undefined) return '-';
|
|
|
|
try {
|
|
return new Intl.NumberFormat('en-US', {
|
|
style: 'currency',
|
|
currency: currency
|
|
}).format(amount);
|
|
} catch (error) {
|
|
console.error('Error formatting currency:', error);
|
|
return amount.toString();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Format number with commas
|
|
* @param {number} num - Number to format
|
|
* @returns {string} Formatted number
|
|
*/
|
|
formatNumber(num) {
|
|
if (num === null || num === undefined) return '0';
|
|
return num.toLocaleString('en-US');
|
|
},
|
|
|
|
/**
|
|
* Show toast notification
|
|
* @param {string} message - Toast message
|
|
* @param {string} type - Toast type: 'success', 'error', 'warning', 'info'
|
|
* @param {number} duration - Duration in ms (default: 3000)
|
|
*/
|
|
showToast(message, type = 'info', duration = 3000) {
|
|
// Create toast element
|
|
const toast = document.createElement('div');
|
|
toast.className = `fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-lg text-white z-50 transition-opacity duration-300 ${
|
|
type === 'success' ? 'bg-green-500' :
|
|
type === 'error' ? 'bg-red-500' :
|
|
type === 'warning' ? 'bg-yellow-500' :
|
|
'bg-blue-500'
|
|
}`;
|
|
toast.textContent = message;
|
|
|
|
document.body.appendChild(toast);
|
|
|
|
// Fade out and remove
|
|
setTimeout(() => {
|
|
toast.style.opacity = '0';
|
|
setTimeout(() => toast.remove(), 300);
|
|
}, duration);
|
|
},
|
|
|
|
/**
|
|
* Debounce function
|
|
* @param {Function} func - Function to debounce
|
|
* @param {number} wait - Wait time in ms
|
|
* @returns {Function} Debounced function
|
|
*/
|
|
debounce(func, wait = 300) {
|
|
let timeout;
|
|
return function executedFunction(...args) {
|
|
const later = () => {
|
|
clearTimeout(timeout);
|
|
func(...args);
|
|
};
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(later, wait);
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Get query parameter from URL
|
|
* @param {string} param - Parameter name
|
|
* @returns {string|null} Parameter value
|
|
*/
|
|
getQueryParam(param) {
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
return urlParams.get(param);
|
|
},
|
|
|
|
/**
|
|
* Copy text to clipboard
|
|
* @param {string} text - Text to copy
|
|
*/
|
|
async copyToClipboard(text) {
|
|
try {
|
|
await navigator.clipboard.writeText(text);
|
|
// Use I18n if available, fallback to hardcoded string
|
|
const message = typeof I18n !== 'undefined' ? I18n.t('clipboard.copied') : 'Copied to clipboard';
|
|
this.showToast(message, 'success');
|
|
} catch (error) {
|
|
console.error('Failed to copy:', error);
|
|
const message = typeof I18n !== 'undefined' ? I18n.t('clipboard.failed') : 'Failed to copy';
|
|
this.showToast(message, 'error');
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Truncate string
|
|
* @param {string} str - String to truncate
|
|
* @param {number} maxLength - Maximum length
|
|
* @returns {string} Truncated string
|
|
*/
|
|
truncate(str, maxLength = 50) {
|
|
if (!str || str.length <= maxLength) return str;
|
|
return str.substring(0, maxLength - 3) + '...';
|
|
},
|
|
|
|
/**
|
|
* Validate email format
|
|
* @param {string} email - Email to validate
|
|
* @returns {boolean} Is valid email
|
|
*/
|
|
isValidEmail(email) {
|
|
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
return re.test(email);
|
|
},
|
|
|
|
/**
|
|
* Get status badge class
|
|
* @param {string} status - Status value
|
|
* @returns {string} Tailwind classes for badge
|
|
*/
|
|
getStatusBadgeClass(status) {
|
|
const statusClasses = {
|
|
'active': 'bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-100',
|
|
'inactive': 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-100',
|
|
'pending': 'bg-yellow-100 text-yellow-800 dark:bg-yellow-800 dark:text-yellow-100',
|
|
'verified': 'bg-blue-100 text-blue-800 dark:bg-blue-800 dark:text-blue-100',
|
|
'rejected': 'bg-red-100 text-red-800 dark:bg-red-800 dark:text-red-100'
|
|
};
|
|
return statusClasses[status.toLowerCase()] || 'bg-gray-100 text-gray-800';
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
// Platform Settings
|
|
// ============================================================================
|
|
// Make available globally
|
|
// Note: PlatformSettings is defined in init-alpine.js for admin pages
|
|
window.Utils = Utils;
|
|
|
|
// Export for modules
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = Utils;
|
|
}
|