TNT / static /app.js
Fred808's picture
Upload 48 files
50d86e3 verified
Raw History Blame Contribute Delete
37.9 kB
/** * 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 '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 = ` <span class="component-name">${this.formatComponentName(name)}</span> <span class="component-status-badge ${status ? 'online' : 'offline'}"> ${status ? 'Online' : 'Offline'} </span> `; 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 = ` <td>${lease.mac_address}</td> <td>${lease.ip_address}</td> <td>${this.formatDuration(lease.lease_time)}</td> <td>${this.formatDuration(remaining)}</td> <td><span class="status-badge status-${lease.state.toLowerCase()}">${lease.state}</span></td> <td> <button class="btn btn-danger btn-sm" onclick="app.releaseDHCPLease('${lease.mac_address}')"> <i class="fas fa-times"></i> Release </button> </td> `; 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 = ` <td>${session.virtual_ip}:${session.virtual_port}</td> <td>${session.real_ip}:${session.real_port}</td> <td>${session.host_ip}:${session.host_port}</td> <td>${session.protocol}</td> <td>${this.formatDuration(session.duration)}</td> <td>${this.formatBytes(session.bytes_in)} / ${this.formatBytes(session.bytes_out)}</td> <td> <button class="btn btn-danger btn-sm" onclick="app.closeNATSession('${session.session_id}')"> <i class="fas fa-times"></i> Close </button> </td> `; 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 = ` <td>${rule.priority}</td> <td>${rule.rule_id}</td> <td><span class="status-badge status-${rule.action.toLowerCase()}">${rule.action}</span></td> <td>${rule.direction}</td> <td>${rule.source_ip || 'Any'}${rule.source_port ? ':' + rule.source_port : ''}</td> <td>${rule.dest_ip || 'Any'}${rule.dest_port ? ':' + rule.dest_port : ''}</td> <td>${rule.protocol || 'Any'}</td> <td>${rule.hit_count || 0}</td> <td><span class="status-badge status-${rule.enabled ? 'active' : 'inactive'}">${rule.enabled ? 'Enabled' : 'Disabled'}</span></td> <td> <button class="btn btn-danger btn-sm" onclick="app.deleteFirewallRule('${rule.rule_id}')"> <i class="fas fa-trash"></i> Delete </button> </td> `; 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 = ` <td>${route.destination}</td> <td>${route.gateway || 'Direct'}</td> <td>${route.interface}</td> <td>${route.metric}</td> <td>${route.type}</td> <td>${route.use_count || 0}</td> <td>${route.last_used ? new Date(route.last_used * 1000).toLocaleString() : 'Never'}</td> `; 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 = ` <td>${iface.name}</td> <td>${iface.ip_address}</td> <td>${iface.network}</td> <td>${iface.mtu}</td> <td><span class="status-badge status-${iface.enabled ? 'active' : 'inactive'}">${iface.enabled ? 'Up' : 'Down'}</span></td> <td> <button class="btn btn-secondary btn-sm"> <i class="fas fa-cog"></i> Configure </button> </td> `; 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 = ` <td>${entry.ip_address}</td> <td>${entry.mac_address}</td> <td> <button class="btn btn-danger btn-sm"> <i class="fas fa-trash"></i> Clear </button> </td> `; 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 = ` <td>${client.client_id}</td> <td>${client.bridge_type}</td> <td>${client.remote_address}:${client.remote_port}</td> <td>${new Date(client.connected_time * 1000).toLocaleString()}</td> <td>${client.packets_received} / ${client.packets_sent}</td> <td>${this.formatBytes(client.bytes_received)} / ${this.formatBytes(client.bytes_sent)}</td> <td> <button class="btn btn-danger btn-sm" onclick="app.disconnectBridgeClient('${client.client_id}')"> <i class="fas fa-times"></i> Disconnect </button> </td> `; 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 = ` <h3>Session Summary</h3> <div class="stat-row"> <div class="stat-item"> <span class="stat-label">Total Sessions</span> <span class="stat-value">${summary.total_sessions || 0}</span> </div> <div class="stat-item"> <span class="stat-label">Active (Last Hour)</span> <span class="stat-value">${summary.active_sessions_by_age?.last_hour || 0}</span> </div> <div class="stat-item"> <span class="stat-label">Active (Last Day)</span> <span class="stat-value">${summary.active_sessions_by_age?.last_day || 0}</span> </div> </div> `; } updateSessionsTable(sessions) { const tbody = document.getElementById('sessionsTableBody'); tbody.innerHTML = ''; sessions.forEach(session => { const row = document.createElement('tr'); row.innerHTML = ` <td>${session.session_id.substring(0, 8)}...</td> <td>${session.session_type}</td> <td><span class="status-badge status-${session.state.toLowerCase()}">${session.state}</span></td> <td>${session.virtual_ip || '-'}${session.virtual_port ? ':' + session.virtual_port : ''}</td> <td>${session.real_ip || '-'}${session.real_port ? ':' + session.real_port : ''}</td> <td>${session.protocol || '-'}</td> <td>${this.formatDuration(session.duration)}</td> <td>${this.formatDuration(session.idle_time)}</td> <td>${this.formatBytes(session.metrics.total_bytes)}</td> `; 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 = ` <div class="log-level ${log.level}">${log.level}</div> <div class="log-content"> <div class="log-timestamp">${new Date(log.timestamp * 1000).toLocaleString()}</div> <div class="log-message">${log.message}</div> ${log.metadata && Object.keys(log.metadata).length > 0 ? `<div class="log-metadata">${JSON.stringify(log.metadata)}</div>` : ''} </div> `; 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 = ` <div class="form-group"> <label>Network:</label> <input type="text" value="${config.dhcp?.network || ''}" name="dhcp_network"> </div> <div class="form-group"> <label>Range Start:</label> <input type="text" value="${config.dhcp?.range_start || ''}" name="dhcp_range_start"> </div> <div class="form-group"> <label>Range End:</label> <input type="text" value="${config.dhcp?.range_end || ''}" name="dhcp_range_end"> </div> <div class="form-group"> <label>Lease Time (seconds):</label> <input type="number" value="${config.dhcp?.lease_time || 3600}" name="dhcp_lease_time"> </div> `; // NAT Configuration const natConfig = document.getElementById('natConfig'); natConfig.innerHTML = ` <div class="form-group"> <label>Port Range Start:</label> <input type="number" value="${config.nat?.port_range_start || 10000}" name="nat_port_start"> </div> <div class="form-group"> <label>Port Range End:</label> <input type="number" value="${config.nat?.port_range_end || 65535}" name="nat_port_end"> </div> <div class="form-group"> <label>Session Timeout (seconds):</label> <input type="number" value="${config.nat?.session_timeout || 300}" name="nat_timeout"> </div> `; // Firewall Configuration const firewallConfig = document.getElementById('firewallConfig'); firewallConfig.innerHTML = ` <div class="form-group"> <label>Default Policy:</label> <select name="firewall_default_policy"> <option value="ACCEPT" ${config.firewall?.default_policy === 'ACCEPT' ? 'selected' : ''}>Accept</option> <option value="DROP" ${config.firewall?.default_policy === 'DROP' ? 'selected' : ''}>Drop</option> </select> </div> <div class="form-group"> <label>Log Blocked:</label> <select name="firewall_log_blocked"> <option value="true" ${config.firewall?.log_blocked ? 'selected' : ''}>Yes</option> <option value="false" ${!config.firewall?.log_blocked ? 'selected' : ''}>No</option> </select> </div> `; } // 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(); });
/**
* 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');
});