Spaces:
Runtime error
Runtime error
| // 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 = ` | |
| <div class="d-flex"> | |
| <div class="toast-body">${message}</div> | |
| <button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button> | |
| </div> | |
| `; | |
| 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 = ` | |
| <div class="progress" style="height: 5px;"> | |
| <div class="progress-bar bg-${color}" | |
| style="width: ${(strength.score / 5) * 100}%"> | |
| </div> | |
| </div> | |
| <small class="text-${color} mt-1 d-block">${strength.label}</small> | |
| `; | |
| }); | |
| } | |
| }); | |
| // Initialize tooltips | |
| document.addEventListener('DOMContentLoaded', () => { | |
| const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); | |
| tooltipTriggerList.map(function (tooltipTriggerEl) { | |
| return new bootstrap.Tooltip(tooltipTriggerEl); | |
| }); | |
| }); | |