diff --git a/app/Http/Middleware/ContentSecurityPolicy.php b/app/Http/Middleware/ContentSecurityPolicy.php index 2f89a9e2c..8237f9011 100644 --- a/app/Http/Middleware/ContentSecurityPolicy.php +++ b/app/Http/Middleware/ContentSecurityPolicy.php @@ -36,15 +36,15 @@ class ContentSecurityPolicy // Build CSP directives for non-Turnstile pages $directives = [ "default-src 'self'", - "script-src 'self' 'nonce-{$nonce}' 'unsafe-inline' 'unsafe-eval' https://www.google.com https://www.gstatic.com https://cdn.jsdelivr.net https://unpkg.com blob:", - "script-src-elem 'self' 'nonce-{$nonce}' 'unsafe-inline' https://www.google.com https://www.gstatic.com https://cdn.jsdelivr.net https://unpkg.com", + "script-src 'self' 'nonce-{$nonce}' 'unsafe-eval' 'unsafe-hashes' https://challenges.cloudflare.com https://cdn.tiny.cloud https://cdn.jsdelivr.net/ https://static.cloudflareinsights.com/ https://cdnjs.cloudflare.com/ https://unpkg.com/ https://cdn.tailwindcss.com/ https://code.jquery.com https://apis.google.com https://www.google.com https://www.gstatic.com https://ajax.cloudflare.com blob:", + "script-src-elem 'self' 'nonce-{$nonce}' https://challenges.cloudflare.com https://cdn.tiny.cloud https://cdn.jsdelivr.net/ https://static.cloudflareinsights.com/ https://cdnjs.cloudflare.com/ https://unpkg.com/ https://cdn.tailwindcss.com/ https://code.jquery.com https://apis.google.com https://www.google.com https://www.gstatic.com https://ajax.cloudflare.com", "script-src-attr 'unsafe-inline'", - "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com", - "font-src 'self' https://fonts.gstatic.com data:", + "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net/ https://cdnjs.cloudflare.com/", + "font-src 'self' https://fonts.gstatic.com https://cdnjs.cloudflare.com/ data:", "img-src 'self' data: https: blob:", "connect-src 'self' https://www.google.com", - "frame-src 'self' https://www.google.com https://www.gstatic.com data: blob:", - "child-src 'self' https://www.google.com blob:", + "frame-src 'self' https://www.google.com https://www.gstatic.com https://challenges.cloudflare.com data: blob:", + "child-src 'self' https://www.google.com https://challenges.cloudflare.com blob:", "worker-src 'self' blob:", "object-src 'none'", "base-uri 'self'", diff --git a/resources/js/csp-safe.js b/resources/js/csp-safe.js index 46549d1e8..da69c7771 100644 --- a/resources/js/csp-safe.js +++ b/resources/js/csp-safe.js @@ -2754,8 +2754,382 @@ function initMobileEnhancements() { } function initAdminDashboardCharts() { - // Placeholder for admin dashboard charts - // Chart initialization happens when the admin dashboard loads + // Only initialize if Chart.js is available and we're on a page with charts + if (typeof Chart === 'undefined') { + // If Chart.js is not loaded yet, wait for it + let attempts = 0; + const maxAttempts = 30; // Try for up to 3 seconds + const checkChartJs = setInterval(() => { + attempts++; + if (typeof Chart !== 'undefined') { + clearInterval(checkChartJs); + initializeAllDashboardCharts(); + } else if (attempts >= maxAttempts) { + clearInterval(checkChartJs); + console.warn('Chart.js not loaded - charts will not be displayed'); + } + }, 100); + return; + } + + initializeAllDashboardCharts(); +} + +function initializeAllDashboardCharts() { + // Check if any chart canvas elements exist + const hasUserStatsCharts = document.getElementById('downloadsChart') || + document.getElementById('apiHitsChart') || + document.getElementById('downloadsMinuteChart') || + document.getElementById('apiHitsMinuteChart'); + + const hasSystemCharts = document.getElementById('cpuHistory24hChart') || + document.getElementById('ramHistory24hChart') || + document.getElementById('cpuHistory30dChart') || + document.getElementById('ramHistory30dChart'); + + if (!hasUserStatsCharts && !hasSystemCharts) { + return; // Not on admin dashboard or charts not present + } + + // Initialize user statistics charts + initializeUserStatCharts(); + + // Initialize system metrics charts + initializeSystemMetricsCharts(); +} + +function initializeUserStatCharts() { + // Downloads Chart (Last 7 Days - Hourly) + const downloadsCanvas = document.getElementById('downloadsChart'); + if (downloadsCanvas) { + const data = JSON.parse(downloadsCanvas.dataset.chartData || '[]'); + if (data.length > 0) { + new Chart(downloadsCanvas, { + type: 'bar', + data: { + labels: data.map(item => item.time), + datasets: [{ + label: 'Downloads', + data: data.map(item => item.count), + backgroundColor: 'rgba(34, 197, 94, 0.7)', + borderColor: 'rgba(34, 197, 94, 1)', + borderWidth: 1 + }] + }, + options: { + responsive: true, + maintainAspectRatio: true, + scales: { + y: { + beginAtZero: true, + ticks: { + precision: 0 + } + } + }, + plugins: { + legend: { + display: false + } + } + } + }); + } + } + + // API Hits Chart (Last 7 Days - Hourly) + const apiHitsCanvas = document.getElementById('apiHitsChart'); + if (apiHitsCanvas) { + const data = JSON.parse(apiHitsCanvas.dataset.chartData || '[]'); + if (data.length > 0) { + new Chart(apiHitsCanvas, { + type: 'line', + data: { + labels: data.map(item => item.time), + datasets: [{ + label: 'API Hits', + data: data.map(item => item.count), + backgroundColor: 'rgba(168, 85, 247, 0.2)', + borderColor: 'rgba(168, 85, 247, 1)', + borderWidth: 2, + fill: true, + tension: 0.4 + }] + }, + options: { + responsive: true, + maintainAspectRatio: true, + scales: { + y: { + beginAtZero: true, + ticks: { + precision: 0 + } + } + }, + plugins: { + legend: { + display: false + } + } + } + }); + } + } + + // Downloads Per Minute Chart (Last 60 Minutes) + const downloadsMinuteCanvas = document.getElementById('downloadsMinuteChart'); + if (downloadsMinuteCanvas) { + const data = JSON.parse(downloadsMinuteCanvas.dataset.chartData || '[]'); + if (data.length > 0) { + new Chart(downloadsMinuteCanvas, { + type: 'line', + data: { + labels: data.map(item => item.time), + datasets: [{ + label: 'Downloads', + data: data.map(item => item.count), + backgroundColor: 'rgba(34, 197, 94, 0.2)', + borderColor: 'rgba(34, 197, 94, 1)', + borderWidth: 2, + fill: true, + tension: 0.4 + }] + }, + options: { + responsive: true, + maintainAspectRatio: true, + scales: { + y: { + beginAtZero: true, + ticks: { + precision: 0 + } + } + }, + plugins: { + legend: { + display: false + } + } + } + }); + } + } + + // API Hits Per Minute Chart (Last 60 Minutes) + const apiHitsMinuteCanvas = document.getElementById('apiHitsMinuteChart'); + if (apiHitsMinuteCanvas) { + const data = JSON.parse(apiHitsMinuteCanvas.dataset.chartData || '[]'); + if (data.length > 0) { + new Chart(apiHitsMinuteCanvas, { + type: 'line', + data: { + labels: data.map(item => item.time), + datasets: [{ + label: 'API Hits', + data: data.map(item => item.count), + backgroundColor: 'rgba(168, 85, 247, 0.2)', + borderColor: 'rgba(168, 85, 247, 1)', + borderWidth: 2, + fill: true, + tension: 0.4 + }] + }, + options: { + responsive: true, + maintainAspectRatio: true, + scales: { + y: { + beginAtZero: true, + ticks: { + precision: 0 + } + } + }, + plugins: { + legend: { + display: false + } + } + } + }); + } + } +} + +function initializeSystemMetricsCharts() { + // CPU Usage 24h Chart + const cpuHistory24hCanvas = document.getElementById('cpuHistory24hChart'); + if (cpuHistory24hCanvas) { + const history = JSON.parse(cpuHistory24hCanvas.dataset.history || '[]'); + if (history.length > 0) { + new Chart(cpuHistory24hCanvas, { + type: 'line', + data: { + labels: history.map(item => item.time), + datasets: [{ + label: 'CPU Usage %', + data: history.map(item => item.value), + backgroundColor: 'rgba(249, 115, 22, 0.2)', + borderColor: 'rgba(249, 115, 22, 1)', + borderWidth: 2, + fill: true, + tension: 0.4 + }] + }, + options: { + responsive: true, + maintainAspectRatio: true, + scales: { + y: { + beginAtZero: true, + max: 100, + ticks: { + callback: function(value) { + return value + '%'; + } + } + } + }, + plugins: { + legend: { + display: false + } + } + } + }); + } + } + + // RAM Usage 24h Chart + const ramHistory24hCanvas = document.getElementById('ramHistory24hChart'); + if (ramHistory24hCanvas) { + const history = JSON.parse(ramHistory24hCanvas.dataset.history || '[]'); + if (history.length > 0) { + new Chart(ramHistory24hCanvas, { + type: 'line', + data: { + labels: history.map(item => item.time), + datasets: [{ + label: 'RAM Usage %', + data: history.map(item => item.value), + backgroundColor: 'rgba(6, 182, 212, 0.2)', + borderColor: 'rgba(6, 182, 212, 1)', + borderWidth: 2, + fill: true, + tension: 0.4 + }] + }, + options: { + responsive: true, + maintainAspectRatio: true, + scales: { + y: { + beginAtZero: true, + max: 100, + ticks: { + callback: function(value) { + return value + '%'; + } + } + } + }, + plugins: { + legend: { + display: false + } + } + } + }); + } + } + + // CPU Usage 30d Chart + const cpuHistory30dCanvas = document.getElementById('cpuHistory30dChart'); + if (cpuHistory30dCanvas) { + const history = JSON.parse(cpuHistory30dCanvas.dataset.history || '[]'); + if (history.length > 0) { + new Chart(cpuHistory30dCanvas, { + type: 'line', + data: { + labels: history.map(item => item.time), + datasets: [{ + label: 'CPU Usage %', + data: history.map(item => item.value), + backgroundColor: 'rgba(249, 115, 22, 0.2)', + borderColor: 'rgba(249, 115, 22, 1)', + borderWidth: 2, + fill: true, + tension: 0.4 + }] + }, + options: { + responsive: true, + maintainAspectRatio: true, + scales: { + y: { + beginAtZero: true, + max: 100, + ticks: { + callback: function(value) { + return value + '%'; + } + } + } + }, + plugins: { + legend: { + display: false + } + } + } + }); + } + } + + // RAM Usage 30d Chart + const ramHistory30dCanvas = document.getElementById('ramHistory30dChart'); + if (ramHistory30dCanvas) { + const history = JSON.parse(ramHistory30dCanvas.dataset.history || '[]'); + if (history.length > 0) { + new Chart(ramHistory30dCanvas, { + type: 'line', + data: { + labels: history.map(item => item.time), + datasets: [{ + label: 'RAM Usage %', + data: history.map(item => item.value), + backgroundColor: 'rgba(6, 182, 212, 0.2)', + borderColor: 'rgba(6, 182, 212, 1)', + borderWidth: 2, + fill: true, + tension: 0.4 + }] + }, + options: { + responsive: true, + maintainAspectRatio: true, + scales: { + y: { + beginAtZero: true, + max: 100, + ticks: { + callback: function(value) { + return value + '%'; + } + } + } + }, + plugins: { + legend: { + display: false + } + } + } + }); + } + } } function initAdminGroups() { diff --git a/resources/views/layouts/admin.blade.php b/resources/views/layouts/admin.blade.php index ff9cc61ff..dad37bb0d 100644 --- a/resources/views/layouts/admin.blade.php +++ b/resources/views/layouts/admin.blade.php @@ -93,7 +93,7 @@ @stack('scripts') -