|
|
|
|
|
class VPNDashboard { |
|
|
constructor() { |
|
|
this.token = localStorage.getItem('auth_token'); |
|
|
this.currentUser = null; |
|
|
this.clients = []; |
|
|
this.serverStatus = {}; |
|
|
this.protocols = []; |
|
|
|
|
|
this.init(); |
|
|
} |
|
|
async init() { |
|
|
const token = localStorage.getItem("auth_token"); |
|
|
if (!token) { |
|
|
window.location.href = |
|
|
'/auth.html'; |
|
|
return; |
|
|
} |
|
|
this.token = token; |
|
|
|
|
|
try { |
|
|
await this.loadUserInfo(); |
|
|
await this.loadInitialData(); |
|
|
this.setupEventListeners(); |
|
|
this.startAutoRefresh(); |
|
|
} catch (error) { |
|
|
console.error("Dashboard initialization failed:", error); |
|
|
this.showAlert("error", "Failed to load dashboard", error.message); |
|
|
|
|
|
localStorage.removeItem("auth_token"); |
|
|
localStorage.removeItem("refresh_token"); |
|
|
localStorage.removeItem("user_data"); |
|
|
window.location.href = "/auth.html"; |
|
|
} |
|
|
} async loadUserInfo() { |
|
|
try { |
|
|
const response = await this.apiCall("/api/profile"); |
|
|
this.currentUser = response.user; |
|
|
document.getElementById('userName').textContent = this.currentUser.username; |
|
|
} catch (error) { |
|
|
console.error('Failed to load user info:', error); |
|
|
throw error; |
|
|
} |
|
|
} |
|
|
|
|
|
async loadInitialData() { |
|
|
this.showLoading(true); |
|
|
|
|
|
try { |
|
|
await Promise.all([ |
|
|
this.loadClients(), |
|
|
this.loadServerStatus(), |
|
|
this.loadProtocols(), |
|
|
this.loadServerStatistics() |
|
|
]); |
|
|
|
|
|
this.updateOverviewStats(); |
|
|
this.addActivity('Dashboard loaded successfully'); |
|
|
} catch (error) { |
|
|
console.error('Failed to load initial data:', error); |
|
|
this.showAlert('error', 'Failed to load data', error.message); |
|
|
} finally { |
|
|
this.showLoading(false); |
|
|
} |
|
|
} |
|
|
|
|
|
async loadClients() { |
|
|
try { |
|
|
const response = await this.apiCall('/api/vpn-clients'); |
|
|
this.clients = response.clients || []; |
|
|
this.renderClientsTable(); |
|
|
} catch (error) { |
|
|
console.error('Failed to load clients:', error); |
|
|
this.clients = []; |
|
|
} |
|
|
} |
|
|
|
|
|
async loadServerStatus() { |
|
|
try { |
|
|
const response = await this.apiCall('/api/server/status'); |
|
|
this.serverStatus = response.status || {}; |
|
|
this.serverInfo = response.server_info || {}; |
|
|
this.renderServerStatus(); |
|
|
} catch (error) { |
|
|
console.error('Failed to load server status:', error); |
|
|
this.serverStatus = {}; |
|
|
} |
|
|
} |
|
|
|
|
|
async loadProtocols() { |
|
|
try { |
|
|
const response = await fetch('/api/server/protocols'); |
|
|
const data = await response.json(); |
|
|
this.protocols = data.protocols || []; |
|
|
this.renderProtocols(); |
|
|
} catch (error) { |
|
|
console.error('Failed to load protocols:', error); |
|
|
this.protocols = []; |
|
|
} |
|
|
} |
|
|
|
|
|
async loadServerStatistics() { |
|
|
try { |
|
|
const response = await this.apiCall('/api/server/statistics'); |
|
|
this.statistics = response.statistics || {}; |
|
|
} catch (error) { |
|
|
console.error('Failed to load server statistics:', error); |
|
|
this.statistics = {}; |
|
|
} |
|
|
} |
|
|
|
|
|
updateOverviewStats() { |
|
|
const totalClients = this.clients.length; |
|
|
const runningServers = Object.values(this.serverStatus).filter(s => s.running).length; |
|
|
const serverLocation = this.serverInfo?.server_ip || 'Unknown'; |
|
|
|
|
|
document.getElementById('totalClients').textContent = totalClients; |
|
|
document.getElementById('runningServers').textContent = runningServers; |
|
|
document.getElementById('serverLocation').textContent = serverLocation; |
|
|
} |
|
|
|
|
|
renderClientsTable() { |
|
|
const tbody = document.getElementById('clientsTableBody'); |
|
|
|
|
|
if (this.clients.length === 0) { |
|
|
tbody.innerHTML = ` |
|
|
<tr> |
|
|
<td colspan="6" style="text-align: center; padding: 2rem; color: #666;"> |
|
|
<i class="fas fa-laptop" style="font-size: 2rem; margin-bottom: 1rem; display: block;"></i> |
|
|
No VPN clients found. Create your first client to get started. |
|
|
</td> |
|
|
</tr> |
|
|
`; |
|
|
return; |
|
|
} |
|
|
|
|
|
tbody.innerHTML = this.clients.map(client => ` |
|
|
<tr> |
|
|
<td> |
|
|
<strong>${this.escapeHtml(client.client_name)}</strong> |
|
|
${client.description ? `<br><small style="color: #666;">${this.escapeHtml(client.description)}</small>` : ''} |
|
|
</td> |
|
|
<td> |
|
|
<span class="protocol-badge ${client.protocol}"> |
|
|
${client.protocol.toUpperCase()} |
|
|
</span> |
|
|
</td> |
|
|
<td> |
|
|
<span class="status-badge ${client.status}"> |
|
|
${client.status} |
|
|
</span> |
|
|
</td> |
|
|
<td>${this.formatDate(client.created_at)}</td> |
|
|
<td>${client.last_connected ? this.formatDate(client.last_connected) : 'Never'}</td> |
|
|
<td> |
|
|
<div class="client-actions"> |
|
|
<button class="btn small primary" onclick="dashboard.downloadClientConfig(${client.id})"> |
|
|
<i class="fas fa-download"></i> |
|
|
</button> |
|
|
<button class="btn small secondary" onclick="dashboard.viewClientDetails(${client.id})"> |
|
|
<i class="fas fa-eye"></i> |
|
|
</button> |
|
|
<button class="btn small danger" onclick="dashboard.revokeClient(${client.id})"> |
|
|
<i class="fas fa-ban"></i> |
|
|
</button> |
|
|
</div> |
|
|
</td> |
|
|
</tr> |
|
|
`).join(''); |
|
|
} |
|
|
|
|
|
renderServerStatus() { |
|
|
const protocols = ['openvpn', 'ikev2', 'wireguard']; |
|
|
|
|
|
protocols.forEach(protocol => { |
|
|
const status = this.serverStatus[protocol] || { running: false, client_count: 0, port: 0 }; |
|
|
const statusElement = document.getElementById(`${protocol}Status`); |
|
|
const clientsElement = document.getElementById(`${protocol}Clients`); |
|
|
const portElement = document.getElementById(`${protocol}Port`); |
|
|
|
|
|
if (statusElement) { |
|
|
statusElement.className = `server-status ${status.running ? 'online' : 'offline'}`; |
|
|
statusElement.innerHTML = ` |
|
|
<span class="status-dot"></span> |
|
|
${status.running ? 'Online' : 'Offline'} |
|
|
`; |
|
|
} |
|
|
|
|
|
if (clientsElement) { |
|
|
clientsElement.textContent = status.client_count || 0; |
|
|
} |
|
|
|
|
|
if (portElement) { |
|
|
portElement.textContent = status.port || 0; |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
renderProtocols() { |
|
|
const container = document.getElementById('protocolsGrid'); |
|
|
|
|
|
container.innerHTML = this.protocols.map(protocol => ` |
|
|
<div class="protocol-card"> |
|
|
<div class="protocol-header"> |
|
|
<div class="protocol-icon"> |
|
|
<i class="fas fa-network-wired"></i> |
|
|
</div> |
|
|
<h3>${protocol.name}</h3> |
|
|
</div> |
|
|
<p class="protocol-description">${protocol.description}</p> |
|
|
<div class="protocol-details"> |
|
|
<div class="protocol-detail"> |
|
|
<span>Port:</span> |
|
|
<span>${protocol.port}</span> |
|
|
</div> |
|
|
<div class="protocol-detail"> |
|
|
<span>Transport:</span> |
|
|
<span>${protocol.transport}</span> |
|
|
</div> |
|
|
</div> |
|
|
<div class="protocol-features"> |
|
|
<h4>Features:</h4> |
|
|
<ul> |
|
|
${protocol.features.map(feature => `<li>${feature}</li>`).join('')} |
|
|
</ul> |
|
|
</div> |
|
|
<button class="btn primary" onclick="dashboard.createClientWithProtocol('${protocol.key}')"> |
|
|
Create ${protocol.name} Client |
|
|
</button> |
|
|
</div> |
|
|
`).join(''); |
|
|
} |
|
|
|
|
|
setupEventListeners() { |
|
|
|
|
|
document.addEventListener('click', (e) => { |
|
|
if (e.target.classList.contains('modal')) { |
|
|
this.closeModal(e.target.id); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
document.addEventListener('click', (e) => { |
|
|
if (!e.target.closest('.user-menu')) { |
|
|
document.getElementById('userDropdown').classList.remove('show'); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
startAutoRefresh() { |
|
|
|
|
|
setInterval(() => { |
|
|
this.loadServerStatus(); |
|
|
}, 30000); |
|
|
|
|
|
|
|
|
setInterval(() => { |
|
|
this.loadClients(); |
|
|
}, 60000); |
|
|
} |
|
|
|
|
|
|
|
|
async apiCall(endpoint, options = {}) { |
|
|
const defaultOptions = { |
|
|
headers: { |
|
|
'Authorization': `Bearer ${this.token}`, |
|
|
'Content-Type': 'application/json' |
|
|
} |
|
|
}; |
|
|
|
|
|
const response = await fetch(endpoint, { ...defaultOptions, ...options }); |
|
|
|
|
|
if (response.status === 401) { |
|
|
localStorage.removeItem('auth_token'); |
|
|
window.location.href = '/auth.html'; |
|
|
return; |
|
|
} |
|
|
|
|
|
if (!response.ok) { |
|
|
const error = await response.json().catch(() => ({ error: 'Network error' })); |
|
|
throw new Error(error.error || 'Request failed'); |
|
|
} |
|
|
|
|
|
return await response.json(); |
|
|
} |
|
|
|
|
|
|
|
|
showSection(sectionId) { |
|
|
|
|
|
document.querySelectorAll('.content-section').forEach(section => { |
|
|
section.classList.remove('active'); |
|
|
}); |
|
|
|
|
|
|
|
|
document.querySelectorAll('.nav-item').forEach(item => { |
|
|
item.classList.remove('active'); |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById(sectionId).classList.add('active'); |
|
|
|
|
|
|
|
|
document.querySelector(`[data-section="${sectionId}"]`).classList.add('active'); |
|
|
|
|
|
|
|
|
this.loadSectionData(sectionId); |
|
|
} |
|
|
|
|
|
async loadSectionData(sectionId) { |
|
|
switch (sectionId) { |
|
|
case 'server-status': |
|
|
await this.loadServerStatus(); |
|
|
break; |
|
|
case 'vpn-clients': |
|
|
await this.loadClients(); |
|
|
break; |
|
|
case 'protocols': |
|
|
await this.loadProtocols(); |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
showCreateClientModal() { |
|
|
document.getElementById('createClientModal').classList.add('show'); |
|
|
document.getElementById('createClientForm').reset(); |
|
|
} |
|
|
|
|
|
createClientWithProtocol(protocol) { |
|
|
this.showCreateClientModal(); |
|
|
document.getElementById('clientProtocol').value = protocol; |
|
|
} |
|
|
|
|
|
async createVPNClient(event) { |
|
|
event.preventDefault(); |
|
|
|
|
|
const formData = new FormData(event.target); |
|
|
const clientData = { |
|
|
client_name: formData.get('clientName'), |
|
|
protocol: formData.get('protocol'), |
|
|
description: formData.get('description') |
|
|
}; |
|
|
|
|
|
try { |
|
|
this.showLoading(true); |
|
|
|
|
|
const response = await this.apiCall('/api/vpn-clients', { |
|
|
method: 'POST', |
|
|
body: JSON.stringify(clientData) |
|
|
}); |
|
|
|
|
|
this.showAlert('success', 'Client Created', 'VPN client created successfully'); |
|
|
this.closeModal('createClientModal'); |
|
|
await this.loadClients(); |
|
|
this.addActivity(`Created VPN client: ${clientData.client_name}`); |
|
|
|
|
|
} catch (error) { |
|
|
console.error('Failed to create client:', error); |
|
|
this.showAlert('error', 'Creation Failed', error.message); |
|
|
} finally { |
|
|
this.showLoading(false); |
|
|
} |
|
|
} |
|
|
|
|
|
async downloadClientConfig(clientId) { |
|
|
try { |
|
|
const response = await fetch(`/api/vpn-clients/${clientId}/download`, { |
|
|
headers: { |
|
|
'Authorization': `Bearer ${this.token}` |
|
|
} |
|
|
}); |
|
|
|
|
|
if (!response.ok) { |
|
|
throw new Error('Download failed'); |
|
|
} |
|
|
|
|
|
const blob = await response.blob(); |
|
|
const url = window.URL.createObjectURL(blob); |
|
|
const a = document.createElement('a'); |
|
|
a.href = url; |
|
|
a.download = `vpn-client-${clientId}.zip`; |
|
|
document.body.appendChild(a); |
|
|
a.click(); |
|
|
window.URL.revokeObjectURL(url); |
|
|
document.body.removeChild(a); |
|
|
|
|
|
this.addActivity(`Downloaded configuration for client ${clientId}`); |
|
|
|
|
|
} catch (error) { |
|
|
console.error('Download failed:', error); |
|
|
this.showAlert('error', 'Download Failed', error.message); |
|
|
} |
|
|
} |
|
|
|
|
|
async revokeClient(clientId) { |
|
|
if (!confirm('Are you sure you want to revoke this client? This action cannot be undone.')) { |
|
|
return; |
|
|
} |
|
|
|
|
|
try { |
|
|
await this.apiCall(`/api/vpn-clients/${clientId}/revoke`, { |
|
|
method: 'POST' |
|
|
}); |
|
|
|
|
|
this.showAlert('success', 'Client Revoked', 'Client access has been revoked'); |
|
|
await this.loadClients(); |
|
|
this.addActivity(`Revoked client ${clientId}`); |
|
|
|
|
|
} catch (error) { |
|
|
console.error('Failed to revoke client:', error); |
|
|
this.showAlert('error', 'Revocation Failed', error.message); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
async startServer(protocol) { |
|
|
try { |
|
|
this.showLoading(true); |
|
|
|
|
|
const response = await this.apiCall(`/api/server/start/${protocol}`, { |
|
|
method: 'POST' |
|
|
}); |
|
|
|
|
|
this.showAlert('success', 'Server Started', response.message); |
|
|
await this.loadServerStatus(); |
|
|
this.addActivity(`Started ${protocol.toUpperCase()} server`); |
|
|
|
|
|
} catch (error) { |
|
|
console.error('Failed to start server:', error); |
|
|
this.showAlert('error', 'Start Failed', error.message); |
|
|
} finally { |
|
|
this.showLoading(false); |
|
|
} |
|
|
} |
|
|
|
|
|
async stopServer(protocol) { |
|
|
if (!confirm(`Are you sure you want to stop the ${protocol.toUpperCase()} server?`)) { |
|
|
return; |
|
|
} |
|
|
|
|
|
try { |
|
|
this.showLoading(true); |
|
|
|
|
|
const response = await this.apiCall(`/api/server/stop/${protocol}`, { |
|
|
method: 'POST' |
|
|
}); |
|
|
|
|
|
this.showAlert('success', 'Server Stopped', response.message); |
|
|
await this.loadServerStatus(); |
|
|
this.addActivity(`Stopped ${protocol.toUpperCase()} server`); |
|
|
|
|
|
} catch (error) { |
|
|
console.error('Failed to stop server:', error); |
|
|
this.showAlert('error', 'Stop Failed', error.message); |
|
|
} finally { |
|
|
this.showLoading(false); |
|
|
} |
|
|
} |
|
|
|
|
|
async restartServer(protocol) { |
|
|
if (!confirm(`Are you sure you want to restart the ${protocol.toUpperCase()} server?`)) { |
|
|
return; |
|
|
} |
|
|
|
|
|
try { |
|
|
this.showLoading(true); |
|
|
|
|
|
const response = await this.apiCall(`/api/server/restart/${protocol}`, { |
|
|
method: 'POST' |
|
|
}); |
|
|
|
|
|
this.showAlert('success', 'Server Restarted', response.message); |
|
|
await this.loadServerStatus(); |
|
|
this.addActivity(`Restarted ${protocol.toUpperCase()} server`); |
|
|
|
|
|
} catch (error) { |
|
|
console.error('Failed to restart server:', error); |
|
|
this.showAlert('error', 'Restart Failed', error.message); |
|
|
} finally { |
|
|
this.showLoading(false); |
|
|
} |
|
|
} |
|
|
|
|
|
async refreshServerStatus() { |
|
|
try { |
|
|
this.showLoading(true); |
|
|
await this.loadServerStatus(); |
|
|
this.showAlert('success', 'Status Refreshed', 'Server status updated'); |
|
|
} catch (error) { |
|
|
this.showAlert('error', 'Refresh Failed', error.message); |
|
|
} finally { |
|
|
this.showLoading(false); |
|
|
} |
|
|
} |
|
|
|
|
|
async loadServerLogs() { |
|
|
const protocol = document.getElementById('logProtocol').value; |
|
|
|
|
|
try { |
|
|
const response = await this.apiCall(`/api/server/logs/${protocol}?lines=100`); |
|
|
const logsContainer = document.getElementById('logsContainer'); |
|
|
|
|
|
if (response.logs && response.logs.length > 0) { |
|
|
logsContainer.innerHTML = ` |
|
|
<div class="logs-content"> |
|
|
${response.logs.join('\\n')} |
|
|
</div> |
|
|
`; |
|
|
} else { |
|
|
logsContainer.innerHTML = ` |
|
|
<div class="logs-content"> |
|
|
No logs available for ${protocol.toUpperCase()} |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
|
|
|
|
|
|
logsContainer.scrollTop = logsContainer.scrollHeight; |
|
|
|
|
|
} catch (error) { |
|
|
console.error('Failed to load logs:', error); |
|
|
this.showAlert('error', 'Logs Failed', error.message); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
filterClients() { |
|
|
const protocolFilter = document.getElementById('protocolFilter').value; |
|
|
const statusFilter = document.getElementById('statusFilter').value; |
|
|
const searchFilter = document.getElementById('searchFilter').value.toLowerCase(); |
|
|
|
|
|
let filteredClients = this.clients; |
|
|
|
|
|
if (protocolFilter) { |
|
|
filteredClients = filteredClients.filter(client => client.protocol === protocolFilter); |
|
|
} |
|
|
|
|
|
if (statusFilter) { |
|
|
filteredClients = filteredClients.filter(client => client.status === statusFilter); |
|
|
} |
|
|
|
|
|
if (searchFilter) { |
|
|
filteredClients = filteredClients.filter(client => |
|
|
client.client_name.toLowerCase().includes(searchFilter) || |
|
|
(client.description && client.description.toLowerCase().includes(searchFilter)) |
|
|
); |
|
|
} |
|
|
|
|
|
|
|
|
const originalClients = this.clients; |
|
|
this.clients = filteredClients; |
|
|
this.renderClientsTable(); |
|
|
this.clients = originalClients; |
|
|
} |
|
|
|
|
|
|
|
|
showModal(modalId) { |
|
|
document.getElementById(modalId).classList.add('show'); |
|
|
} |
|
|
|
|
|
closeModal(modalId) { |
|
|
document.getElementById(modalId).classList.remove('show'); |
|
|
} |
|
|
|
|
|
showLoading(show) { |
|
|
const overlay = document.getElementById('loadingOverlay'); |
|
|
if (show) { |
|
|
overlay.classList.add('show'); |
|
|
} else { |
|
|
overlay.classList.remove('show'); |
|
|
} |
|
|
} |
|
|
|
|
|
showAlert(type, title, message) { |
|
|
const container = document.getElementById('alertContainer'); |
|
|
const alertId = 'alert-' + Date.now(); |
|
|
|
|
|
const alertHtml = ` |
|
|
<div class="alert ${type}" id="${alertId}"> |
|
|
<div class="alert-icon"> |
|
|
<i class="fas fa-${type === 'success' ? 'check-circle' : type === 'error' ? 'exclamation-circle' : 'info-circle'}"></i> |
|
|
</div> |
|
|
<div class="alert-content"> |
|
|
<div class="alert-title">${title}</div> |
|
|
<div class="alert-message">${message}</div> |
|
|
</div> |
|
|
<button class="alert-close" onclick="dashboard.closeAlert('${alertId}')"> |
|
|
<i class="fas fa-times"></i> |
|
|
</button> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
container.insertAdjacentHTML('beforeend', alertHtml); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
this.closeAlert(alertId); |
|
|
}, 5000); |
|
|
} |
|
|
|
|
|
closeAlert(alertId) { |
|
|
const alert = document.getElementById(alertId); |
|
|
if (alert) { |
|
|
alert.remove(); |
|
|
} |
|
|
} |
|
|
|
|
|
addActivity(message) { |
|
|
const activityList = document.getElementById('activityList'); |
|
|
const activityHtml = ` |
|
|
<div class="activity-item"> |
|
|
<i class="fas fa-info-circle"></i> |
|
|
<span>${this.escapeHtml(message)}</span> |
|
|
<time>Just now</time> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
activityList.insertAdjacentHTML('afterbegin', activityHtml); |
|
|
|
|
|
|
|
|
const activities = activityList.querySelectorAll('.activity-item'); |
|
|
if (activities.length > 10) { |
|
|
activities[activities.length - 1].remove(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
escapeHtml(text) { |
|
|
const div = document.createElement('div'); |
|
|
div.textContent = text; |
|
|
return div.innerHTML; |
|
|
} |
|
|
|
|
|
formatDate(dateString) { |
|
|
if (!dateString) return 'Never'; |
|
|
|
|
|
const date = new Date(dateString); |
|
|
const now = new Date(); |
|
|
const diff = now - date; |
|
|
|
|
|
if (diff < 60000) return 'Just now'; |
|
|
if (diff < 3600000) return `${Math.floor(diff / 60000)} minutes ago`; |
|
|
if (diff < 86400000) return `${Math.floor(diff / 3600000)} hours ago`; |
|
|
|
|
|
return date.toLocaleDateString(); |
|
|
} |
|
|
|
|
|
|
|
|
toggleUserMenu() { |
|
|
document.getElementById('userDropdown').classList.toggle('show'); |
|
|
} |
|
|
|
|
|
showProfile() { |
|
|
this.showAlert('info', 'Profile', 'Profile management coming soon'); |
|
|
} |
|
|
|
|
|
showSettings() { |
|
|
this.showAlert('info', 'Settings', 'Settings management coming soon'); |
|
|
} |
|
|
|
|
|
logout() { |
|
|
localStorage.removeItem('auth_token'); |
|
|
window.location.href = '/auth.html'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
let dashboard; |
|
|
|
|
|
function showSection(sectionId) { |
|
|
dashboard.showSection(sectionId); |
|
|
} |
|
|
|
|
|
function showCreateClientModal() { |
|
|
dashboard.showCreateClientModal(); |
|
|
} |
|
|
|
|
|
function createVPNClient(event) { |
|
|
dashboard.createVPNClient(event); |
|
|
} |
|
|
|
|
|
function closeModal(modalId) { |
|
|
dashboard.closeModal(modalId); |
|
|
} |
|
|
|
|
|
function toggleUserMenu() { |
|
|
dashboard.toggleUserMenu(); |
|
|
} |
|
|
|
|
|
function showProfile() { |
|
|
dashboard.showProfile(); |
|
|
} |
|
|
|
|
|
function showSettings() { |
|
|
dashboard.showSettings(); |
|
|
} |
|
|
|
|
|
function logout() { |
|
|
dashboard.logout(); |
|
|
} |
|
|
|
|
|
function startServer(protocol) { |
|
|
dashboard.startServer(protocol); |
|
|
} |
|
|
|
|
|
function stopServer(protocol) { |
|
|
dashboard.stopServer(protocol); |
|
|
} |
|
|
|
|
|
function restartServer(protocol) { |
|
|
dashboard.restartServer(protocol); |
|
|
} |
|
|
|
|
|
function refreshServerStatus() { |
|
|
dashboard.refreshServerStatus(); |
|
|
} |
|
|
|
|
|
function loadServerLogs() { |
|
|
dashboard.loadServerLogs(); |
|
|
} |
|
|
|
|
|
function filterClients() { |
|
|
dashboard.filterClients(); |
|
|
} |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
dashboard = new VPNDashboard(); |
|
|
}); |
|
|
|
|
|
|