/**
* 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');
}