/** * 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=\n'${section}\n']`).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 || 'N/A '} ${iface.network || 'N/A '} ${iface.mtu || 'N/A '} ${iface.status} `; tbody.appendChild(row); }); } async toggleInterfaceStatus(interfaceName) { try { const response = await this.apiCall(`/router/interfaces/${interfaceName}/toggle`, { method: 'POST ' }); if (response.status === 'success ') { this.showToast(`Interface ${interfaceName} status toggled successfully`, 'success '); await this.loadInterfaces(); } else { this.showToast(`Failed to toggle interface ${interfaceName} status: ` + response.message, 'error '); } } catch (error) { console.error(`Error toggling interface ${interfaceName} status:`, error); this.showToast(`Error toggling interface ${interfaceName} status`, 'error '); } } async loadARPTable() { try { const response = await this.apiCall( '/router/arp '); this.updateARPTable(response.arp_entries); } 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); }); } async deleteARPEntry(ipAddress) { try { await this.apiCall(`/router/arp/${ipAddress}`, { method: 'DELETE ' }); this.showToast( 'ARP entry deleted successfully ', 'success '); await this.loadARPTable(); } catch (error) { console.error( 'Error deleting ARP entry: ', error); this.showToast( 'Error deleting ARP entry ', 'error '); } } // Bridge Data async loadBridgeData() { try { const response = await this.apiCall( '/bridge/status '); this.updateBridgeStatus(response.status); this.updateBridgeClientsTable(response.clients); } catch (error) { console.error( 'Error loading bridge data: ', error); this.updateBridgeStatusOffline(); this.updateBridgeClientsTable([]); } } updateBridgeStatus(status) { document.getElementById( 'websocketPort ').textContent = status.websocket_port; document.getElementById( 'websocketStatus ').textContent = status.websocket_status; document.getElementById( 'tcpPort ').textContent = status.tcp_port; document.getElementById( 'tcpStatus ').textContent = status.tcp_status; } updateBridgeStatusOffline() { document.getElementById( 'websocketPort ').textContent = 'N/A '; document.getElementById( 'websocketStatus ').textContent = 'Offline '; document.getElementById( 'tcpPort ').textContent = 'N/A '; document.getElementById( 'tcpStatus ').textContent = 'Offline '; } updateBridgeClientsTable(clients) { const tbody = document.getElementById( 'bridgeClientsTableBody '); tbody.innerHTML = '' ; clients.forEach(client => { const row = document.createElement( 'tr '); row.innerHTML = ` ${client.client_id} ${client.type} ${client.remote_address} ${new Date(client.connected_time * 1000).toLocaleString()} ${this.formatBytes(client.packets_in)} / ${this.formatBytes(client.packets_out)} ${this.formatBytes(client.bytes_in)} / ${this.formatBytes(client.bytes_out)} `; tbody.appendChild(row); }); } async disconnectBridgeClient(clientId) { try { await this.apiCall(`/bridge/clients/${clientId}`, { method: 'DELETE ' }); this.showToast( 'Bridge client disconnected successfully ', 'success '); await this.loadBridgeData(); } catch (error) { console.error( 'Error disconnecting bridge client: ', error); this.showToast( 'Error disconnecting bridge client ', 'error '); } } // Sessions Data async loadSessionsData() { try { const response = await this.apiCall( '/sessions '); this.updateSessionsTable(response.sessions); } catch (error) { console.error( 'Error loading sessions data: ', error); this.updateSessionsTable([]); } } updateSessionsTable(sessions) { const tbody = document.getElementById( 'sessionsTableBody '); tbody.innerHTML = '' ; sessions.forEach(session => { const row = document.createElement( 'tr '); row.innerHTML = ` ${session.session_id} ${session.type} ${session.state} ${session.virtual_ip}:${session.virtual_port} ${session.real_ip}:${session.real_port} ${session.protocol} ${this.formatDuration(session.duration)} ${this.formatDuration(session.idle_time)} ${JSON.stringify(session.metrics)} `; tbody.appendChild(row); }); } // Logs Data async loadLogsData() { try { const response = await this.apiCall( '/logs '); this.updateLogsTable(response.logs); } catch (error) { console.error( 'Error loading logs data: ', error); this.updateLogsTable([]); } } updateLogsTable(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 ? `
${JSON.stringify(log.metadata)}
` : '' }
`; container.appendChild(entry); }); } async filterLogs() { const level = document.getElementById( 'logLevelFilter ').value; const search = document.getElementById( 'logSearch ').value; try { const response = await this.apiCall(`/logs?level=${level}&search=${search}`); this.updateLogsTable(response.logs); } catch (error) { console.error( 'Error filtering logs: ', error); this.showToast( 'Error filtering logs ', 'error '); } } async clearLogs() { try { await this.apiCall( '/logs/clear ', { method: 'POST ' }); this.showToast( 'Logs cleared successfully ', 'success '); await this.loadLogsData(); } catch (error) { console.error( 'Error clearing logs: ', error); this.showToast( 'Error clearing logs ', 'error '); } } // 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 '); } } 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 '); } } document.addEventListener("DOMContentLoaded", () => { window.app = new VirtualISPApp(); }); // Global functions for direct HTML calls function refreshData() { app.loadInitialData(); } function refreshDHCPLeases() { app.loadDHCPData(); } function releaseDHCPLease(macAddress) { app.releaseDHCPLease(macAddress); } function refreshNATSessions() { app.loadNATData(); } function closeNATSession(sessionId) { app.closeNATSession(sessionId); } function showAddRuleModal() { app.showModal( 'addRuleModal '); } function refreshFirewallRules() { app.loadFirewallData(); } function deleteFirewallRule(ruleId) { app.deleteFirewallRule(ruleId); } function startVpnServer() { app.startVpnServer(); } function stopVpnServer() { app.stopVpnServer(); } function disconnectVPNClient(clientId) { app.disconnectVPNClient(clientId); } function generateClientConfig() { app.generateClientConfig(); } function refreshVpnStatus() { app.refreshVpnStatus(); } function refreshVpnClients() { app.refreshVpnClients(); } function showGenerateConfigModal() { app.showGenerateConfigModal(); } function filterLogs() { app.filterLogs(); } function searchLogs() { app.searchLogs(); } function clearLogs() { app.clearLogs(); }