diff --git "a/public/dashboard.js" "b/public/dashboard.js" new file mode 100644--- /dev/null +++ "b/public/dashboard.js" @@ -0,0 +1,2496 @@ +let currentUser = null; +let pluginsData = []; +let usageStats = null; +let adminUsers = []; +let currentPage = 0; +const usersPerPage = 3; +let selectedUserForRole = null; +let countdownInterval = null; +let apiStatsCharts = { + topEndpoints: null, + successFail: null, + dailyRequests: null, + hourlyTrends: null +}; + +async function loadApiStats() { + try { + const token = localStorage.getItem('dashx_token'); + const response = await fetch('/api/stats/all', { + headers: { 'Authorization': `Bearer ${token}` } + }); + + if (response.ok) { + const result = await response.json(); + if (result.success) { + updateApiStatsUI(result.stats); + renderApiStatsCharts(result.stats); + } + } + } catch (error) { + console.error('Error loading API stats:', error); + } +} + +function updateApiStatsUI(stats) { + document.getElementById('globalTotalRequests').textContent = formatNumber(stats.totalRequests); + document.getElementById('globalTodayRequests').textContent = formatNumber(stats.todayRequests); + document.getElementById('successRate').textContent = stats.successRate + '%'; + document.getElementById('failRate').textContent = stats.failRate + '%'; + + const listContainer = document.getElementById('topEndpointsList'); + if (stats.topEndpoints.length > 0) { + listContainer.innerHTML = stats.topEndpoints.map((ep, index) => ` +
+
+
+ #${index + 1} + ${ep.endpoint} +
+
+ ${ep.count} total + āœ“ ${ep.successCount} + āœ— ${ep.failCount} +
+
+
+
+
+
+ `).join(''); + } else { + listContainer.innerHTML = '
No endpoint data available
'; + } +} + +function renderApiStatsCharts(stats) { + Object.values(apiStatsCharts).forEach(chart => { + if (chart) chart.destroy(); + }); + + const topEndpointsCtx = document.getElementById('topEndpointsChart'); + if (topEndpointsCtx && stats.topEndpoints.length > 0) { + apiStatsCharts.topEndpoints = new Chart(topEndpointsCtx, { + type: 'pie', + data: { + labels: stats.topEndpoints.map(ep => ep.endpoint), + datasets: [{ + data: stats.topEndpoints.map(ep => ep.count), + backgroundColor: [ + 'rgba(133, 48, 48, 0.8)', + 'rgba(255, 107, 107, 0.8)', + 'rgba(255, 168, 168, 0.8)', + 'rgba(108, 117, 125, 0.8)', + 'rgba(255, 193, 7, 0.8)', + 'rgba(40, 167, 69, 0.8)', + 'rgba(23, 162, 184, 0.8)', + 'rgba(220, 53, 69, 0.8)', + 'rgba(138, 43, 226, 0.8)', + 'rgba(255, 140, 0, 0.8)' + ], + borderWidth: 2, + borderColor: 'rgba(255, 255, 255, 0.2)' + }] + }, + options: { + responsive: true, + maintainAspectRatio: true, + plugins: { + legend: { + position: 'bottom', + labels: { + color: 'rgba(255, 255, 255, 0.8)', + padding: 15, + font: { size: 11 } + } + }, + tooltip: { + backgroundColor: 'rgba(0, 0, 0, 0.8)', + padding: 12, + titleColor: '#fff', + bodyColor: '#fff' + } + } + } + }); + } + + const successFailCtx = document.getElementById('successFailChart'); + if (successFailCtx) { + apiStatsCharts.successFail = new Chart(successFailCtx, { + type: 'doughnut', + data: { + labels: ['Success', 'Failed'], + datasets: [{ + data: [stats.successCount, stats.failCount], + backgroundColor: [ + 'rgba(40, 167, 69, 0.8)', + 'rgba(220, 53, 69, 0.8)' + ], + borderWidth: 2, + borderColor: 'rgba(255, 255, 255, 0.2)' + }] + }, + options: { + responsive: true, + maintainAspectRatio: true, + plugins: { + legend: { + position: 'bottom', + labels: { + color: 'rgba(255, 255, 255, 0.8)', + padding: 15, + font: { size: 12 } + } + }, + tooltip: { + backgroundColor: 'rgba(0, 0, 0, 0.8)', + padding: 12, + callbacks: { + label: function(context) { + const label = context.label || ''; + const value = context.parsed || 0; + const total = stats.totalRequests; + const percentage = ((value / total) * 100).toFixed(2); + return `${label}: ${value} (${percentage}%)`; + } + } + } + } + } + }); + } + + const dailyRequestsCtx = document.getElementById('dailyRequestsChart'); + if (dailyRequestsCtx && stats.dailyRequests.length > 0) { + apiStatsCharts.dailyRequests = new Chart(dailyRequestsCtx, { + type: 'bar', + data: { + labels: stats.dailyRequests.map(day => { + const date = new Date(day.date); + return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); + }), + datasets: [ + { + label: 'Success', + data: stats.dailyRequests.map(day => day.success), + backgroundColor: 'rgba(40, 167, 69, 0.7)', + borderColor: 'rgba(40, 167, 69, 1)', + borderWidth: 1 + }, + { + label: 'Failed', + data: stats.dailyRequests.map(day => day.fail), + backgroundColor: 'rgba(220, 53, 69, 0.7)', + borderColor: 'rgba(220, 53, 69, 1)', + borderWidth: 1 + } + ] + }, + options: { + responsive: true, + maintainAspectRatio: true, + scales: { + x: { + stacked: true, + ticks: { color: 'rgba(255, 255, 255, 0.8)' }, + grid: { color: 'rgba(255, 255, 255, 0.1)' } + }, + y: { + stacked: true, + ticks: { color: 'rgba(255, 255, 255, 0.8)' }, + grid: { color: 'rgba(255, 255, 255, 0.1)' } + } + }, + plugins: { + legend: { + labels: { color: 'rgba(255, 255, 255, 0.8)' } + }, + tooltip: { + backgroundColor: 'rgba(0, 0, 0, 0.8)', + padding: 12 + } + } + } + }); + } + + const hourlyTrendsCtx = document.getElementById('hourlyTrendsChart'); + if (hourlyTrendsCtx && stats.hourlyRequests.length > 0) { + apiStatsCharts.hourlyTrends = new Chart(hourlyTrendsCtx, { + type: 'line', + data: { + labels: stats.hourlyRequests.map(hour => { + const date = new Date(hour.hour); + return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); + }), + datasets: [{ + label: 'Requests per Hour', + data: stats.hourlyRequests.map(hour => hour.count), + borderColor: 'rgba(133, 48, 48, 1)', + backgroundColor: 'rgba(133, 48, 48, 0.1)', + borderWidth: 2, + fill: true, + tension: 0.4, + pointRadius: 4, + pointBackgroundColor: 'rgba(133, 48, 48, 1)', + pointBorderColor: '#fff', + pointBorderWidth: 2 + }] + }, + options: { + responsive: true, + maintainAspectRatio: true, + scales: { + x: { + ticks: { + color: 'rgba(255, 255, 255, 0.8)', + maxRotation: 45, + minRotation: 45 + }, + grid: { color: 'rgba(255, 255, 255, 0.1)' } + }, + y: { + ticks: { color: 'rgba(255, 255, 255, 0.8)' }, + grid: { color: 'rgba(255, 255, 255, 0.1)' } + } + }, + plugins: { + legend: { + labels: { color: 'rgba(255, 255, 255, 0.8)' } + }, + tooltip: { + backgroundColor: 'rgba(0, 0, 0, 0.8)', + padding: 12 + } + } + } + }); + } +} + +function formatCountdown(hours, minutes, seconds) { + const h = String(hours).padStart(2, '0'); + const m = String(minutes).padStart(2, '0'); + const s = String(seconds).padStart(2, '0'); + return `${h}:${m}:${s}`; +} + +function startCountdownTimer(nextResetTime) { + if (countdownInterval) { + clearInterval(countdownInterval); + } + + const updateCountdown = () => { + const now = new Date(); + const resetTime = new Date(nextResetTime); + const diff = resetTime - now; + + if (diff <= 0) { + loadUserProfile(); + return; + } + + const hours = Math.floor(diff / (1000 * 60 * 60)); + const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); + const seconds = Math.floor((diff % (1000 * 60)) / 1000); + + const countdownElements = document.querySelectorAll('.reset-countdown'); + countdownElements.forEach(el => { + el.textContent = formatCountdown(hours, minutes, seconds); + }); + + const progressBars = document.querySelectorAll('.reset-progress'); + progressBars.forEach(bar => { + const totalDay = 24 * 60 * 60 * 1000; + const elapsed = totalDay - diff; + const percentage = (elapsed / totalDay) * 100; + bar.style.width = `${percentage}%`; + }); + }; + + updateCountdown(); + countdownInterval = setInterval(updateCountdown, 1000); +} + +async function searchUsersForRole(query) { + const resultsDiv = document.getElementById('userSearchResults'); + + if (!query.trim()) { + resultsDiv.style.display = 'none'; + return; + } + + try { + const token = localStorage.getItem('dashx_token'); + const response = await fetch(`/api/admin/search-users?query=${encodeURIComponent(query)}`, { + headers: { 'Authorization': `Bearer ${token}` } + }); + + const result = await response.json(); + if (result.success && result.users.length > 0) { + resultsDiv.innerHTML = result.users.map(user => ` +
+
${user.username}
+
${user.email}
+
Limit: ${user.limit} | ${user.premium ? 'Premium' : 'Free'}
+
+ `).join(''); + resultsDiv.style.display = 'block'; + } else { + resultsDiv.innerHTML = '
No users found
'; + resultsDiv.style.display = 'block'; + } + } catch (error) { + console.error('Search error:', error); + } +} + +function selectUserForRole(userId, username, email) { + selectedUserForRole = userId; + document.getElementById('selectedUserId').value = userId; + document.getElementById('selectedUserDisplay').value = `${username} (${email})`; + document.getElementById('userSearchResults').style.display = 'none'; + document.getElementById('searchUserInput').value = ''; +} + +async function assignUserRole() { + const userId = document.getElementById('selectedUserId').value; + const roleName = document.getElementById('assignRoleType').value; + const customApiKey = document.getElementById('customApiKey').value.trim(); + + if (!userId) { + Swal.fire({ + icon: 'error', + title: 'No User Selected', + text: 'Please search and select a user first' + }); + return; + } + + if (!roleName) { + Swal.fire({ + icon: 'error', + title: 'Invalid Role', + text: 'Please select a role' + }); + return; + } + + try { + const token = localStorage.getItem('dashx_token'); + const response = await fetch('/api/admin/set-role', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify({ userId, roleName, customApiKey }) + }); + + const result = await response.json(); + + if (result.success) { + await Swal.fire({ + icon: 'success', + title: 'Role Assigned!', + html: ` +

Role: ${roleName.toUpperCase()}

+

New Limit: ${result.newLimit}

+

Expires: ${new Date(result.expiresAt).toLocaleString()}

+ ${customApiKey ? `

API Key: ${result.apiKey}

` : ''} + ` + }); + + document.getElementById('selectedUserId').value = ''; + document.getElementById('selectedUserDisplay').value = ''; + document.getElementById('customApiKey').value = ''; + selectedUserForRole = null; + + loadAdminUsers(); + } else { + Swal.fire({ + icon: 'error', + title: 'Assignment Failed', + text: result.error + }); + } + } catch (error) { + Swal.fire({ + icon: 'error', + title: 'Network Error', + text: 'Failed to assign role' + }); + } +} + +async function loadServerStats() { + try { + const token = localStorage.getItem('dashx_token'); + const response = await fetch('/api/server-stats', { + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + } + }); + + const result = await response.json(); + + if (!response.ok) { + console.error('Server stats error:', result.error); + return; + } + + if (result.success) { + document.getElementById('totalUsersCount').textContent = result.stats.totalUsers || 0; + document.getElementById('regularUsersCount').textContent = result.stats.regularUsers || 0; + document.getElementById('premiumUsersCount').textContent = result.stats.premiumUsers || 0; + + document.getElementById('cheapCount').textContent = result.stats.roleDistribution.cheap || 0; + document.getElementById('premiumCount').textContent = result.stats.roleDistribution.premium || 0; + document.getElementById('vipCount').textContent = result.stats.roleDistribution.vip || 0; + document.getElementById('supremeCount').textContent = result.stats.roleDistribution.supreme || 0; + + document.getElementById('dbSize').textContent = result.stats.database.size || '0 MB'; + document.getElementById('ramUsage').textContent = result.stats.system.ramUsage || '0 MB'; + document.getElementById('cpuUsage').textContent = result.stats.system.cpuUsage + '%' || '0%'; + document.getElementById('storageUsed').textContent = result.stats.system.storageUsed || '0 GB'; + + renderRoleChart(result.stats.roleDistribution); + } + } catch (error) { + console.error('Server stats error:', error); + } +} + +function renderRoleChart(roleData) { + const ctx = document.getElementById('roleChart'); + if (!ctx) return; + + if (window.roleChartInstance) { + window.roleChartInstance.destroy(); + } + + window.roleChartInstance = new Chart(ctx, { + type: 'pie', + data: { + labels: ['Regular', 'Cheap', 'Premium', 'VIP', 'Supreme'], + datasets: [{ + data: [ + roleData.regular || 0, + roleData.cheap || 0, + roleData.premium || 0, + roleData.vip || 0, + roleData.supreme || 0 + ], + backgroundColor: [ + 'rgba(108, 117, 125, 0.8)', + 'rgba(255, 193, 7, 0.8)', + 'rgba(133, 48, 48, 0.8)', + 'rgba(255, 215, 0, 0.8)', + 'rgba(138, 43, 226, 0.8)' + ], + borderWidth: 2, + borderColor: 'rgba(255, 255, 255, 0.2)' + }] + }, + options: { + responsive: true, + maintainAspectRatio: true, + plugins: { + legend: { + position: 'bottom', + labels: { + color: 'rgba(255, 255, 255, 0.8)', + padding: 15, + font: { + size: 12 + } + } + }, + tooltip: { + backgroundColor: 'rgba(0, 0, 0, 0.8)', + padding: 12, + titleColor: '#fff', + bodyColor: '#fff', + borderColor: 'rgba(255, 255, 255, 0.2)', + borderWidth: 1 + } + } + } + }); +} + +document.addEventListener('DOMContentLoaded', function() { + if (!checkAuth()) return; + loadUserProfile(); + loadPlugins(); + showSection('overview'); + + const redeemTypeEl = document.getElementById('redeemType'); + if (redeemTypeEl) { + redeemTypeEl.addEventListener('change', updateRedeemFormVisibility); + updateRedeemFormVisibility(); + } + + setInterval(() => { + if (currentUser) { + loadUserProfile(); + if (currentUser.role === 'admin') { + loadAdminStats(); + } + } + }, 5000); + + setTimeout(setupAutoSave, 1000); +}); + +function checkAuth() { + const token = localStorage.getItem('dashx_token'); + const user = JSON.parse(localStorage.getItem('dashx_user') || '{}'); + + if (!token || !user.username) { + window.location.href = '/auth'; + return false; + } + + currentUser = user; + + if (user.role === 'admin') { + const adminMenu = document.getElementById('adminMenu'); + if (adminMenu) { + adminMenu.style.display = 'block'; + } + loadAdminStats(); + } + + loadServerStats(); + + return true; +} + +async function loadUserProfile() { + try { + const token = localStorage.getItem('dashx_token'); + if (!token) { + window.location.href = '/auth'; + return; + } + + const response = await fetch('/api/user/profile', { + headers: { 'Authorization': `Bearer ${token}` } + }); + + if (response.status === 401) { + localStorage.removeItem('dashx_token'); + localStorage.removeItem('dashx_user'); + window.location.href = '/auth'; + return; + } + + if (response.ok) { + const result = await response.json(); + if (result.success) { + const user = result.user; + localStorage.setItem('dashx_user', JSON.stringify(user)); + currentUser = user; + + await loadUsageStats(); + updateProfileElements(user); + + if (user.tempBanned) { + const banUntil = new Date(user.tempBanUntil); + Swal.fire({ + icon: 'warning', + title: 'Account Temporarily Banned', + html: `Your account is temporarily banned until ${banUntil.toLocaleString()}
Reason: ${user.tempBanReason || 'No reason provided'}`, + allowOutsideClick: false + }); + } + } + } + } catch (error) { + console.error('Error loading profile:', error); + } +} + +async function loadUsageStats() { + try { + const token = localStorage.getItem('dashx_token'); + const response = await fetch('/api/user/usage-stats', { + headers: { 'Authorization': `Bearer ${token}` } + }); + + if (response.ok) { + const result = await response.json(); + if (result.success) { + usageStats = result.stats; + updateUsageChart(); + } + } + } catch (error) { + console.error('Error loading usage stats:', error); + const chartElement = document.querySelector('.usage-overview'); + if (chartElement) { + chartElement.innerHTML = ` +
+ +

Unable to load usage statistics

+ +
+ `; + } + } +} + +function updateUsageChart() { + const chartElement = document.querySelector('.usage-overview'); + if (!chartElement || !usageStats) return; + + const hasData = usageStats.topEndpoints.length > 0 || usageStats.dailyUsage.length > 0 || usageStats.recentIPs.length > 0; + + if (!hasData) { + chartElement.innerHTML = ` +
+ +

No API usage data yet

+ Start making API requests to see analytics here +
+ `; + return; + } + + chartElement.innerHTML = ` +
+

Top API Endpoints

+
+ ${usageStats.topEndpoints.length > 0 ? + usageStats.topEndpoints.slice(0, 5).map(ep => ` +
+ ${ep.endpoint || 'Unknown'} + ${ep.count} requests +
+ `).join('') : + '
No endpoints used yet
' + } +
+
+ +
+

Daily Usage (Last 7 Days)

+
+ ${usageStats.dailyUsage.length > 0 ? + usageStats.dailyUsage.map(day => ` +
+
${new Date(day.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}
+
+
+
+
${day.limitUsed || 0}
+
+ `).join('') : + '
No usage data available
' + } +
+
+ +
+

Recent IP Addresses

+
+ ${usageStats.recentIPs.length > 0 ? + usageStats.recentIPs.slice(0, 5).map(ip => ` +
+
+ ${ip.address} +
${ip.timeAgo}
+
+ ${ip.count} requests +
+ `).join('') : + '
No IP data available
' + } +
+
+ `; +} + +function updateProfileElements(user) { + const apiKeyElement = document.getElementById('userApiKey'); + const requestsElement = document.getElementById('userRequests'); + const todayElement = document.getElementById('userRequestsToday'); + const limitElement = document.getElementById('userLimit'); + const limitTodayElement = document.getElementById('userLimitToday'); + + if (apiKeyElement) { + if (user.role === 'admin' && user.apikey === 'DHX-M3SA') { + apiKeyElement.textContent = user.apikey; + apiKeyElement.style.fontSize = '0.9rem'; + apiKeyElement.style.fontWeight = 'bold'; + apiKeyElement.style.color = '#853030'; + } else { + apiKeyElement.textContent = user.apikey || 'N/A'; + } + } + + if (requestsElement && usageStats) { + requestsElement.textContent = formatNumber(usageStats.totalRequests || 0); + } + + if (todayElement) { + const todayRequests = user.requestsToday || 0; + todayElement.textContent = formatNumber(todayRequests); + } + + if (limitElement) { + if (user.role === 'admin' && user.limit >= 9999) { + limitElement.textContent = 'āˆž'; + limitElement.style.fontSize = '2.5rem'; + limitElement.style.color = '#853030'; + limitElement.style.fontWeight = 'bold'; + } else { + limitElement.textContent = formatNumber(user.limit || 30); + } + } + + if (limitTodayElement) { + if (user.role === 'admin' && user.limit >= 9999) { + limitTodayElement.textContent = 'āˆž'; + limitTodayElement.style.fontSize = '2.5rem'; + limitTodayElement.style.color = '#853030'; + limitTodayElement.style.fontWeight = 'bold'; + } else { + const requestsToday = user.requestsToday || 0; + const totalLimit = user.limit || 30; + const remainingLimit = Math.max(0, totalLimit - requestsToday); + + limitTodayElement.textContent = formatNumber(remainingLimit); + + const percentage = (remainingLimit / totalLimit) * 100; + + if (remainingLimit <= 0) { + limitTodayElement.style.color = '#ff6b6b'; + } else if (percentage <= 10) { + limitTodayElement.style.color = '#ff6b6b'; + } else if (percentage <= 30) { + limitTodayElement.style.color = '#ffa500'; + } else if (percentage <= 50) { + limitTodayElement.style.color = '#ffeb3b'; + } else { + limitTodayElement.style.color = '#4caf50'; + } + } + } + + if (user.nextResetTime) { + startCountdownTimer(user.nextResetTime); + } + + if (user.premium || user.role === 'admin') { + const premiumSettings = document.getElementById('premiumSettings'); + if (premiumSettings) { + premiumSettings.style.display = 'block'; + } + } +} + +function formatNumber(num) { + if (typeof num !== 'number') return '0'; + + if (num >= 1000000) { + return (num / 1000000).toFixed(1) + 'M'; + } else if (num >= 1000) { + return (num / 1000).toFixed(1) + 'K'; + } + return num.toString(); +} + +async function loadPlugins() { + try { + const response = await fetch('/api/plugins'); + const result = await response.json(); + + if (result.success) { + pluginsData = result.plugins; + renderPluginsByTags(pluginsData); + } + } catch (error) { + console.error('Error loading plugins:', error); + const pluginsGrid = document.getElementById('pluginsGrid'); + if (pluginsGrid) { + pluginsGrid.innerHTML = ` +
+ + Error loading plugins: ${error.message} +
+ `; + } + } +} + +function renderPluginsByTags(plugins) { + const pluginsGrid = document.getElementById('pluginsGrid'); + if (!pluginsGrid) return; + + if (plugins.length === 0) { + pluginsGrid.innerHTML = ` +
+ +

No plugins available at the moment

+
+ `; + return; + } + + const pluginsByTag = {}; + const noTagPlugins = []; + + plugins.forEach((plugin, index) => { + plugin.originalIndex = index; + + if (plugin.main && Array.isArray(plugin.main) && plugin.main.length > 0) { + plugin.main.forEach(tag => { + if (!pluginsByTag[tag]) { + pluginsByTag[tag] = []; + } + pluginsByTag[tag].push(plugin); + }); + } else { + noTagPlugins.push(plugin); + } + }); + + let html = ''; + + if (noTagPlugins.length > 0) { + html += ` +
+
+
+ + No Tag + ${noTagPlugins.length} plugins +
+ +
+
+ ${noTagPlugins.map(plugin => createPluginCardHTML(plugin, plugin.originalIndex)).join('')} +
+
+ `; + } + + Object.keys(pluginsByTag).sort().forEach(tag => { + const tagPlugins = pluginsByTag[tag]; + const tagId = tag.replace(/[^a-zA-Z0-9]/g, '-').toLowerCase(); + + html += ` +
+
+
+ + ${tag} + ${tagPlugins.length} plugins +
+ +
+
+ ${tagPlugins.map(plugin => createPluginCardHTML(plugin, plugin.originalIndex)).join('')} +
+
+ `; + }); + + pluginsGrid.innerHTML = html; +} + +function toggleTagSection(tagId) { + const content = document.getElementById(`content-${tagId}`); + const expand = document.getElementById(`expand-${tagId}`); + + if (!content || !expand) return; + + const isExpanded = content.classList.contains('expanded'); + + if (isExpanded) { + content.classList.remove('expanded'); + expand.classList.remove('expanded'); + } else { + content.classList.add('expanded'); + expand.classList.add('expanded'); + } +} + +function searchPlugins(query) { + if (!query.trim()) { + renderPluginsByTags(pluginsData); + return; + } + + const filtered = pluginsData.filter(plugin => + plugin.name.toLowerCase().includes(query.toLowerCase()) || + plugin.description.toLowerCase().includes(query.toLowerCase()) || + (plugin.tags && plugin.tags.some(tag => tag.toLowerCase().includes(query.toLowerCase()))) || + (plugin.main && plugin.main.some(tag => tag.toLowerCase().includes(query.toLowerCase()))) + ); + + renderPluginsByTags(filtered); +} + +function createPluginCardHTML(plugin, index) { + const user = JSON.parse(localStorage.getItem('dashx_user') || '{}'); + const userApiKey = user.apikey || 'N/A'; + + const parametersHTML = (plugin.parameters || []) + .filter(param => param !== 'key') + .map(param => ` +
+ + +
+ `).join(''); + + const keyFieldHTML = ` +
+ + +
+ `; + + return ` +
+
+
+
+
${plugin.name}
+
${plugin.description}
+
+ Limit Cost: ${plugin.limit || 1} +
+
+
+
+
${plugin.type}
+ +
+
+
+
+
+ ${(plugin.tags || []).map(tag => `${tag}`).join('')} +
+
+ ${window.location.origin}/${plugin.routes[0] || 'unknown'} +
+ +
+
+ + Parameters +
+
+
+ ${parametersHTML} + ${keyFieldHTML} +
+
+ + +
+
+
+ +
+
+
+ + Response +
+
+
+ +
+
+
+
+
+
+
+ `; +} + +function getPlaceholder(param) { + const placeholders = { + 'url': 'https://example.com', + 'sitekey': '0x4AAAAAAAdJZmNxW54o-Gvd', + 'text': 'Your text here', + 'size': '200', + 'format': 'png', + 'query': 'search term', + 'username': 'username', + 'type': 'words', + 'count': '10', + 'length': '12', + 'wordType': 'lorem' + }; + return placeholders[param] || `Enter ${param}`; + } + + function togglePlugin(index) { + const content = document.getElementById(`content-${index}`); + const expand = document.getElementById(`expand-${index}`); + const card = content ? content.parentElement : null; + + if (!content || !expand) { + return; + } + + const isExpanded = content.classList.contains('expanded'); + + if (isExpanded) { + content.classList.remove('expanded'); + expand.classList.remove('expanded'); + if (card) card.classList.remove('expanded'); + } else { + content.classList.add('expanded'); + expand.classList.add('expanded'); + if (card) card.classList.add('expanded'); + } + } + + function updatePluginURL(index, route, method) { + const form = document.getElementById(`form-${index}`); + const urlElement = document.getElementById(`plugin-url-${index}`); + + if (!form || !urlElement) return; + + const baseURL = `${window.location.origin}/${route}`; + + if (!urlElement.getAttribute('data-base-url')) { + urlElement.setAttribute('data-base-url', baseURL); + } + + const formData = new FormData(form); + const params = {}; + + for (let [key, value] of formData.entries()) { + if (value.trim()) { + params[key] = value; + } + } + + if (Object.keys(params).length === 0 || (Object.keys(params).length === 1 && params.key)) { + urlElement.textContent = baseURL; + return; + } + + if (method === 'GET') { + const queryString = new URLSearchParams(params).toString(); + urlElement.textContent = `${baseURL}?${queryString}`; + } else { + urlElement.textContent = baseURL; + } + } + + function copyPluginURL(index) { + const urlElement = document.getElementById(`plugin-url-${index}`); + if (!urlElement) return; + + const url = urlElement.textContent; + + navigator.clipboard.writeText(url).then(() => { + const originalBg = urlElement.style.background; + urlElement.style.background = 'rgba(40, 167, 69, 0.2)'; + + Swal.fire({ + title: "Copied!", + text: "URL copied to clipboard", + icon: "success", + timer: 1500, + showConfirmButton: false + }); + + setTimeout(() => { + urlElement.style.background = originalBg; + }, 1000); + }).catch(() => { + Swal.fire({ + icon: "error", + title: "Copy Failed", + text: "Could not copy URL to clipboard" + }); + }); + } + + async function executePlugin(event, index, route, method) { + event.preventDefault(); + + const form = document.getElementById(`form-${index}`); + const executeBtn = document.getElementById(`execute-${index}`); + const responseContainer = document.getElementById(`response-${index}`); + const responseBody = document.getElementById(`response-body-${index}`); + const statusElement = document.getElementById(`status-${index}`); + + if (!form || !executeBtn || !responseContainer || !responseBody || !statusElement) { + return; + } + + const formData = new FormData(form); + const params = Object.fromEntries(formData); + + executeBtn.disabled = true; + executeBtn.innerHTML = '
Executing...'; + + try { + const url = method === 'GET' ? + `/${route}?${new URLSearchParams(params).toString()}` : + `/${route}`; + + const options = { + method: method, + headers: method === 'POST' ? { 'Content-Type': 'application/json' } : {} + }; + + if (method === 'POST') { + options.body = JSON.stringify(params); + } + + const response = await fetch(url, options); + const result = await response.json(); + + responseContainer.classList.add('show'); + responseBody.textContent = JSON.stringify(result, null, 2); + + if (response.ok && result.success) { + statusElement.textContent = 'Success'; + statusElement.className = 'response-status success'; + + Swal.fire({ + title: "Success!", + text: "API request executed successfully", + icon: "success", + timer: 2000, + showConfirmButton: false + }); + + await loadUserProfile(); + await loadUsageStats(); + } else { + statusElement.textContent = 'Error'; + statusElement.className = 'response-status error'; + + Swal.fire({ + icon: "error", + title: "Request Failed", + text: result.error || "Unknown error occurred" + }); + } + + } catch (error) { + responseContainer.classList.add('show'); + responseBody.textContent = JSON.stringify({ error: error.message }, null, 2); + statusElement.textContent = 'Error'; + statusElement.className = 'response-status error'; + + Swal.fire({ + icon: "error", + title: "Network Error", + text: "Failed to connect to the API" + }); + } finally { + executeBtn.disabled = false; + executeBtn.innerHTML = ' Execute'; + } + } + + function resetForm(index) { + const form = document.getElementById(`form-${index}`); + const responseContainer = document.getElementById(`response-${index}`); + + if (form) { + const user = JSON.parse(localStorage.getItem('dashx_user') || '{}'); + const userApiKey = user.apikey || 'N/A'; + + form.reset(); + + const keyInput = form.querySelector('input[name="key"]'); + if (keyInput) { + keyInput.value = userApiKey; + keyInput.placeholder = userApiKey; + } + } + if (responseContainer) responseContainer.classList.remove('show'); + + const urlElement = document.getElementById(`plugin-url-${index}`); + if (urlElement) { + const baseURL = urlElement.getAttribute('data-base-url'); + if (baseURL) { + urlElement.textContent = baseURL; + } + } + } + + function copyResponse(index) { + const responseBody = document.getElementById(`response-body-${index}`); + if (!responseBody) return; + + const text = responseBody.textContent; + + navigator.clipboard.writeText(text).then(() => { + Swal.fire({ + title: "Copied!", + text: "Response copied to clipboard", + icon: "success", + timer: 1500, + showConfirmButton: false + }); + }).catch(() => { + Swal.fire({ + icon: "error", + title: "Copy Failed", + text: "Could not copy to clipboard" + }); + }); + } + + async function loadAdminStats() { + try { + const token = localStorage.getItem('dashx_token'); + const response = await fetch('/api/stats', { + headers: { 'Authorization': `Bearer ${token}` } + }); + + if (response.ok) { + const result = await response.json(); + if (result.success) { + const totalUsersEl = document.getElementById('adminTotalUsers'); + const totalRequestsEl = document.getElementById('adminTotalRequests'); + const todayRequestsEl = document.getElementById('adminTodayRequests'); + + if (totalUsersEl) totalUsersEl.textContent = formatNumber(result.stats.totalUsers || 0); + if (totalRequestsEl) totalRequestsEl.textContent = formatNumber(result.stats.totalRequests || 0); + if (todayRequestsEl) todayRequestsEl.textContent = formatNumber(result.stats.todayRequests || 0); + } + } + } catch (error) { + console.error('Error loading admin stats:', error); + } + } + + async function loadAdminUsers() { + try { + const token = localStorage.getItem('dashx_token'); + const response = await fetch('/api/admin/users', { + headers: { 'Authorization': `Bearer ${token}` } + }); + + if (response.ok) { + const result = await response.json(); + if (result.success) { + adminUsers = result.users || []; + currentPage = 0; + renderAdminUsers(); + } + } + } catch (error) { + console.error('Error loading admin users:', error); + const usersList = document.getElementById('adminUsersList'); + if (usersList) { + usersList.innerHTML = ` +
+ +

Failed to load users

+ +
+ `; + } + } + } + + function renderAdminUsers() { + const usersList = document.getElementById('adminUsersList'); + if (!usersList) return; + + if (adminUsers.length === 0) { + usersList.innerHTML = ` +
+ +

No users found

+
+ `; + return; + } + + const startIndex = currentPage * usersPerPage; + const endIndex = startIndex + usersPerPage; + const currentUsers = adminUsers.slice(startIndex, endIndex); + const totalPages = Math.ceil(adminUsers.length / usersPerPage); + + usersList.innerHTML = ` +
+ Showing ${startIndex + 1}-${Math.min(endIndex, adminUsers.length)} of ${adminUsers.length} users +
+ + ${currentUsers.map(user => ` +
+
+
${user.username || 'Unknown User'}
+
+ ${user.email || 'No email'} + ${user.role || 'user'} + ${user.premium ? 'Premium' : ''} +
+
+ Requests: ${user.requests || 0} + Today: ${user.requestsToday || 0} + Limit: ${user.limit || 30} +
+ ${user.tempBanned ? ` +
+ Banned until: ${new Date(user.tempBanUntil).toLocaleString()}
+ Reason: ${user.tempBanReason || 'No reason provided'} +
+ ` : ''} +
+
+ ${!user.tempBanned ? ` + + ` : ` + + `} +
+
+ `).join('')} + + ${totalPages > 1 ? ` +
+ + + + Page ${currentPage + 1} of ${totalPages} + + + +
+ ` : ''} + `; + } + + function changePage(page) { + const totalPages = Math.ceil(adminUsers.length / usersPerPage); + if (page >= 0 && page < totalPages) { + currentPage = page; + renderAdminUsers(); + } + } + + function showTempBanModal(userId, username) { + Swal.fire({ + title: `Temporary Ban - ${username}`, + html: ` +
+ + + + +
+ `, + showCancelButton: true, + confirmButtonText: 'Apply Ban', + confirmButtonColor: '#ff6b6b', + preConfirm: () => { + const banUntil = document.getElementById('banUntil').value; + const reason = document.getElementById('banReason').value; + + if (!banUntil) { + Swal.showValidationMessage('Please select ban until date/time'); + return false; + } + + return { banUntil, reason }; + } + }).then((result) => { + if (result.isConfirmed) { + applyTempBan(userId, result.value.banUntil, result.value.reason); + } + }); + } + + async function applyTempBan(userId, banUntil, reason) { + try { + const token = localStorage.getItem('dashx_token'); + const response = await fetch('/api/admin/temp-ban', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify({ userId, banUntil, reason }) + }); + + const result = await response.json(); + + if (result.success) { + Swal.fire({ + icon: 'success', + title: 'User Banned', + text: result.message + }); + loadAdminUsers(); + } else { + Swal.fire({ + icon: 'error', + title: 'Ban Failed', + text: result.error + }); + } + } catch (error) { + Swal.fire({ + icon: 'error', + title: 'Network Error', + text: 'Failed to apply ban' + }); + } + } + + async function removeTempBan(userId, username) { + const result = await Swal.fire({ + title: `Remove Ban - ${username}`, + text: 'Are you sure you want to remove the temporary ban?', + icon: 'question', + showCancelButton: true, + confirmButtonText: 'Remove Ban', + confirmButtonColor: '#28a745' + }); + + if (result.isConfirmed) { + try { + const token = localStorage.getItem('dashx_token'); + const response = await fetch('/api/admin/remove-temp-ban', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify({ userId }) + }); + + const apiResult = await response.json(); + + if (apiResult.success) { + Swal.fire({ + icon: 'success', + title: 'Ban Removed', + text: apiResult.message + }); + loadAdminUsers(); + } else { + Swal.fire({ + icon: 'error', + title: 'Failed', + text: apiResult.error + }); + } + } catch (error) { + Swal.fire({ + icon: 'error', + title: 'Network Error', + text: 'Failed to remove ban' + }); + } + } + } + + function showSection(sectionName) { + document.querySelectorAll('.section').forEach(section => { + section.classList.remove('active'); + }); + + document.querySelectorAll('.menu-item').forEach(item => { + item.classList.remove('active'); + }); + + const targetSection = document.getElementById(sectionName); + const targetMenuItem = document.querySelector(`[onclick="showSection('${sectionName}')"]`); + + if (targetSection) { + targetSection.classList.add('active'); + } + + if (targetMenuItem) { + targetMenuItem.classList.add('active'); + } + + if (window.serverStatsInterval) { + clearInterval(window.serverStatsInterval); + window.serverStatsInterval = null; + } + + if (sectionName === 'plugins') { + loadPlugins(); + } else if (sectionName === 'overview') { + loadUsageStats(); + } + + else if (sectionName === 'api-stats') { + loadApiStats(); + }else if (sectionName === 'management' && currentUser && currentUser.role === 'admin') { + loadAdminStats(); + loadAdminUsers(); + } else if (sectionName === 'server-stats') { + loadServerStats(); + + window.serverStatsInterval = setInterval(() => { + loadServerStats(); + }, 5000); + } + } + + async function redeemCode() { + const codeInput = document.getElementById('redeemCode'); + if (!codeInput) { + return; + } + + const code = codeInput.value.trim().toUpperCase(); + + if (!code) { + Swal.fire({ + icon: "error", + title: "Missing Code", + text: "Please enter a redeem code" + }); + return; + } + + try { + const token = localStorage.getItem('dashx_token'); + const response = await fetch('/api/user/redeem', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify({ code }) + }); + const result = await response.json(); + + if (result.success) { + Swal.fire({ + title: "Code Redeemed Successfully!", + text: "Your account has been updated", + icon: "success" + }); + codeInput.value = ''; + loadUserProfile(); + } else { + Swal.fire({ + icon: "error", + title: "Redeem Failed", + text: result.error + }); + } + } catch (error) { + Swal.fire({ + icon: "error", + title: "Network Error", + text: "Please try again later" + }); + } + } + + async function updateProfile() { + const usernameInput = document.getElementById('usernameInput'); + if (!usernameInput) { + return; + } + + const username = usernameInput.value.trim(); + + if (!username) { + Swal.fire({ + icon: "error", + title: "Missing Username", + text: "Please enter a new username" + }); + return; + } + + try { + const token = localStorage.getItem('dashx_token'); + const response = await fetch('/api/user/profile', { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify({ username }) + }); + + const result = await response.json(); + + if (result.success) { + Swal.fire({ + title: "Profile Updated!", + text: "Your username has been updated successfully", + icon: "success" + }); + usernameInput.value = ''; + + const user = JSON.parse(localStorage.getItem('dashx_user')); + user.username = username; + localStorage.setItem('dashx_user', JSON.stringify(user)); + currentUser = user; + + loadUserProfile(); + } else { + Swal.fire({ + icon: "error", + title: "Update Failed", + text: result.error + }); + } + } catch (error) { + Swal.fire({ + icon: "error", + title: "Network Error", + text: "Please try again later" + }); + } + } + + async function regenerateApiKey() { + const result = await Swal.fire({ + title: "Are you sure?", + text: "This will generate a new API key and invalidate the current one!", + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#853030", + cancelButtonColor: "#292727", + confirmButtonText: "Yes, generate new key!", + cancelButtonText: "Cancel" + }); + + if (result.isConfirmed) { + try { + const token = localStorage.getItem('dashx_token'); + const response = await fetch('/api/user/regenerate-key', { + method: 'POST', + headers: { 'Authorization': `Bearer ${token}` } + }); + + const apiResult = await response.json(); + + if (apiResult.success) { + Swal.fire({ + title: "Key Regenerated!", + text: `New API Key: ${apiResult.apikey}`, + icon: "success" + }); + + const user = JSON.parse(localStorage.getItem('dashx_user')); + user.apikey = apiResult.apikey; + localStorage.setItem('dashx_user', JSON.stringify(user)); + currentUser = user; + + loadUserProfile(); + } else { + Swal.fire({ + title: "Failed!", + text: apiResult.error, + icon: "error" + }); + } + } catch (error) { + Swal.fire({ + title: "Error!", + text: "Network error occurred", + icon: "error" + }); + } + } + } + + async function deleteAccount() { + const result = await Swal.fire({ + title: "Are you sure?", + text: "You won't be able to revert this action!", + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#ff6b6b", + cancelButtonColor: "#853030", + confirmButtonText: "Yes, delete my account!", + cancelButtonText: "Cancel" + }); + + if (result.isConfirmed) { + try { + const token = localStorage.getItem('dashx_token'); + const response = await fetch('/api/user/account', { + method: 'DELETE', + headers: { 'Authorization': `Bearer ${token}` } + }); + + const apiResult = await response.json(); + + if (apiResult.success) { + await Swal.fire({ + title: "Account Deleted!", + text: "Your account has been permanently deleted.", + icon: "success" + }); + logout(); + } else { + Swal.fire({ + title: "Delete Failed!", + text: apiResult.error, + icon: "error" + }); + } + } catch (error) { + Swal.fire({ + title: "Error!", + text: "Network error occurred", + icon: "error" + }); + } + } + } + + function updateRedeemFormVisibility() { + const typeEl = document.getElementById('redeemType'); + const limitContainer = document.getElementById('limitContainer'); + const codeExpiredContainer = document.getElementById('codeExpiredContainer'); + const premiumExpiredContainer = document.getElementById('premiumExpiredContainer'); + const limitValueEl = document.getElementById('limitValue'); + + if (!typeEl) return; + + const type = typeEl.value; + + if (limitContainer) limitContainer.style.display = 'none'; + if (codeExpiredContainer) codeExpiredContainer.style.display = 'none'; + if (premiumExpiredContainer) premiumExpiredContainer.style.display = 'none'; + + if (type === 'premium') { + if (limitContainer) { + limitContainer.style.display = 'block'; + const label = limitContainer.querySelector('label'); + if (label) label.innerHTML = 'Limit Additional (Optional)'; + if (limitValueEl) { + limitValueEl.required = false; + limitValueEl.placeholder = '0 or any value'; + limitValueEl.min = '0'; + } + } + if (codeExpiredContainer) codeExpiredContainer.style.display = 'block'; + if (premiumExpiredContainer) premiumExpiredContainer.style.display = 'block'; + + } else if (type === 'limit') { + if (limitContainer) { + limitContainer.style.display = 'block'; + const label = limitContainer.querySelector('label'); + if (label) label.innerHTML = 'Limit *'; + if (limitValueEl) { + limitValueEl.required = true; + limitValueEl.placeholder = 'Must be greater than 0'; + limitValueEl.min = '1'; + } + } + if (codeExpiredContainer) codeExpiredContainer.style.display = 'block'; + + } else if (type === 'both') { + if (limitContainer) { + limitContainer.style.display = 'block'; + const label = limitContainer.querySelector('label'); + if (label) label.innerHTML = 'Limit *'; + if (limitValueEl) { + limitValueEl.required = true; + limitValueEl.placeholder = 'Must be greater than 0'; + limitValueEl.min = '1'; + } + } + if (codeExpiredContainer) codeExpiredContainer.style.display = 'block'; + if (premiumExpiredContainer) premiumExpiredContainer.style.display = 'block'; + } + } + + async function createRedeemCode() { + const typeEl = document.getElementById('redeemType'); + const limitValueEl = document.getElementById('limitValue'); + const codeExpiredEl = document.getElementById('codeExpired'); + const premiumExpiredEl = document.getElementById('premiumExpired'); + + if (!typeEl || !codeExpiredEl) return; + + const type = typeEl.value; + const codeExpired = codeExpiredEl.value; + + if (!type || !codeExpired) { + Swal.fire({ + icon: "error", + title: "Invalid Input", + text: "Please fill all required fields" + }); + return; + } + + const codeExpiredDate = new Date(codeExpired); + if (codeExpiredDate <= new Date()) { + Swal.fire({ + icon: "error", + title: "Invalid Date", + text: "Code expiration date must be in the future" + }); + return; + } + + let limitValue = 0; + let premiumExpired = null; + + if (type === 'premium') { + if (!limitValueEl || !premiumExpiredEl) return; + + limitValue = parseInt(limitValueEl.value) || 0; + premiumExpired = premiumExpiredEl.value; + + if (!premiumExpired) { + Swal.fire({ + icon: "error", + title: "Missing Premium Expiry", + text: "Premium expiration date is required for Premium Only type" + }); + return; + } + + const premiumExpiredDate = new Date(premiumExpired); + if (premiumExpiredDate <= new Date()) { + Swal.fire({ + icon: "error", + title: "Invalid Date", + text: "Premium expiration date must be in the future" + }); + return; + } + + } else if (type === 'limit') { + if (!limitValueEl) return; + + limitValue = parseInt(limitValueEl.value) || 0; + + if (limitValue <= 0) { + Swal.fire({ + icon: "error", + title: "Invalid Limit", + text: "Limit value must be greater than 0 for Limit Only type" + }); + return; + } + + } else if (type === 'both') { + if (!limitValueEl || !premiumExpiredEl) return; + + limitValue = parseInt(limitValueEl.value) || 0; + premiumExpired = premiumExpiredEl.value; + + if (limitValue <= 0) { + Swal.fire({ + icon: "error", + title: "Invalid Limit", + text: "Limit value must be greater than 0 for Both type" + }); + return; + } + + if (!premiumExpired) { + Swal.fire({ + icon: "error", + title: "Missing Premium Expiry", + text: "Premium expiration date is required for Both type" + }); + return; + } + + const premiumExpiredDate = new Date(premiumExpired); + if (premiumExpiredDate <= new Date()) { + Swal.fire({ + icon: "error", + title: "Invalid Date", + text: "Premium expiration date must be in the future" + }); + return; + } + } + + try { + const token = localStorage.getItem('dashx_token'); + const response = await fetch('/api/admin/redeem-code', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify({ + type, + limitValue, + codeExpired, + premiumExpired + }) + }); + + const result = await response.json(); + + if (result.success) { + let benefitsHtml = `

Type: ${type}

`; + + if (limitValue > 0) { + benefitsHtml += `

Limit Value: +${limitValue}

`; + } + + benefitsHtml += `

Code Valid Until: ${new Date(codeExpired).toLocaleString()}

`; + + if (premiumExpired) { + benefitsHtml += `

Premium Valid Until: ${new Date(premiumExpired).toLocaleString()}

`; + } + + await Swal.fire({ + title: "Redeem Code Created!", + html: ` +
+ ${result.code} +
+ ${benefitsHtml} + `, + icon: "success" + }); + + if (limitValueEl) limitValueEl.value = ''; + if (codeExpiredEl) codeExpiredEl.value = ''; + if (premiumExpiredEl) premiumExpiredEl.value = ''; + typeEl.selectedIndex = 0; + + updateRedeemFormVisibility(); + } else { + Swal.fire({ + icon: "error", + title: "Creation Failed", + text: result.error + }); + } + } catch (error) { + Swal.fire({ + icon: "error", + title: "Network Error", + text: "Please try again later" + }); + } + } + + function logout() { + localStorage.removeItem('dashx_token'); + localStorage.removeItem('dashx_user'); + currentUser = null; + + if (countdownInterval) { + clearInterval(countdownInterval); + } + + if (window.serverStatsInterval) { + clearInterval(window.serverStatsInterval); + } + + window.location.href = '/'; + } + + function goToProfile() { + window.location.href = '/profile'; + } + + function debounce(func, wait) { + let timeout; + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; + } + + function setupAutoSave() { + const forms = document.querySelectorAll('form'); + forms.forEach(form => { + const inputs = form.querySelectorAll('input, select, textarea'); + inputs.forEach(input => { + input.addEventListener('input', debounce(() => { + const formData = new FormData(form); + const data = Object.fromEntries(formData); + localStorage.setItem(`form_${form.id}`, JSON.stringify(data)); + }, 1000)); + }); + }); + } + + window.addEventListener('error', (event) => { + console.error('Uncaught error:', event.error); + }); + + window.addEventListener('online', () => { + if (currentUser) { + loadUserProfile(); + loadUsageStats(); + } + }); + + window.addEventListener('offline', () => { + Swal.fire({ + icon: 'warning', + title: 'Connection Lost', + text: 'You are currently offline. Some features may not work.', + toast: true, + position: 'top-end', + showConfirmButton: false, + timer: 3000 + }); + }); + + window.addEventListener('beforeunload', () => { + if (countdownInterval) { + clearInterval(countdownInterval); + } + + if (window.serverStatsInterval) { + clearInterval(window.serverStatsInterval); + } + }); + + window.addEventListener('visibilitychange', () => { + if (document.hidden) { + if (window.serverStatsInterval) { + clearInterval(window.serverStatsInterval); + window.serverStatsInterval = null; + } + } else { + const activeSection = document.querySelector('.section.active'); + if (activeSection && activeSection.id === 'server-stats') { + loadServerStats(); + window.serverStatsInterval = setInterval(() => { + loadServerStats(); + }, 5000); + } + } + }); + + if ('serviceWorker' in navigator) { + window.addEventListener('load', () => { + navigator.serviceWorker.register('/sw.js').catch(() => {}); + }); + } + + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape') { + document.querySelectorAll('.plugin-content.expanded').forEach(content => { + content.classList.remove('expanded'); + const index = content.id.replace('content-', ''); + const expand = document.getElementById(`expand-${index}`); + if (expand) expand.classList.remove('expanded'); + }); + + document.querySelectorAll('.tag-content.expanded').forEach(content => { + content.classList.remove('expanded'); + const tagId = content.id.replace('content-', ''); + const expand = document.getElementById(`expand-${tagId}`); + if (expand) expand.classList.remove('expanded'); + }); + } + }); + + let touchStartY = 0; + let touchEndY = 0; + + document.addEventListener('touchstart', (e) => { + touchStartY = e.changedTouches[0].screenY; + }, false); + + document.addEventListener('touchend', (e) => { + touchEndY = e.changedTouches[0].screenY; + handleSwipe(); + }, false); + + function handleSwipe() { + const swipeThreshold = 100; + const swipeDistance = touchStartY - touchEndY; + + if (Math.abs(swipeDistance) > swipeThreshold) { + if (swipeDistance > 0) { + console.log('Swiped up'); + } else { + console.log('Swiped down'); + } + } + } + + const observerOptions = { + root: null, + rootMargin: '0px', + threshold: 0.1 + }; + + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.classList.add('fade-in'); + } + }); + }, observerOptions); + + document.querySelectorAll('.stat-card, .admin-card, .plugin-card').forEach(card => { + observer.observe(card); + }); + + function validateEmail(email) { + const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return re.test(email); + } + + function validateUsername(username) { + const re = /^[a-zA-Z0-9_-]{3,20}$/; + return re.test(username); + } + + function sanitizeInput(input) { + const div = document.createElement('div'); + div.textContent = input; + return div.innerHTML; + } + + function formatDate(date) { + const options = { + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }; + return new Date(date).toLocaleDateString('en-US', options); + } + + function formatBytes(bytes, decimals = 2) { + if (bytes === 0) return '0 Bytes'; + + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; + + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; + } + + function getTimeAgo(timestamp) { + const now = new Date(); + const past = new Date(timestamp); + const diffMs = now - past; + const diffSecs = Math.floor(diffMs / 1000); + const diffMins = Math.floor(diffSecs / 60); + const diffHours = Math.floor(diffMins / 60); + const diffDays = Math.floor(diffHours / 24); + + if (diffSecs < 60) return 'Just now'; + if (diffMins < 60) return `${diffMins} minute${diffMins > 1 ? 's' : ''} ago`; + if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`; + if (diffDays < 7) return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`; + + return formatDate(timestamp); + } + + function copyToClipboard(text) { + return navigator.clipboard.writeText(text).then(() => { + return true; + }).catch(() => { + const textArea = document.createElement('textarea'); + textArea.value = text; + textArea.style.position = 'fixed'; + textArea.style.left = '-999999px'; + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + + try { + const successful = document.execCommand('copy'); + document.body.removeChild(textArea); + return successful; + } catch (err) { + document.body.removeChild(textArea); + return false; + } + }); + } + + function throttle(func, limit) { + let inThrottle; + return function() { + const args = arguments; + const context = this; + if (!inThrottle) { + func.apply(context, args); + inThrottle = true; + setTimeout(() => inThrottle = false, limit); + } + }; + } + + const scrollHandler = throttle(() => { + const scrollTop = window.pageYOffset || document.documentElement.scrollTop; + const navbar = document.querySelector('.navbar'); + + if (navbar) { + if (scrollTop > 100) { + navbar.style.boxShadow = '0 4px 30px rgba(0, 0, 0, 0.3)'; + } else { + navbar.style.boxShadow = '0 2px 20px rgba(0, 0, 0, 0.2)'; + } + } + }, 100); + + window.addEventListener('scroll', scrollHandler); + + function showNotification(title, message, type = 'info') { + const icon = type === 'success' ? 'success' : + type === 'error' ? 'error' : + type === 'warning' ? 'warning' : 'info'; + + Swal.fire({ + toast: true, + position: 'top-end', + icon: icon, + title: title, + text: message, + showConfirmButton: false, + timer: 3000, + timerProgressBar: true + }); + } + + function detectDeviceType() { + const ua = navigator.userAgent; + if (/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i.test(ua)) { + return 'tablet'; + } + if (/Mobile|Android|iP(hone|od)|IEMobile|BlackBerry|Kindle|Silk-Accelerated|(hpw|web)OS|Opera M(obi|ini)/.test(ua)) { + return 'mobile'; + } + return 'desktop'; + } + + const deviceType = detectDeviceType(); + document.body.classList.add(`device-${deviceType}`); + + function preloadImages(urls) { + urls.forEach(url => { + const img = new Image(); + img.src = url; + }); + } + + preloadImages([ + 'https://files.catbox.moe/8l6hhm', + 'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.4.0/svgs/solid/key.svg', + 'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.4.0/svgs/solid/chart-pie.svg' + ]); + + if ('Notification' in window && Notification.permission === 'default') { + Notification.requestPermission(); + } + + function sendBrowserNotification(title, body) { + if ('Notification' in window && Notification.permission === 'granted') { + new Notification(title, { + body: body, + icon: '/icon.webp', + badge: '/icon.webp' + }); + } + } + + const performanceObserver = new PerformanceObserver((list) => { + for (const entry of list.getEntries()) { + if (entry.duration > 1000) { + console.warn(`Slow operation detected: ${entry.name} took ${entry.duration}ms`); + } + } + }); + + try { + performanceObserver.observe({ entryTypes: ['measure', 'navigation'] }); + } catch (e) { + console.log('Performance observer not supported'); + } + + function measurePerformance(name, fn) { + performance.mark(`${name}-start`); + const result = fn(); + performance.mark(`${name}-end`); + performance.measure(name, `${name}-start`, `${name}-end`); + return result; + } + + const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; + if (isDarkMode) { + document.body.classList.add('dark-mode'); + } + + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { + if (e.matches) { + document.body.classList.add('dark-mode'); + } else { + document.body.classList.remove('dark-mode'); + } + }); + + document.addEventListener('DOMContentLoaded', () => { + const lazyImages = document.querySelectorAll('img[data-src]'); + + const imageObserver = new IntersectionObserver((entries, observer) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const img = entry.target; + img.src = img.dataset.src; + img.removeAttribute('data-src'); + observer.unobserve(img); + } + }); + }); + + lazyImages.forEach(img => imageObserver.observe(img)); + }); + + const networkStatus = { + isOnline: navigator.onLine, + lastCheck: Date.now() + }; + + window.addEventListener('online', () => { + networkStatus.isOnline = true; + networkStatus.lastCheck = Date.now(); + showNotification('Connection Restored', 'You are back online', 'success'); + }); + + window.addEventListener('offline', () => { + networkStatus.isOnline = false; + networkStatus.lastCheck = Date.now(); + showNotification('Connection Lost', 'You are currently offline', 'warning'); + }); + + async function checkNetworkQuality() { + if (!networkStatus.isOnline) return 'offline'; + + const startTime = Date.now(); + try { + await fetch('/api/stats', { method: 'HEAD' }); + const latency = Date.now() - startTime; + + if (latency < 100) return 'excellent'; + if (latency < 300) return 'good'; + if (latency < 600) return 'fair'; + return 'poor'; + } catch { + return 'offline'; + } + } + + function handleError(error, context = 'Unknown') { + console.error(`Error in ${context}:`, error); + + if (error.message.includes('Failed to fetch')) { + showNotification('Network Error', 'Unable to connect to server', 'error'); + } else if (error.message.includes('401')) { + showNotification('Session Expired', 'Please login again', 'warning'); + setTimeout(() => logout(), 2000); + } else { + showNotification('Error', error.message || 'An unexpected error occurred', 'error'); + } + } + + window.addEventListener('unhandledrejection', (event) => { + handleError(event.reason, 'Unhandled Promise Rejection'); + }); + + const memoryMonitor = setInterval(() => { + if (performance.memory) { + const usedMemory = performance.memory.usedJSHeapSize; + const totalMemory = performance.memory.totalJSHeapSize; + const memoryPercentage = (usedMemory / totalMemory) * 100; + + if (memoryPercentage > 90) { + console.warn('High memory usage detected:', memoryPercentage.toFixed(2) + '%'); + } + } + }, 30000); + + function cleanupResources() { + if (window.roleChartInstance) { + window.roleChartInstance.destroy(); + window.roleChartInstance = null; + } + + if (countdownInterval) { + clearInterval(countdownInterval); + countdownInterval = null; + } + + if (window.serverStatsInterval) { + clearInterval(window.serverStatsInterval); + window.serverStatsInterval = null; + } + } + + window.addEventListener('beforeunload', cleanupResources); + + console.log('%cšŸš€ DashX Dashboard Loaded', 'color: #853030; font-size: 20px; font-weight: bold;'); + console.log('%cVersion: 1.0.0', 'color: #666; font-size: 12px;'); + console.log('%cāš ļø Warning: Do not paste any code here unless you know what you are doing!', 'color: #ff6b6b; font-size: 14px; font-weight: bold;'); \ No newline at end of file