/** * Dashboard animations and interactivity */ // Initialize animations when DOM is loaded document.addEventListener('DOMContentLoaded', () => { initializeAnimations(); initializeCharts(); initializeEventListeners(); }); // Animation utilities const animate = { fadeIn: (element, delay = 0) => { element.style.opacity = '0'; element.style.transform = 'translateY(20px)'; setTimeout(() => { element.style.transition = 'all 0.5s ease'; element.style.opacity = '1'; element.style.transform = 'translateY(0)'; }, delay); }, slideIn: (element, direction = 'left', delay = 0) => { const transforms = { left: 'translateX(-50px)', right: 'translateX(50px)', up: 'translateY(50px)', down: 'translateY(-50px)' }; element.style.opacity = '0'; element.style.transform = transforms[direction]; setTimeout(() => { element.style.transition = 'all 0.5s ease'; element.style.opacity = '1'; element.style.transform = 'translate(0)'; }, delay); }, pulse: (element) => { element.style.transform = 'scale(1)'; element.style.transition = 'transform 0.2s ease'; element.style.transform = 'scale(1.05)'; setTimeout(() => { element.style.transform = 'scale(1)'; }, 200); } }; // Initialize animations function initializeAnimations() { // Animate stat cards const statCards = document.querySelectorAll('.stat-card'); statCards.forEach((card, index) => { animate.fadeIn(card, index * 100); }); // Animate charts const charts = document.querySelectorAll('.chart-container'); charts.forEach((chart, index) => { animate.slideIn(chart, 'up', 300 + index * 100); }); // Animate session cards const sessionCards = document.querySelectorAll('.session-card'); sessionCards.forEach((card, index) => { animate.slideIn(card, 'right', 500 + index * 100); }); } // Initialize Charts function initializeCharts() { // Traffic Chart const trafficChart = document.getElementById('traffic-chart'); if (trafficChart) { new Chart(trafficChart, { type: 'line', data: { labels: getLastNHours(24), datasets: [{ label: 'Upload', data: generateRandomData(24), borderColor: '#4F46E5', tension: 0.4 }, { label: 'Download', data: generateRandomData(24), borderColor: '#10B981', tension: 0.4 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'top' } }, scales: { y: { beginAtZero: true } } } }); } // Protocol Distribution Chart const protocolChart = document.getElementById('protocol-chart'); if (protocolChart) { new Chart(protocolChart, { type: 'doughnut', data: { labels: ['Shadowsocks', 'IKEv2', 'L2TP', 'PPTP'], datasets: [{ data: [40, 30, 20, 10], backgroundColor: [ '#4F46E5', '#10B981', '#F59E0B', '#EF4444' ] }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom' } } } }); } } // Initialize Event Listeners function initializeEventListeners() { // Quick Action Buttons const quickActions = document.querySelectorAll('.quick-action'); quickActions.forEach(action => { action.addEventListener('mouseenter', () => { animate.pulse(action); }); }); // Responsive Sidebar Toggle const sidebarToggle = document.getElementById('sidebar-toggle'); const sidebar = document.querySelector('.dashboard-sidebar'); if (sidebarToggle && sidebar) { sidebarToggle.addEventListener('click', () => { sidebar.classList.toggle('active'); }); } // Live Data Updates setInterval(updateLiveStats, 5000); } // Utility Functions function getLastNHours(n) { const hours = []; for (let i = n - 1; i >= 0; i--) { const d = new Date(); d.setHours(d.getHours() - i); hours.push(d.getHours() + ':00'); } return hours; } function generateRandomData(n) { return Array.from({length: n}, () => Math.floor(Math.random() * 100)); } function formatBytes(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } // Live Data Updates function updateLiveStats() { fetch('/api/stats') .then(response => response.json()) .then(data => { // Update connection status const statusIndicator = document.querySelector('.status-indicator'); if (statusIndicator) { statusIndicator.className = `status-indicator ${data.isConnected ? 'online' : 'offline'}`; statusIndicator.textContent = data.isConnected ? 'Connected' : 'Disconnected'; } // Update traffic stats const uploadStat = document.getElementById('upload-stat'); const downloadStat = document.getElementById('download-stat'); if (uploadStat) uploadStat.textContent = formatBytes(data.uploadTotal); if (downloadStat) downloadStat.textContent = formatBytes(data.downloadTotal); // Update active sessions updateSessionsList(data.sessions); }) .catch(console.error); } function updateSessionsList(sessions) { const sessionsList = document.getElementById('active-sessions'); if (!sessionsList) return; sessions.forEach(session => { const existingSession = document.getElementById(`session-${session.id}`); if (!existingSession) { // Create new session card const sessionCard = createSessionCard(session); animate.slideIn(sessionCard, 'right'); sessionsList.appendChild(sessionCard); } else { // Update existing session updateSessionCard(existingSession, session); } }); } function createSessionCard(session) { const card = document.createElement('div'); card.id = `session-${session.id}`; card.className = 'session-card dashboard-card'; card.innerHTML = `
${session.protocol}
${session.ip}
↑ ${formatBytes(session.upload)}
↓ ${formatBytes(session.download)}
`; return card; } function updateSessionCard(card, session) { const uploadEl = card.querySelector('.session-stats div:first-child'); const downloadEl = card.querySelector('.session-stats div:last-child'); uploadEl.textContent = `↑ ${formatBytes(session.upload)}`; downloadEl.textContent = `↓ ${formatBytes(session.download)}`; }