/** * Virtual ISP Stack Frontend Application * Native JavaScript implementation for managing the Virtual ISP Stack */ class VirtualISPApp { constructor() { this.apiBase = '/api'; this.currentSection = 'dashboard'; this.refreshInterval = null; this.charts = {}; this.init(); } async init() { this.setupEventListeners(); this.setupNavigation(); this.setupCharts(); await this.loadInitialData(); this.startAutoRefresh(); // Hide loading overlay this.hideLoading(); console.log('Virtual ISP Stack App initialized'); } setupEventListeners() { // Navigation document.querySelectorAll('.nav-item').forEach(item => { item.addEventListener('click', (e) => { const section = e.currentTarget.dataset.section; this.navigateToSection(section); }); }); // Tab buttons document.querySelectorAll('.tab-btn').forEach(btn => { btn.addEventListener('click', (e) => { const tab = e.currentTarget.dataset.tab; this.switchTab(tab); }); }); // Modal close buttons document.querySelectorAll('.close').forEach(btn => { btn.addEventListener('click', (e) => { const modal = e.currentTarget.closest('.modal'); this.closeModal(modal.id); }); }); // Click outside modal to close document.querySelectorAll('.modal').forEach(modal => { modal.addEventListener('click', (e) => { if (e.target === modal) { this.closeModal(modal.id); } }); }); // Form submissions document.getElementById('addRuleForm')?.addEventListener('submit', (e) => { e.preventDefault(); this.addFirewallRule(); }); } setupNavigation() { // Set initial active section this.navigateToSection('dashboard'); } navigateToSection(section) { // Update navigation document.querySelectorAll('.nav-item').forEach(item => { item.classList.remove('active'); }); document.querySelector(`[data-section="${section}"]`).classList.add('active'); // Update content document.querySelectorAll('.content-section').forEach(sec => { sec.classList.remove('active'); }); document.getElementById(section).classList.add('active'); this.currentSection = section; // Load section-specific data this.loadSectionData(section); } switchTab(tab) { const container = event.target.closest('.router-tabs'); // Update tab buttons container.querySelectorAll('.tab-btn').forEach(btn => { btn.classList.remove('active'); }); event.target.classList.add('active'); // Update tab content container.querySelectorAll('.tab-pane').forEach(pane => { pane.classList.remove('active'); }); container.querySelector(`#${tab}`).classList.add('active'); // Load tab-specific data this.loadTabData(tab); } async loadInitialData() { try { await Promise.all([ this.loadSystemStatus(), this.loadDashboardData(), this.loadConfiguration() ]); } catch (error) { console.error('Error loading initial data:', error); this.showToast('Error loading initial data', 'error'); } } async loadSectionData(section) { try { switch (section) { case 'dashboard': await this.loadDashboardData(); break; case 'dhcp': await this.loadDHCPData(); break; case 'nat': await this.loadNATData(); break; case 'firewall': await this.loadFirewallData(); break; case 'router': await this.loadRouterData(); break; case 'bridge': await this.loadBridgeData(); break; case 'sessions': await this.loadSessionsData(); break; case 'logs': await this.loadLogsData(); break; case 'vpn': await this.loadVPNData(); break; case 'config': await this.loadConfiguration(); break; } } catch (error) { console.error(`Error loading ${section} data:`, error); this.showToast(`Error loading ${section} data`, 'error'); } } async loadTabData(tab) { try { switch (tab) { case 'routes': await this.loadRoutingTable(); break; case 'interfaces': await this.loadInterfaces(); break; case 'arp': await this.loadARPTable(); break; } } catch (error) { console.error(`Error loading ${tab} data:`, error); } } // API Methods async apiCall(endpoint, options = {}) { const url = `${this.apiBase}${endpoint}`; const defaultOptions = { headers: { 'Content-Type': 'application/json', }, }; const response = await fetch(url, { ...defaultOptions, ...options }); if (!response.ok) { throw new Error(`API call failed: ${response.status} ${response.statusText}`); } return await response.json(); } // System Status async loadSystemStatus() { try { const response = await this.apiCall('/status'); this.updateSystemStatus(response.system_status); } catch (error) { console.error('Error loading system status:', error); this.updateSystemStatusOffline(); } } updateSystemStatus(status) { const indicator = document.getElementById('systemStatus'); const components = status.components; // Update header status const allOnline = Object.values(components).every(c => c === true); indicator.className = `status-indicator ${allOnline ? 'online' : 'offline'}`; indicator.querySelector('span').textContent = allOnline ? 'All Systems Online' : 'System Issues'; // Update component status this.updateComponentStatus(components); } updateSystemStatusOffline() { const indicator = document.getElementById('systemStatus'); indicator.className = 'status-indicator offline'; indicator.querySelector('span').textContent = 'System Offline'; } updateComponentStatus(components) { const container = document.getElementById('componentStatus'); container.innerHTML = ''; Object.entries(components).forEach(([name, status]) => { const item = document.createElement('div'); item.className = `component-item ${status ? 'online' : 'offline'}`; item.innerHTML = ` ${this.formatComponentName(name)} ${status ? 'Online' : 'Offline'} `; container.appendChild(item); }); } formatComponentName(name) { return name.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); } // Dashboard Data async loadDashboardData() { try { const [statusResponse, statsResponse] = await Promise.all([ this.apiCall('/status'), this.apiCall('/stats') ]); this.updateDashboardStats(statusResponse.system_status.stats); this.updateCharts(statsResponse.stats); } catch (error) { console.error('Error loading dashboard data:', error); } } updateDashboardStats(stats) { document.getElementById('dhcpLeaseCount').textContent = stats.dhcp_leases || 0; document.getElementById('natSessionCount').textContent = stats.nat_sessions || 0; document.getElementById('firewallRuleCount').textContent = stats.firewall_rules || 0; document.getElementById('bridgeClientCount').textContent = stats.bridge_clients || 0; } // DHCP Data async loadDHCPData() { try { const response = await this.apiCall('/dhcp/leases'); this.updateDHCPTable(response.leases); } catch (error) { console.error('Error loading DHCP data:', error); this.updateDHCPTable([]); } } updateDHCPTable(leases) { const tbody = document.getElementById('dhcpTableBody'); tbody.innerHTML = ''; leases.forEach(lease => { const row = document.createElement('tr'); const remaining = Math.max(0, lease.lease_time - (Date.now() / 1000 - lease.lease_start)); row.innerHTML = ` ${lease.mac_address} ${lease.ip_address} ${this.formatDuration(lease.lease_time)} ${this.formatDuration(remaining)} ${lease.state} `; tbody.appendChild(row); }); } async releaseDHCPLease(macAddress) { try { await this.apiCall(`/dhcp/leases/${macAddress}`, { method: 'DELETE' }); this.showToast('DHCP lease released successfully', 'success'); await this.loadDHCPData(); } catch (error) { console.error('Error releasing DHCP lease:', error); this.showToast('Error releasing DHCP lease', 'error'); } } // NAT Data async loadNATData() { try { const [sessionsResponse, statsResponse] = await Promise.all([ this.apiCall('/nat/sessions'), this.apiCall('/nat/stats') ]); this.updateNATStats(statsResponse.stats); this.updateNATTable(sessionsResponse.sessions); } catch (error) { console.error('Error loading NAT data:', error); this.updateNATStats({}); this.updateNATTable([]); } } updateNATStats(stats) { document.getElementById('natActiveSessions').textContent = stats.active_sessions || 0; document.getElementById('natPortUtilization').textContent = `${Math.round((stats.ports_used / stats.total_ports) * 100) || 0}%`; document.getElementById('natBytesTranslated').textContent = this.formatBytes(stats.bytes_translated || 0); } updateNATTable(sessions) { const tbody = document.getElementById('natTableBody'); tbody.innerHTML = ''; sessions.forEach(session => { const row = document.createElement('tr'); row.innerHTML = ` ${session.virtual_ip}:${session.virtual_port} ${session.real_ip}:${session.real_port} ${session.host_ip}:${session.host_port} ${session.protocol} ${this.formatDuration(session.duration)} ${this.formatBytes(session.bytes_in)} / ${this.formatBytes(session.bytes_out)} `; tbody.appendChild(row); }); } // Firewall Data async loadFirewallData() { try { const [rulesResponse, logsResponse, statsResponse] = await Promise.all([ this.apiCall('/firewall/rules'), this.apiCall('/firewall/logs?limit=50'), this.apiCall('/firewall/stats') ]); this.updateFirewallTable(rulesResponse.rules); } catch (error) { console.error('Error loading firewall data:', error); this.updateFirewallTable([]); } } updateFirewallTable(rules) { const tbody = document.getElementById('firewallTableBody'); tbody.innerHTML = ''; rules.forEach(rule => { const row = document.createElement('tr'); row.innerHTML = ` ${rule.priority} ${rule.rule_id} ${rule.action} ${rule.direction} ${rule.source_ip || 'Any'}${rule.source_port ? ':' + rule.source_port : ''} ${rule.dest_ip || 'Any'}${rule.dest_port ? ':' + rule.dest_port : ''} ${rule.protocol || 'Any'} ${rule.hit_count || 0} ${rule.enabled ? 'Enabled' : 'Disabled'} `; tbody.appendChild(row); }); } async deleteFirewallRule(ruleId) { try { await this.apiCall(`/firewall/rules/${ruleId}`, { method: 'DELETE' }); this.showToast('Firewall rule deleted successfully', 'success'); await this.loadFirewallData(); } catch (error) { console.error('Error deleting firewall rule:', error); this.showToast('Error deleting firewall rule', 'error'); } } // Router Data async loadRouterData() { await Promise.all([ this.loadRoutingTable(), this.loadInterfaces(), this.loadARPTable() ]); } async loadRoutingTable() { try { const response = await this.apiCall('/router/routes'); this.updateRoutingTable(response.routes); } catch (error) { console.error('Error loading routing table:', error); this.updateRoutingTable([]); } } updateRoutingTable(routes) { const tbody = document.getElementById('routesTableBody'); tbody.innerHTML = ''; routes.forEach(route => { const row = document.createElement('tr'); row.innerHTML = ` ${route.destination} ${route.gateway || 'Direct'} ${route.interface} ${route.metric} ${route.type} ${route.use_count || 0} ${route.last_used ? new Date(route.last_used * 1000).toLocaleString() : 'Never'} `; tbody.appendChild(row); }); } async loadInterfaces() { try { const response = await this.apiCall('/router/interfaces'); this.updateInterfacesTable(response.interfaces); } catch (error) { console.error('Error loading interfaces:', error); this.updateInterfacesTable([]); } } updateInterfacesTable(interfaces) { const tbody = document.getElementById('interfacesTableBody'); tbody.innerHTML = ''; interfaces.forEach(iface => { const row = document.createElement('tr'); row.innerHTML = ` ${iface.name} ${iface.ip_address} ${iface.network} ${iface.mtu} ${iface.enabled ? 'Up' : 'Down'} `; tbody.appendChild(row); }); } async loadARPTable() { try { const response = await this.apiCall('/router/arp'); this.updateARPTable(response.arp_table); } catch (error) { console.error('Error loading ARP table:', error); this.updateARPTable([]); } } updateARPTable(arpEntries) { const tbody = document.getElementById('arpTableBody'); tbody.innerHTML = ''; arpEntries.forEach(entry => { const row = document.createElement('tr'); row.innerHTML = ` ${entry.ip_address} ${entry.mac_address} `; tbody.appendChild(row); }); } // Bridge Data async loadBridgeData() { try { const [clientsResponse, statsResponse] = await Promise.all([ this.apiCall('/bridge/clients'), this.apiCall('/bridge/stats') ]); this.updateBridgeTable(clientsResponse.clients); } catch (error) { console.error('Error loading bridge data:', error); this.updateBridgeTable({}); } } updateBridgeTable(clients) { const tbody = document.getElementById('bridgeTableBody'); tbody.innerHTML = ''; Object.values(clients).forEach(client => { const row = document.createElement('tr'); row.innerHTML = ` ${client.client_id} ${client.bridge_type} ${client.remote_address}:${client.remote_port} ${new Date(client.connected_time * 1000).toLocaleString()} ${client.packets_received} / ${client.packets_sent} ${this.formatBytes(client.bytes_received)} / ${this.formatBytes(client.bytes_sent)} `; tbody.appendChild(row); }); } // Sessions Data async loadSessionsData() { try { const [sessionsResponse, summaryResponse] = await Promise.all([ this.apiCall('/sessions?limit=100'), this.apiCall('/sessions/summary') ]); this.updateSessionSummary(summaryResponse.summary); this.updateSessionsTable(sessionsResponse.sessions); } catch (error) { console.error('Error loading sessions data:', error); this.updateSessionSummary({}); this.updateSessionsTable([]); } } updateSessionSummary(summary) { const container = document.getElementById('sessionSummary'); container.innerHTML = `

Session Summary

Total Sessions ${summary.total_sessions || 0}
Active (Last Hour) ${summary.active_sessions_by_age?.last_hour || 0}
Active (Last Day) ${summary.active_sessions_by_age?.last_day || 0}
`; } updateSessionsTable(sessions) { const tbody = document.getElementById('sessionsTableBody'); tbody.innerHTML = ''; sessions.forEach(session => { const row = document.createElement('tr'); row.innerHTML = ` ${session.session_id.substring(0, 8)}... ${session.session_type} ${session.state} ${session.virtual_ip || '-'}${session.virtual_port ? ':' + session.virtual_port : ''} ${session.real_ip || '-'}${session.real_port ? ':' + session.real_port : ''} ${session.protocol || '-'} ${this.formatDuration(session.duration)} ${this.formatDuration(session.idle_time)} ${this.formatBytes(session.metrics.total_bytes)} `; tbody.appendChild(row); }); } // Logs Data async loadLogsData() { try { const response = await this.apiCall('/logs?limit=100'); this.updateLogsContainer(response.logs); } catch (error) { console.error('Error loading logs data:', error); this.updateLogsContainer([]); } } updateLogsContainer(logs) { const container = document.getElementById('logContainer'); container.innerHTML = ''; logs.forEach(log => { const entry = document.createElement('div'); entry.className = 'log-entry'; entry.innerHTML = `
${log.level}
${new Date(log.timestamp * 1000).toLocaleString()}
${log.message}
${log.metadata && Object.keys(log.metadata).length > 0 ? `
${JSON.stringify(log.metadata)}
` : ''}
`; container.appendChild(entry); }); } // Configuration async loadConfiguration() { try { const response = await this.apiCall('/config'); this.updateConfigurationForms(response.config); } catch (error) { console.error('Error loading configuration:', error); } } updateConfigurationForms(config) { // DHCP Configuration const dhcpConfig = document.getElementById('dhcpConfig'); dhcpConfig.innerHTML = `
`; // NAT Configuration const natConfig = document.getElementById('natConfig'); natConfig.innerHTML = `
`; // Firewall Configuration const firewallConfig = document.getElementById('firewallConfig'); firewallConfig.innerHTML = `
`; } // Charts setupCharts() { // Traffic Chart const trafficCtx = document.getElementById('trafficChart'); if (trafficCtx) { this.charts.traffic = new Chart(trafficCtx, { type: 'line', data: { labels: [], datasets: [{ label: 'Bytes In', data: [], borderColor: '#4facfe', backgroundColor: 'rgba(79, 172, 254, 0.1)', tension: 0.4 }, { label: 'Bytes Out', data: [], borderColor: '#00f2fe', backgroundColor: 'rgba(0, 242, 254, 0.1)', tension: 0.4 }] }, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true } } } }); } // Connection Chart const connectionCtx = document.getElementById('connectionChart'); if (connectionCtx) { this.charts.connection = new Chart(connectionCtx, { type: 'doughnut', data: { labels: ['DHCP', 'NAT', 'TCP', 'Bridge'], datasets: [{ data: [0, 0, 0, 0], backgroundColor: [ '#4facfe', '#00f2fe', '#51cf66', '#ff6b6b' ] }] }, options: { responsive: true, maintainAspectRatio: false } }); } } updateCharts(stats) { // Update traffic chart with sample data if (this.charts.traffic) { const now = new Date(); const labels = this.charts.traffic.data.labels; const bytesIn = this.charts.traffic.data.datasets[0].data; const bytesOut = this.charts.traffic.data.datasets[1].data; labels.push(now.toLocaleTimeString()); bytesIn.push(Math.random() * 1000000); bytesOut.push(Math.random() * 800000); // Keep only last 10 data points if (labels.length > 10) { labels.shift(); bytesIn.shift(); bytesOut.shift(); } this.charts.traffic.update(); } // Update connection chart if (this.charts.connection && stats) { this.charts.connection.data.datasets[0].data = [ Object.keys(stats.dhcp || {}).length, stats.nat?.active_sessions || 0, Object.keys(stats.tcp || {}).length, stats.bridge?.active_clients || 0 ]; this.charts.connection.update(); } } // Modal Management showModal(modalId) { document.getElementById(modalId).style.display = 'block'; } closeModal(modalId) { document.getElementById(modalId).style.display = 'none'; } showAddRuleModal() { this.showModal('addRuleModal'); } async addFirewallRule() { try { const form = document.getElementById('addRuleForm'); const formData = new FormData(form); const ruleData = Object.fromEntries(formData.entries()); await this.apiCall('/firewall/rules', { method: 'POST', body: JSON.stringify(ruleData) }); this.showToast('Firewall rule added successfully', 'success'); this.closeModal('addRuleModal'); form.reset(); await this.loadFirewallData(); } catch (error) { console.error('Error adding firewall rule:', error); this.showToast('Error adding firewall rule', 'error'); } } // Configuration Management async saveConfiguration() { try { const configData = this.collectConfigurationData(); await this.apiCall('/config', { method: 'POST', body: JSON.stringify(configData) }); this.showToast('Configuration saved successfully', 'success'); } catch (error) { console.error('Error saving configuration:', error); this.showToast('Error saving configuration', 'error'); } } collectConfigurationData() { const config = {}; // Collect DHCP config const dhcpInputs = document.querySelectorAll('#dhcpConfig input, #dhcpConfig select'); config.dhcp = {}; dhcpInputs.forEach(input => { const key = input.name.replace('dhcp_', ''); config.dhcp[key] = input.type === 'number' ? parseInt(input.value) : input.value; }); // Collect NAT config const natInputs = document.querySelectorAll('#natConfig input, #natConfig select'); config.nat = {}; natInputs.forEach(input => { const key = input.name.replace('nat_', ''); config.nat[key] = input.type === 'number' ? parseInt(input.value) : input.value; }); // Collect Firewall config const firewallInputs = document.querySelectorAll('#firewallConfig input, #firewallConfig select'); config.firewall = {}; firewallInputs.forEach(input => { const key = input.name.replace('firewall_', ''); config.firewall[key] = input.type === 'checkbox' ? input.checked : input.value; }); return config; } // Utility Functions 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]; } formatDuration(seconds) { if (seconds < 60) return `${Math.round(seconds)}s`; if (seconds < 3600) return `${Math.round(seconds / 60)}m`; if (seconds < 86400) return `${Math.round(seconds / 3600)}h`; return `${Math.round(seconds / 86400)}d`; } // Loading Management showLoading() { document.getElementById('loadingOverlay').style.display = 'block'; } hideLoading() { document.getElementById('loadingOverlay').style.display = 'none'; } // Toast Notifications showToast(message, type = 'info') { const container = document.getElementById('toastContainer'); const toast = document.createElement('div'); toast.className = `toast ${type}`; toast.textContent = message; container.appendChild(toast); // Auto remove after 5 seconds setTimeout(() => { toast.remove(); }, 5000); } // Auto Refresh startAutoRefresh() { this.refreshInterval = setInterval(async () => { if (this.currentSection === 'dashboard') { await this.loadSystemStatus(); await this.loadDashboardData(); } }, 30000); // Refresh every 30 seconds } stopAutoRefresh() { if (this.refreshInterval) { clearInterval(this.refreshInterval); this.refreshInterval = null; } } // Manual Refresh Functions async refreshData() { this.showLoading(); try { await this.loadSectionData(this.currentSection); this.showToast('Data refreshed successfully', 'success'); } catch (error) { this.showToast('Error refreshing data', 'error'); } finally { this.hideLoading(); } } async refreshDHCPLeases() { await this.loadDHCPData(); this.showToast('DHCP leases refreshed', 'info'); } async refreshNATSessions() { await this.loadNATData(); this.showToast('NAT sessions refreshed', 'info'); } async refreshFirewallRules() { await this.loadFirewallData(); this.showToast('Firewall rules refreshed', 'info'); } async refreshBridgeClients() { await this.loadBridgeData(); this.showToast('Bridge clients refreshed', 'info'); } async refreshSessions() { await this.loadSessionsData(); this.showToast('Sessions refreshed', 'info'); } async refreshLogs() { await this.loadLogsData(); this.showToast('Logs refreshed', 'info'); } // Filter Functions filterSessions() { // Implementation for session filtering const filter = document.getElementById('sessionTypeFilter').value; // Apply filter logic here } filterLogs() { // Implementation for log filtering const levelFilter = document.getElementById('logLevelFilter').value; const categoryFilter = document.getElementById('logCategoryFilter').value; // Apply filter logic here } searchLogs() { // Implementation for log searching const searchTerm = document.getElementById('logSearchInput').value; // Apply search logic here } clearLogs() { if (confirm('Are you sure you want to clear all logs?')) { document.getElementById('logContainer').innerHTML = ''; this.showToast('Logs cleared', 'info'); } } resetConfiguration() { if (confirm('Are you sure you want to reset configuration to defaults?')) { this.loadConfiguration(); this.showToast('Configuration reset to defaults', 'info'); } } } // Global Functions (for onclick handlers) let app; function refreshData() { app.refreshData(); } function showAddRuleModal() { app.showAddRuleModal(); } function closeModal(modalId) { app.closeModal(modalId); } function addFirewallRule() { app.addFirewallRule(); } function saveConfiguration() { app.saveConfiguration(); } function resetConfiguration() { app.resetConfiguration(); } function refreshDHCPLeases() { app.refreshDHCPLeases(); } function refreshNATSessions() { app.refreshNATSessions(); } function refreshFirewallRules() { app.refreshFirewallRules(); } function refreshBridgeClients() { app.refreshBridgeClients(); } function refreshSessions() { app.refreshSessions(); } function refreshLogs() { app.refreshLogs(); } function filterSessions() { app.filterSessions(); } function filterLogs() { app.filterLogs(); } function searchLogs() { app.searchLogs(); } function clearLogs() { app.clearLogs(); } // Initialize App document.addEventListener('DOMContentLoaded', () => { app = new VirtualISPApp(); }); // VPN Management Functions async loadVPNData() { try { await Promise.all([ this.loadVPNStatus(), this.loadVPNClients() ]); } catch (error) { console.error('Error loading VPN data:', error); } } async loadVPNStatus() { try { const response = await this.apiCall('/openvpn/status'); this.updateVPNStatus(response.status); } catch (error) { console.error('Error loading VPN status:', error); this.updateVPNStatusOffline(); } } updateVPNStatus(status) { document.getElementById('vpnServerStatus').textContent = status.is_running ? 'Running' : 'Stopped'; document.getElementById('vpnServerIp').textContent = status.server_ip || '-'; document.getElementById('vpnServerPort').textContent = status.server_port || '-'; document.getElementById('vpnConnectedClients').textContent = status.connected_clients || 0; document.getElementById('vpnUptime').textContent = status.uptime ? this.formatDuration(status.uptime) : '-'; document.getElementById('vpnBytesReceived').textContent = this.formatBytes(status.total_bytes_received || 0); document.getElementById('vpnBytesSent').textContent = this.formatBytes(status.total_bytes_sent || 0); // Update button states const startBtn = document.getElementById('startVpnBtn'); const stopBtn = document.getElementById('stopVpnBtn'); if (status.is_running) { startBtn.disabled = true; stopBtn.disabled = false; startBtn.classList.add('disabled'); stopBtn.classList.remove('disabled'); } else { startBtn.disabled = false; stopBtn.disabled = true; startBtn.classList.remove('disabled'); stopBtn.classList.add('disabled'); } } updateVPNStatusOffline() { document.getElementById('vpnServerStatus').textContent = 'Unknown'; document.getElementById('vpnServerIp').textContent = '-'; document.getElementById('vpnServerPort').textContent = '-'; document.getElementById('vpnConnectedClients').textContent = '0'; document.getElementById('vpnUptime').textContent = '-'; document.getElementById('vpnBytesReceived').textContent = '0'; document.getElementById('vpnBytesSent').textContent = '0'; // Enable both buttons when status is unknown const startBtn = document.getElementById('startVpnBtn'); const stopBtn = document.getElementById('stopVpnBtn'); startBtn.disabled = false; stopBtn.disabled = false; startBtn.classList.remove('disabled'); stopBtn.classList.remove('disabled'); } async loadVPNClients() { try { const response = await this.apiCall('/openvpn/clients'); this.updateVPNClientsTable(response.clients); } catch (error) { console.error('Error loading VPN clients:', error); this.updateVPNClientsTable([]); } } updateVPNClientsTable(clients) { const tbody = document.getElementById('vpnClientsTableBody'); tbody.innerHTML = ''; clients.forEach(client => { const row = document.createElement('tr'); const connectedSince = new Date(client.connected_at * 1000).toLocaleString(); row.innerHTML = ` ${client.client_id} ${client.common_name} ${client.ip_address} ${connectedSince} ${this.formatBytes(client.bytes_received)} ${this.formatBytes(client.bytes_sent)} ${client.status} `; tbody.appendChild(row); }); } async startVpnServer() { try { this.showLoading(); const response = await this.apiCall('/openvpn/start', { method: 'POST' }); if (response.status === 'success') { this.showToast('VPN server started successfully', 'success'); await this.loadVPNStatus(); } else { this.showToast('Failed to start VPN server: ' + response.message, 'error'); } } catch (error) { console.error('Error starting VPN server:', error); this.showToast('Error starting VPN server', 'error'); } finally { this.hideLoading(); } } async stopVpnServer() { try { this.showLoading(); const response = await this.apiCall('/openvpn/stop', { method: 'POST' }); if (response.status === 'success') { this.showToast('VPN server stopped successfully', 'success'); await this.loadVPNStatus(); await this.loadVPNClients(); // Refresh clients list } else { this.showToast('Failed to stop VPN server: ' + response.message, 'error'); } } catch (error) { console.error('Error stopping VPN server:', error); this.showToast('Error stopping VPN server', 'error'); } finally { this.hideLoading(); } } async disconnectVPNClient(clientId) { try { const response = await this.apiCall(`/openvpn/clients/${clientId}`, { method: 'DELETE' }); if (response.status === 'success') { this.showToast('VPN client disconnected successfully', 'success'); await this.loadVPNClients(); await this.loadVPNStatus(); // Update client count } else { this.showToast('Failed to disconnect VPN client: ' + response.message, 'error'); } } catch (error) { console.error('Error disconnecting VPN client:', error); this.showToast('Error disconnecting VPN client', 'error'); } } async generateClientConfig() { try { const clientName = document.getElementById('clientName').value; const serverIp = document.getElementById('serverIp').value; if (!clientName || !serverIp) { this.showToast('Please fill in all fields', 'error'); return; } this.showLoading(); const response = await this.apiCall('/openvpn/generate-config', { method: 'POST', body: JSON.stringify({ client_name: clientName, server_ip: serverIp }) }); if (response.status === 'success') { // Create and download the config file const blob = new Blob([response.config], { type: 'text/plain' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${clientName}.ovpn`; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); this.showToast('VPN client configuration generated and downloaded', 'success'); this.closeModal('generateConfigModal'); // Clear form document.getElementById('generateConfigForm').reset(); } else { this.showToast('Failed to generate VPN config: ' + response.message, 'error'); } } catch (error) { console.error('Error generating VPN config:', error); this.showToast('Error generating VPN config', 'error'); } finally { this.hideLoading(); } } refreshVpnStatus() { this.loadVPNStatus(); } refreshVpnClients() { this.loadVPNClients(); } showGenerateConfigModal() { this.showModal('generateConfigModal'); }