HINTECH / static /app.js
Factor Studios
Upload 73 files
aaaaa79 verified
/**
* 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 = `
<span class=\n"component-name\n">${this.formatComponentName(name)}</span>
<span class=\n"component-status-badge ${status ? \n'online\n' : \n'offline\n'}\n">
${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=\n"status-badge status-${lease.state.toLowerCase()}\n">${lease.state}</span></td>
<td>
<button class=\n"btn btn-danger btn-sm\n" onclick=\n"app.releaseDHCPLease(\'${lease.mac_address}\')\n">
<i class=\n"fas fa-times\n"></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=\n"btn btn-danger btn-sm\n" onclick=\n"app.closeNATSession(\'${session.session_id}\')\n">
<i class=\n"fas fa-times\n"></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=\n"status-badge status-${rule.action.toLowerCase()}\n">${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=\n"status-badge status-${rule.enabled ? \n'active\n' : \n'inactive\n'}\n">${rule.enabled ?
'Enabled
' :
'Disabled
'}</span></td>
<td>
<button class=\n"btn btn-danger btn-sm\n" onclick=\n"app.deleteFirewallRule(\'${rule.rule_id}\')\n">
<i class=\n"fas fa-trash\n"></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 ||
'N/A
'}</td>
<td>${iface.network ||
'N/A
'}</td>
<td>${iface.mtu ||
'N/A
'}</td>
<td><span class=\n"status-badge status-${iface.status.toLowerCase()}\n">${iface.status}</span></td>
<td>
<button class=\n"btn btn-secondary btn-sm\n" onclick=\n"app.toggleInterfaceStatus(\'${iface.name}\')\n">
<i class=\n"fas fa-toggle-${iface.status ===
'Up
' ?
'on
' :
'off
'}\n"></i> ${iface.status ===
'Up
' ?
'Down
' :
'Up
'}
</button>
</td>
`;
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 = `
<td>${entry.ip_address}</td>
<td>${entry.mac_address}</td>
<td>
<button class=\n"btn btn-danger btn-sm\n" onclick=\n"app.deleteARPEntry(\'${entry.ip_address}\')\n">
<i class=\n"fas fa-trash\n"></i> Delete
</button>
</td>
`;
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 = `
<td>${client.client_id}</td>
<td>${client.type}</td>
<td>${client.remote_address}</td>
<td>${new Date(client.connected_time * 1000).toLocaleString()}</td>
<td>${this.formatBytes(client.packets_in)} / ${this.formatBytes(client.packets_out)}</td>
<td>${this.formatBytes(client.bytes_in)} / ${this.formatBytes(client.bytes_out)}</td>
<td>
<button class=\n"btn btn-danger btn-sm\n" onclick=\n"app.disconnectBridgeClient(\'${client.client_id}\')\n">
<i class=\n"fas fa-times\n"></i> Disconnect
</button>
</td>
`;
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 = `
<td>${session.session_id}</td>
<td>${session.type}</td>
<td>${session.state}</td>
<td>${session.virtual_ip}:${session.virtual_port}</td>
<td>${session.real_ip}:${session.real_port}</td>
<td>${session.protocol}</td>
<td>${this.formatDuration(session.duration)}</td>
<td>${this.formatDuration(session.idle_time)}</td>
<td>${JSON.stringify(session.metrics)}</td>
`;
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 = `
<span class=\n"log-level ${log.level}\n">${log.level}</span>
<div class=\n"log-content\n">
<div class=\n"log-timestamp\n">${new Date(log.timestamp * 1000).toLocaleString()}</div>
<div class=\n"log-message\n">${log.message}</div>
${log.metadata ? `<div class=\n"log-metadata\n">${JSON.stringify(log.metadata)}</div>` :
''
}
</div>
`;
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 = `
<td>${client.client_id}</td>
<td>${client.common_name}</td>
<td>${client.ip_address}</td>
<td>${connectedSince}</td>
<td>${this.formatBytes(client.bytes_received)}</td>
<td>${this.formatBytes(client.bytes_sent)}</td>
<td><span class=\n"status-badge status-${client.status.toLowerCase()}\n">${client.status}</span></td>
<td>
<button class=\n"btn btn-danger btn-sm\n" onclick=\n"app.disconnectVPNClient(\'${client.client_id}\')\n">
<i class=\n"fas fa-times\n"></i> Disconnect
</button>
</td>
`;
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();
}