| document.addEventListener('DOMContentLoaded', function() { |
| |
| const nodes = Array.from({length: 25}, (_, i) => ({ |
| id: `NODE-${1000 + i}`, |
| name: `Node ${i + 1}`, |
| uptime: Math.floor(Math.random() * 20) + 80 + Math.random(), |
| performance: Math.floor(Math.random() * 20) + 80 + Math.random(), |
| score: Math.floor(Math.random() * 20) + 80 + Math.random(), |
| status: Math.random() > 0.1 ? 'active' : 'inactive', |
| lastUpdated: new Date(Date.now() - Math.floor(Math.random() * 86400000)) |
| })).sort((a, b) => b.score - a.score); |
| |
| |
| const performanceChartData = { |
| labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'], |
| datasets: [ |
| { |
| label: 'Average Score', |
| data: [72, 75, 78, 82, 85, 87, 89], |
| borderColor: '#6366f1', |
| backgroundColor: 'rgba(99, 102, 241, 0.1)', |
| tension: 0.4, |
| fill: true |
| }, |
| { |
| label: 'Network Uptime %', |
| data: [92, 93, 94, 95, 96, 97, 98], |
| borderColor: '#10b981', |
| backgroundColor: 'rgba(16, 185, 129, 0.1)', |
| tension: 0.4, |
| fill: true |
| } |
| ] |
| }; |
| |
| |
| const ctx = document.getElementById('performance-chart').getContext('2d'); |
| const performanceChart = new Chart(ctx, { |
| type: 'line', |
| data: performanceChartData, |
| options: { |
| responsive: true, |
| maintainAspectRatio: false, |
| plugins: { |
| legend: { |
| position: 'top', |
| labels: { |
| color: '#6b7280', |
| font: { |
| family: 'Inter' |
| } |
| } |
| } |
| }, |
| scales: { |
| y: { |
| beginAtZero: false, |
| min: 60, |
| max: 100, |
| grid: { |
| color: 'rgba(209, 213, 219, 0.2)' |
| }, |
| ticks: { |
| color: '#6b7280' |
| } |
| }, |
| x: { |
| grid: { |
| display: false |
| }, |
| ticks: { |
| color: '#6b7280' |
| } |
| } |
| } |
| } |
| }); |
| |
| |
| let currentPage = 1; |
| const rowsPerPage = 10; |
| const totalPages = Math.ceil(nodes.length / rowsPerPage); |
| |
| |
| const leaderboardBody = document.getElementById('leaderboard-body'); |
| const prevBtn = document.getElementById('prev-btn'); |
| const nextBtn = document.getElementById('next-btn'); |
| const showingCount = document.getElementById('showing-count'); |
| const totalCount = document.getElementById('total-count'); |
| const refreshBtn = document.getElementById('refresh-btn'); |
| |
| |
| totalCount.textContent = nodes.length; |
| |
| |
| function renderTableRows() { |
| leaderboardBody.innerHTML = ''; |
| |
| const startIndex = (currentPage - 1) * rowsPerPage; |
| const endIndex = Math.min(startIndex + rowsPerPage, nodes.length); |
| |
| for (let i = startIndex; i < endIndex; i++) { |
| const node = nodes[i]; |
| const row = document.createElement('tr'); |
| row.className = `table-row-hover ${i % 2 === 0 ? 'bg-gray-50 dark:bg-gray-800' : 'bg-white dark:bg-gray-800'}`; |
| |
| row.innerHTML = ` |
| <td class="py-4 text-gray-700 dark:text-gray-300 font-medium">${i + 1}</td> |
| <td class="py-4"> |
| <div class="flex items-center"> |
| <div class="flex-shrink-0 h-10 w-10 rounded-full bg-primary-100 dark:bg-gray-700 flex items-center justify-center"> |
| <i data-feather="server" class="text-primary-600 dark:text-primary-500"></i> |
| </div> |
| <div class="ml-4"> |
| <div class="text-sm font-medium text-gray-900 dark:text-white">${node.id}</div> |
| <div class="text-sm text-gray-500 dark:text-gray-400">${node.name}</div> |
| </div> |
| </div> |
| </td> |
| <td class="py-4"> |
| <div class="flex items-center"> |
| <div class="w-24 bg-gray-200 dark:bg-gray-700 rounded-full h-2.5"> |
| <div class="bg-green-500 h-2.5 rounded-full" style="width: ${node.uptime}%"></div> |
| </div> |
| <span class="ml-2 text-sm text-gray-500 dark:text-gray-400">${node.uptime.toFixed(1)}%</span> |
| </div> |
| </td> |
| <td class="py-4"> |
| <div class="flex items-center"> |
| <div class="w-24 bg-gray-200 dark:bg-gray-700 rounded-full h-2.5"> |
| <div class="bg-blue-500 h-2.5 rounded-full" style="width: ${node.performance}%"></div> |
| </div> |
| <span class="ml-2 text-sm text-gray-500 dark:text-gray-400">${node.performance.toFixed(1)}%</span> |
| </div> |
| </td> |
| <td class="py-4 text-right font-bold text-gray-900 dark:text-white">${node.score.toFixed(1)}</td> |
| `; |
| |
| leaderboardBody.appendChild(row); |
| } |
| |
| |
| showingCount.textContent = `${startIndex + 1}-${endIndex}`; |
| |
| |
| prevBtn.disabled = currentPage === 1; |
| nextBtn.disabled = currentPage === totalPages; |
| |
| |
| feather.replace(); |
| } |
| |
| |
| prevBtn.addEventListener('click', () => { |
| if (currentPage > 1) { |
| currentPage--; |
| renderTableRows(); |
| } |
| }); |
| |
| nextBtn.addEventListener('click', () => { |
| if (currentPage < totalPages) { |
| currentPage++; |
| renderTableRows(); |
| } |
| }); |
| |
| refreshBtn.addEventListener('click', () => { |
| |
| refreshBtn.classList.add('animate-spin'); |
| setTimeout(() => { |
| refreshBtn.classList.remove('animate-spin'); |
| |
| }, 1000); |
| }); |
| |
| |
| renderTableRows(); |
| |
| |
| const darkModeObserver = new MutationObserver(() => { |
| const isDark = document.documentElement.classList.contains('dark'); |
| |
| performanceChart.options.scales.y.grid.color = isDark ? 'rgba(75, 85, 99, 0.2)' : 'rgba(209, 213, 219, 0.2)'; |
| performanceChart.options.scales.y.ticks.color = isDark ? '#9ca3af' : '#6b7280'; |
| performanceChart.options.scales.x.ticks.color = isDark ? '#9ca3af' : '#6b7280'; |
| performanceChart.options.plugins.legend.labels.color = isDark ? '#9ca3af' : '#6b7280'; |
| |
| performanceChart.update(); |
| }); |
| |
| darkModeObserver.observe(document.documentElement, { |
| attributes: true, |
| attributeFilter: ['class'] |
| }); |
| }); |