TNT / static /app.js
Fred808's picture
Upload 48 files
50d86e3 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="${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();
});