// Dashboard JavaScript
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);
// If loading fails, it might be due to an invalid token, redirect to login
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 = `
|
No VPN clients found. Create your first client to get started.
|
`;
return;
}
tbody.innerHTML = this.clients.map(client => `
${this.escapeHtml(client.client_name)}
${client.description ? ` ${this.escapeHtml(client.description)}` : ''}
|
${client.protocol.toUpperCase()}
|
${client.status}
|
${this.formatDate(client.created_at)} |
${client.last_connected ? this.formatDate(client.last_connected) : 'Never'} |
|
`).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 = `
${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 => `
${protocol.description}
Port:
${protocol.port}
Transport:
${protocol.transport}
Features:
${protocol.features.map(feature => `- ${feature}
`).join('')}
`).join('');
}
setupEventListeners() {
// Close modals when clicking outside
document.addEventListener('click', (e) => {
if (e.target.classList.contains('modal')) {
this.closeModal(e.target.id);
}
});
// Close user dropdown when clicking outside
document.addEventListener('click', (e) => {
if (!e.target.closest('.user-menu')) {
document.getElementById('userDropdown').classList.remove('show');
}
});
}
startAutoRefresh() {
// Refresh server status every 30 seconds
setInterval(() => {
this.loadServerStatus();
}, 30000);
// Refresh clients every 60 seconds
setInterval(() => {
this.loadClients();
}, 60000);
}
// API Helper
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();
}
// Navigation
showSection(sectionId) {
// Hide all sections
document.querySelectorAll('.content-section').forEach(section => {
section.classList.remove('active');
});
// Remove active class from nav items
document.querySelectorAll('.nav-item').forEach(item => {
item.classList.remove('active');
});
// Show selected section
document.getElementById(sectionId).classList.add('active');
// Add active class to nav item
document.querySelector(`[data-section="${sectionId}"]`).classList.add('active');
// Load section-specific data
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;
}
}
// Client Management
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);
}
}
// Server Management
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 = `
${response.logs.join('\\n')}
`;
} else {
logsContainer.innerHTML = `
No logs available for ${protocol.toUpperCase()}
`;
}
// Scroll to bottom
logsContainer.scrollTop = logsContainer.scrollHeight;
} catch (error) {
console.error('Failed to load logs:', error);
this.showAlert('error', 'Logs Failed', error.message);
}
}
// Filtering
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))
);
}
// Temporarily store original clients and render filtered
const originalClients = this.clients;
this.clients = filteredClients;
this.renderClientsTable();
this.clients = originalClients;
}
// UI Helpers
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 = `
`;
container.insertAdjacentHTML('beforeend', alertHtml);
// Auto-remove after 5 seconds
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 = `
${this.escapeHtml(message)}
`;
activityList.insertAdjacentHTML('afterbegin', activityHtml);
// Keep only last 10 activities
const activities = activityList.querySelectorAll('.activity-item');
if (activities.length > 10) {
activities[activities.length - 1].remove();
}
}
// Utility functions
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();
}
// User menu
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';
}
}
// Global functions for onclick handlers
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();
}
// Initialize dashboard when page loads
document.addEventListener('DOMContentLoaded', () => {
dashboard = new VPNDashboard();
});