JRNET / web /static /js /dashboard.js
Factor Studios
Upload 96 files
6a5b8d8 verified
/**
* Dashboard animations and interactivity
*/
// Initialize animations when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
initializeAnimations();
initializeCharts();
initializeEventListeners();
});
// Animation utilities
const animate = {
fadeIn: (element, delay = 0) => {
element.style.opacity = '0';
element.style.transform = 'translateY(20px)';
setTimeout(() => {
element.style.transition = 'all 0.5s ease';
element.style.opacity = '1';
element.style.transform = 'translateY(0)';
}, delay);
},
slideIn: (element, direction = 'left', delay = 0) => {
const transforms = {
left: 'translateX(-50px)',
right: 'translateX(50px)',
up: 'translateY(50px)',
down: 'translateY(-50px)'
};
element.style.opacity = '0';
element.style.transform = transforms[direction];
setTimeout(() => {
element.style.transition = 'all 0.5s ease';
element.style.opacity = '1';
element.style.transform = 'translate(0)';
}, delay);
},
pulse: (element) => {
element.style.transform = 'scale(1)';
element.style.transition = 'transform 0.2s ease';
element.style.transform = 'scale(1.05)';
setTimeout(() => {
element.style.transform = 'scale(1)';
}, 200);
}
};
// Initialize animations
function initializeAnimations() {
// Animate stat cards
const statCards = document.querySelectorAll('.stat-card');
statCards.forEach((card, index) => {
animate.fadeIn(card, index * 100);
});
// Animate charts
const charts = document.querySelectorAll('.chart-container');
charts.forEach((chart, index) => {
animate.slideIn(chart, 'up', 300 + index * 100);
});
// Animate session cards
const sessionCards = document.querySelectorAll('.session-card');
sessionCards.forEach((card, index) => {
animate.slideIn(card, 'right', 500 + index * 100);
});
}
// Initialize Charts
function initializeCharts() {
// Traffic Chart
const trafficChart = document.getElementById('traffic-chart');
if (trafficChart) {
new Chart(trafficChart, {
type: 'line',
data: {
labels: getLastNHours(24),
datasets: [{
label: 'Upload',
data: generateRandomData(24),
borderColor: '#4F46E5',
tension: 0.4
}, {
label: 'Download',
data: generateRandomData(24),
borderColor: '#10B981',
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'top'
}
},
scales: {
y: {
beginAtZero: true
}
}
}
});
}
// Protocol Distribution Chart
const protocolChart = document.getElementById('protocol-chart');
if (protocolChart) {
new Chart(protocolChart, {
type: 'doughnut',
data: {
labels: ['Shadowsocks', 'IKEv2', 'L2TP', 'PPTP'],
datasets: [{
data: [40, 30, 20, 10],
backgroundColor: [
'#4F46E5',
'#10B981',
'#F59E0B',
'#EF4444'
]
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom'
}
}
}
});
}
}
// Initialize Event Listeners
function initializeEventListeners() {
// Quick Action Buttons
const quickActions = document.querySelectorAll('.quick-action');
quickActions.forEach(action => {
action.addEventListener('mouseenter', () => {
animate.pulse(action);
});
});
// Responsive Sidebar Toggle
const sidebarToggle = document.getElementById('sidebar-toggle');
const sidebar = document.querySelector('.dashboard-sidebar');
if (sidebarToggle && sidebar) {
sidebarToggle.addEventListener('click', () => {
sidebar.classList.toggle('active');
});
}
// Live Data Updates
setInterval(updateLiveStats, 5000);
}
// Utility Functions
function getLastNHours(n) {
const hours = [];
for (let i = n - 1; i >= 0; i--) {
const d = new Date();
d.setHours(d.getHours() - i);
hours.push(d.getHours() + ':00');
}
return hours;
}
function generateRandomData(n) {
return Array.from({length: n}, () => Math.floor(Math.random() * 100));
}
function 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];
}
// Live Data Updates
function updateLiveStats() {
fetch('/api/stats')
.then(response => response.json())
.then(data => {
// Update connection status
const statusIndicator = document.querySelector('.status-indicator');
if (statusIndicator) {
statusIndicator.className = `status-indicator ${data.isConnected ? 'online' : 'offline'}`;
statusIndicator.textContent = data.isConnected ? 'Connected' : 'Disconnected';
}
// Update traffic stats
const uploadStat = document.getElementById('upload-stat');
const downloadStat = document.getElementById('download-stat');
if (uploadStat) uploadStat.textContent = formatBytes(data.uploadTotal);
if (downloadStat) downloadStat.textContent = formatBytes(data.downloadTotal);
// Update active sessions
updateSessionsList(data.sessions);
})
.catch(console.error);
}
function updateSessionsList(sessions) {
const sessionsList = document.getElementById('active-sessions');
if (!sessionsList) return;
sessions.forEach(session => {
const existingSession = document.getElementById(`session-${session.id}`);
if (!existingSession) {
// Create new session card
const sessionCard = createSessionCard(session);
animate.slideIn(sessionCard, 'right');
sessionsList.appendChild(sessionCard);
} else {
// Update existing session
updateSessionCard(existingSession, session);
}
});
}
function createSessionCard(session) {
const card = document.createElement('div');
card.id = `session-${session.id}`;
card.className = 'session-card dashboard-card';
card.innerHTML = `
<div class="session-info">
<h6 class="mb-1">${session.protocol}</h6>
<small class="text-muted">${session.ip}</small>
</div>
<div class="session-stats">
<div class="mb-1">↑ ${formatBytes(session.upload)}</div>
<div>↓ ${formatBytes(session.download)}</div>
</div>
`;
return card;
}
function updateSessionCard(card, session) {
const uploadEl = card.querySelector('.session-stats div:first-child');
const downloadEl = card.querySelector('.session-stats div:last-child');
uploadEl.textContent = `↑ ${formatBytes(session.upload)}`;
downloadEl.textContent = `↓ ${formatBytes(session.download)}`;
}