// Main JavaScript file for Outline VPN // Helper function to format bytes to human-readable format function formatBytes(bytes, decimals = 2) { if (bytes === 0) return '0 Bytes'; const k = 1024; const dm = decimals < 0 ? 0 : decimals; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; } // Helper function to format duration function formatDuration(milliseconds) { const seconds = Math.floor(milliseconds / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24); if (days > 0) return `${days}d ${hours % 24}h`; if (hours > 0) return `${hours}h ${minutes % 60}m`; if (minutes > 0) return `${minutes}m ${seconds % 60}s`; return `${seconds}s`; } // Show toast notification function showToast(message, type = 'info') { const toast = document.createElement('div'); toast.className = `toast align-items-center text-white bg-${type} border-0`; toast.setAttribute('role', 'alert'); toast.setAttribute('aria-live', 'assertive'); toast.setAttribute('aria-atomic', 'true'); toast.innerHTML = `
${message}
`; const container = document.createElement('div'); container.className = 'toast-container position-fixed bottom-0 end-0 p-3'; container.appendChild(toast); document.body.appendChild(container); const bsToast = new bootstrap.Toast(toast); bsToast.show(); toast.addEventListener('hidden.bs.toast', () => { container.remove(); }); } // Copy text to clipboard function copyToClipboard(text, successMessage = 'Copied to clipboard!') { navigator.clipboard.writeText(text) .then(() => showToast(successMessage, 'success')) .catch(() => showToast('Failed to copy text', 'danger')); } // Download file helper function downloadFile(content, filename, type = 'application/json') { const blob = new Blob([content], { type }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); } // Form validation function validateForm(formElement) { const requiredFields = formElement.querySelectorAll('[required]'); let isValid = true; requiredFields.forEach(field => { if (!field.value.trim()) { isValid = false; field.classList.add('is-invalid'); } else { field.classList.remove('is-invalid'); } }); return isValid; } // Password strength checker function checkPasswordStrength(password) { let strength = 0; const messages = []; if (password.length >= 8) strength++; else messages.push('Password should be at least 8 characters long'); if (password.match(/[a-z]/)) strength++; if (password.match(/[A-Z]/)) strength++; else messages.push('Include at least one uppercase letter'); if (password.match(/[0-9]/)) strength++; else messages.push('Include at least one number'); if (password.match(/[^a-zA-Z0-9]/)) strength++; else messages.push('Include at least one special character'); return { score: strength, messages: messages, label: ['Very Weak', 'Weak', 'Fair', 'Good', 'Strong'][strength - 1] || 'Very Weak' }; } // Initialize password strength meter if it exists document.addEventListener('DOMContentLoaded', () => { const passwordInput = document.querySelector('input[type="password"]'); if (passwordInput) { const feedbackDiv = document.createElement('div'); feedbackDiv.className = 'password-strength-meter mt-2'; passwordInput.parentNode.appendChild(feedbackDiv); passwordInput.addEventListener('input', (e) => { const strength = checkPasswordStrength(e.target.value); const color = ['danger', 'warning', 'info', 'primary', 'success'][strength.score - 1] || 'danger'; feedbackDiv.innerHTML = `
${strength.label} `; }); } }); // Initialize tooltips document.addEventListener('DOMContentLoaded', () => { const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); tooltipTriggerList.map(function (tooltipTriggerEl) { return new bootstrap.Tooltip(tooltipTriggerEl); }); });