// Dashboard JavaScript class VPNDashboard { constructor() { this.token = localStorage.getItem('auth_token'); this.currentUser = null; this.clients = []; this.serverStatus = {}; this.protocols = []; this.init(); } async init() { const token = localStorage.getItem("auth_token"); if (!token) { window.location.href = '/auth.html'; return; } this.token = token; try { await this.loadUserInfo(); await this.loadInitialData(); this.setupEventListeners(); this.startAutoRefresh(); } catch (error) { console.error("Dashboard initialization failed:", error); this.showAlert("error", "Failed to load dashboard", error.message); // If loading fails, it might be due to an invalid token, redirect to login localStorage.removeItem("auth_token"); localStorage.removeItem("refresh_token"); localStorage.removeItem("user_data"); window.location.href = "/auth.html"; } } async loadUserInfo() { try { const response = await this.apiCall("/api/profile"); this.currentUser = response.user; document.getElementById('userName').textContent = this.currentUser.username; } catch (error) { console.error('Failed to load user info:', error); throw error; } } async loadInitialData() { this.showLoading(true); try { await Promise.all([ this.loadClients(), this.loadServerStatus(), this.loadProtocols(), this.loadServerStatistics() ]); this.updateOverviewStats(); this.addActivity('Dashboard loaded successfully'); } catch (error) { console.error('Failed to load initial data:', error); this.showAlert('error', 'Failed to load data', error.message); } finally { this.showLoading(false); } } async loadClients() { try { const response = await this.apiCall('/api/vpn-clients'); this.clients = response.clients || []; this.renderClientsTable(); } catch (error) { console.error('Failed to load clients:', error); this.clients = []; } } async loadServerStatus() { try { const response = await this.apiCall('/api/server/status'); this.serverStatus = response.status || {}; this.serverInfo = response.server_info || {}; this.renderServerStatus(); } catch (error) { console.error('Failed to load server status:', error); this.serverStatus = {}; } } async loadProtocols() { try { const response = await fetch('/api/server/protocols'); const data = await response.json(); this.protocols = data.protocols || []; this.renderProtocols(); } catch (error) { console.error('Failed to load protocols:', error); this.protocols = []; } } async loadServerStatistics() { try { const response = await this.apiCall('/api/server/statistics'); this.statistics = response.statistics || {}; } catch (error) { console.error('Failed to load server statistics:', error); this.statistics = {}; } } updateOverviewStats() { const totalClients = this.clients.length; const runningServers = Object.values(this.serverStatus).filter(s => s.running).length; const serverLocation = this.serverInfo?.server_ip || 'Unknown'; document.getElementById('totalClients').textContent = totalClients; document.getElementById('runningServers').textContent = runningServers; document.getElementById('serverLocation').textContent = serverLocation; } renderClientsTable() { const tbody = document.getElementById('clientsTableBody'); if (this.clients.length === 0) { tbody.innerHTML = ` No VPN clients found. Create your first client to get started. `; return; } tbody.innerHTML = this.clients.map(client => ` ${this.escapeHtml(client.client_name)} ${client.description ? `
${this.escapeHtml(client.description)}` : ''} ${client.protocol.toUpperCase()} ${client.status} ${this.formatDate(client.created_at)} ${client.last_connected ? this.formatDate(client.last_connected) : 'Never'}
`).join(''); } renderServerStatus() { const protocols = ['openvpn', 'ikev2', 'wireguard']; protocols.forEach(protocol => { const status = this.serverStatus[protocol] || { running: false, client_count: 0, port: 0 }; const statusElement = document.getElementById(`${protocol}Status`); const clientsElement = document.getElementById(`${protocol}Clients`); const portElement = document.getElementById(`${protocol}Port`); if (statusElement) { statusElement.className = `server-status ${status.running ? 'online' : 'offline'}`; statusElement.innerHTML = ` ${status.running ? 'Online' : 'Offline'} `; } if (clientsElement) { clientsElement.textContent = status.client_count || 0; } if (portElement) { portElement.textContent = status.port || 0; } }); } renderProtocols() { const container = document.getElementById('protocolsGrid'); container.innerHTML = this.protocols.map(protocol => `

${protocol.name}

${protocol.description}

Port: ${protocol.port}
Transport: ${protocol.transport}

Features:

`).join(''); } setupEventListeners() { // Close modals when clicking outside document.addEventListener('click', (e) => { if (e.target.classList.contains('modal')) { this.closeModal(e.target.id); } }); // Close user dropdown when clicking outside document.addEventListener('click', (e) => { if (!e.target.closest('.user-menu')) { document.getElementById('userDropdown').classList.remove('show'); } }); } startAutoRefresh() { // Refresh server status every 30 seconds setInterval(() => { this.loadServerStatus(); }, 30000); // Refresh clients every 60 seconds setInterval(() => { this.loadClients(); }, 60000); } // API Helper async apiCall(endpoint, options = {}) { const defaultOptions = { headers: { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json' } }; const response = await fetch(endpoint, { ...defaultOptions, ...options }); if (response.status === 401) { localStorage.removeItem('auth_token'); window.location.href = '/auth.html'; return; } if (!response.ok) { const error = await response.json().catch(() => ({ error: 'Network error' })); throw new Error(error.error || 'Request failed'); } return await response.json(); } // Navigation showSection(sectionId) { // Hide all sections document.querySelectorAll('.content-section').forEach(section => { section.classList.remove('active'); }); // Remove active class from nav items document.querySelectorAll('.nav-item').forEach(item => { item.classList.remove('active'); }); // Show selected section document.getElementById(sectionId).classList.add('active'); // Add active class to nav item document.querySelector(`[data-section="${sectionId}"]`).classList.add('active'); // Load section-specific data this.loadSectionData(sectionId); } async loadSectionData(sectionId) { switch (sectionId) { case 'server-status': await this.loadServerStatus(); break; case 'vpn-clients': await this.loadClients(); break; case 'protocols': await this.loadProtocols(); break; } } // Client Management showCreateClientModal() { document.getElementById('createClientModal').classList.add('show'); document.getElementById('createClientForm').reset(); } createClientWithProtocol(protocol) { this.showCreateClientModal(); document.getElementById('clientProtocol').value = protocol; } async createVPNClient(event) { event.preventDefault(); const formData = new FormData(event.target); const clientData = { client_name: formData.get('clientName'), protocol: formData.get('protocol'), description: formData.get('description') }; try { this.showLoading(true); const response = await this.apiCall('/api/vpn-clients', { method: 'POST', body: JSON.stringify(clientData) }); this.showAlert('success', 'Client Created', 'VPN client created successfully'); this.closeModal('createClientModal'); await this.loadClients(); this.addActivity(`Created VPN client: ${clientData.client_name}`); } catch (error) { console.error('Failed to create client:', error); this.showAlert('error', 'Creation Failed', error.message); } finally { this.showLoading(false); } } async downloadClientConfig(clientId) { try { const response = await fetch(`/api/vpn-clients/${clientId}/download`, { headers: { 'Authorization': `Bearer ${this.token}` } }); if (!response.ok) { throw new Error('Download failed'); } const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `vpn-client-${clientId}.zip`; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); this.addActivity(`Downloaded configuration for client ${clientId}`); } catch (error) { console.error('Download failed:', error); this.showAlert('error', 'Download Failed', error.message); } } async revokeClient(clientId) { if (!confirm('Are you sure you want to revoke this client? This action cannot be undone.')) { return; } try { await this.apiCall(`/api/vpn-clients/${clientId}/revoke`, { method: 'POST' }); this.showAlert('success', 'Client Revoked', 'Client access has been revoked'); await this.loadClients(); this.addActivity(`Revoked client ${clientId}`); } catch (error) { console.error('Failed to revoke client:', error); this.showAlert('error', 'Revocation Failed', error.message); } } // Server Management async startServer(protocol) { try { this.showLoading(true); const response = await this.apiCall(`/api/server/start/${protocol}`, { method: 'POST' }); this.showAlert('success', 'Server Started', response.message); await this.loadServerStatus(); this.addActivity(`Started ${protocol.toUpperCase()} server`); } catch (error) { console.error('Failed to start server:', error); this.showAlert('error', 'Start Failed', error.message); } finally { this.showLoading(false); } } async stopServer(protocol) { if (!confirm(`Are you sure you want to stop the ${protocol.toUpperCase()} server?`)) { return; } try { this.showLoading(true); const response = await this.apiCall(`/api/server/stop/${protocol}`, { method: 'POST' }); this.showAlert('success', 'Server Stopped', response.message); await this.loadServerStatus(); this.addActivity(`Stopped ${protocol.toUpperCase()} server`); } catch (error) { console.error('Failed to stop server:', error); this.showAlert('error', 'Stop Failed', error.message); } finally { this.showLoading(false); } } async restartServer(protocol) { if (!confirm(`Are you sure you want to restart the ${protocol.toUpperCase()} server?`)) { return; } try { this.showLoading(true); const response = await this.apiCall(`/api/server/restart/${protocol}`, { method: 'POST' }); this.showAlert('success', 'Server Restarted', response.message); await this.loadServerStatus(); this.addActivity(`Restarted ${protocol.toUpperCase()} server`); } catch (error) { console.error('Failed to restart server:', error); this.showAlert('error', 'Restart Failed', error.message); } finally { this.showLoading(false); } } async refreshServerStatus() { try { this.showLoading(true); await this.loadServerStatus(); this.showAlert('success', 'Status Refreshed', 'Server status updated'); } catch (error) { this.showAlert('error', 'Refresh Failed', error.message); } finally { this.showLoading(false); } } async loadServerLogs() { const protocol = document.getElementById('logProtocol').value; try { const response = await this.apiCall(`/api/server/logs/${protocol}?lines=100`); const logsContainer = document.getElementById('logsContainer'); if (response.logs && response.logs.length > 0) { logsContainer.innerHTML = `
${response.logs.join('\\n')}
`; } else { logsContainer.innerHTML = `
No logs available for ${protocol.toUpperCase()}
`; } // Scroll to bottom logsContainer.scrollTop = logsContainer.scrollHeight; } catch (error) { console.error('Failed to load logs:', error); this.showAlert('error', 'Logs Failed', error.message); } } // Filtering filterClients() { const protocolFilter = document.getElementById('protocolFilter').value; const statusFilter = document.getElementById('statusFilter').value; const searchFilter = document.getElementById('searchFilter').value.toLowerCase(); let filteredClients = this.clients; if (protocolFilter) { filteredClients = filteredClients.filter(client => client.protocol === protocolFilter); } if (statusFilter) { filteredClients = filteredClients.filter(client => client.status === statusFilter); } if (searchFilter) { filteredClients = filteredClients.filter(client => client.client_name.toLowerCase().includes(searchFilter) || (client.description && client.description.toLowerCase().includes(searchFilter)) ); } // Temporarily store original clients and render filtered const originalClients = this.clients; this.clients = filteredClients; this.renderClientsTable(); this.clients = originalClients; } // UI Helpers showModal(modalId) { document.getElementById(modalId).classList.add('show'); } closeModal(modalId) { document.getElementById(modalId).classList.remove('show'); } showLoading(show) { const overlay = document.getElementById('loadingOverlay'); if (show) { overlay.classList.add('show'); } else { overlay.classList.remove('show'); } } showAlert(type, title, message) { const container = document.getElementById('alertContainer'); const alertId = 'alert-' + Date.now(); const alertHtml = `
${title}
${message}
`; container.insertAdjacentHTML('beforeend', alertHtml); // Auto-remove after 5 seconds setTimeout(() => { this.closeAlert(alertId); }, 5000); } closeAlert(alertId) { const alert = document.getElementById(alertId); if (alert) { alert.remove(); } } addActivity(message) { const activityList = document.getElementById('activityList'); const activityHtml = `
${this.escapeHtml(message)}
`; activityList.insertAdjacentHTML('afterbegin', activityHtml); // Keep only last 10 activities const activities = activityList.querySelectorAll('.activity-item'); if (activities.length > 10) { activities[activities.length - 1].remove(); } } // Utility functions escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } formatDate(dateString) { if (!dateString) return 'Never'; const date = new Date(dateString); const now = new Date(); const diff = now - date; if (diff < 60000) return 'Just now'; if (diff < 3600000) return `${Math.floor(diff / 60000)} minutes ago`; if (diff < 86400000) return `${Math.floor(diff / 3600000)} hours ago`; return date.toLocaleDateString(); } // User menu toggleUserMenu() { document.getElementById('userDropdown').classList.toggle('show'); } showProfile() { this.showAlert('info', 'Profile', 'Profile management coming soon'); } showSettings() { this.showAlert('info', 'Settings', 'Settings management coming soon'); } logout() { localStorage.removeItem('auth_token'); window.location.href = '/auth.html'; } } // Global functions for onclick handlers let dashboard; function showSection(sectionId) { dashboard.showSection(sectionId); } function showCreateClientModal() { dashboard.showCreateClientModal(); } function createVPNClient(event) { dashboard.createVPNClient(event); } function closeModal(modalId) { dashboard.closeModal(modalId); } function toggleUserMenu() { dashboard.toggleUserMenu(); } function showProfile() { dashboard.showProfile(); } function showSettings() { dashboard.showSettings(); } function logout() { dashboard.logout(); } function startServer(protocol) { dashboard.startServer(protocol); } function stopServer(protocol) { dashboard.stopServer(protocol); } function restartServer(protocol) { dashboard.restartServer(protocol); } function refreshServerStatus() { dashboard.refreshServerStatus(); } function loadServerLogs() { dashboard.loadServerLogs(); } function filterClients() { dashboard.filterClients(); } // Initialize dashboard when page loads document.addEventListener('DOMContentLoaded', () => { dashboard = new VPNDashboard(); });