From 0eaa57b600f6542790af9bee0e256518cdaa35e6 Mon Sep 17 00:00:00 2001 From: Yogesh Date: Thu, 14 Aug 2025 00:09:40 +0530 Subject: [PATCH] Adding graphs for better analytics --- src/templates.rs | 5 +- templates/monitor/content.html | 25 ++ templates/monitor/page.html | 474 ++++++++++++++++++++++++++++--- templates/monitor/script.js | 494 ++++++++++++++++++++++++++++++++- templates/monitor/styles.css | 75 +++++ 5 files changed, 1022 insertions(+), 51 deletions(-) diff --git a/src/templates.rs b/src/templates.rs index 830d8e0..df92ed1 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -527,7 +527,10 @@ impl TemplateEngine { pub fn render_monitor_page(&self) -> Result { let page_title = "Monitor"; let page_styles = r#""#; - let page_scripts = r#""#; + let page_scripts = r#" + + + "#; let header_actions = r#"← Back to Files"#; let variables = HashMap::new(); diff --git a/templates/monitor/content.html b/templates/monitor/content.html index 00c607f..d6b9d39 100644 --- a/templates/monitor/content.html +++ b/templates/monitor/content.html @@ -14,6 +14,31 @@

Server Monitor

+ +
+
+

Server Activity

+
+ +
Loading chart data...
+
+
+
+

Memory Usage

+
+ +
Loading chart data...
+
+
+
+

Upload Stats

+
+ +
Loading chart data...
+
+
+
+
diff --git a/templates/monitor/page.html b/templates/monitor/page.html index af00597..a74d57c 100644 --- a/templates/monitor/page.html +++ b/templates/monitor/page.html @@ -15,6 +15,21 @@ + + + +
@@ -22,6 +37,32 @@

Server Monitor

+ + +
+
+

Server Activity

+
+ +
Loading chart data...
+
+
+
+

Memory Usage

+
+ +
Loading chart data...
+
+
+
+

Upload Stats

+
+ +
Loading chart data...
+
+
+
+

Requests

@@ -76,54 +117,417 @@

Uptime

Auto-refreshes every 30s. © IronDrop Monitor
diff --git a/templates/monitor/script.js b/templates/monitor/script.js index 9802547..8590c8f 100644 --- a/templates/monitor/script.js +++ b/templates/monitor/script.js @@ -1,4 +1,47 @@ -// Monitor Page JavaScript - Real-time metrics +// Monitor Page JavaScript - Real-time metrics with Chart.js support + +// Debug variables to track chart status +let chartJsLoaded = false; +let chartsInitialized = false; +let dataPointsCollected = 0; + +// Check if Chart.js loaded correctly +window.addEventListener('load', function() { + if (typeof Chart === 'undefined') { + console.error("Chart.js failed to load"); + updateStatus('error', 'Error: Chart.js failed to load'); + chartJsLoaded = false; + } else { + console.log("Chart.js loaded successfully"); + chartJsLoaded = true; + } +}); + +// Historical data storage - will be populated over time +const historyData = { + timestamps: [], + requests: { + total: [], + successful: [], + errors: [] + }, + memory: { + current: [], + peak: [] + }, + uploads: { + total: [], + successful: [], + failed: [], + bytes: [] + } +}; + +// Maximum number of data points to keep in history +const MAX_HISTORY_POINTS = 20; + +// Chart instances +let requestsChart, memoryChart, uploadsChart; // Utility functions function humanBytes(bytes) { @@ -34,26 +77,124 @@ function prettyUptime(seconds) { // Status management function updateStatus(type, message) { const statusEl = document.getElementById('status'); - const statusText = statusEl.querySelector('.status-text'); - + if (!statusEl) return; + // Clear existing status classes - statusEl.className = 'status-indicator'; - + statusEl.className = 'monitor-status'; + // Add new status class statusEl.classList.add(`status-${type}`); - + // Update text - statusText.textContent = message; + statusEl.textContent = message; } function setLoadingState(isLoading) { - const metricsGrid = document.querySelector('.metrics-grid'); - if (isLoading) { - metricsGrid.classList.add('loading-data'); - document.getElementById('status').classList.add('refreshing'); + const monitorGrid = document.querySelector('.monitor-grid'); + const statusEl = document.getElementById('status'); + + if (monitorGrid && isLoading) { + monitorGrid.classList.add('loading-data'); + } else if (monitorGrid) { + monitorGrid.classList.remove('loading-data'); + } + + if (statusEl && isLoading) { + statusEl.classList.add('refreshing'); + } else if (statusEl) { + statusEl.classList.remove('refreshing'); + } +} + +// Update charts with new data +function updateCharts() { + console.log("Updating charts with history length:", historyData.timestamps.length); + dataPointsCollected = historyData.timestamps.length; + + if (!historyData.timestamps.length) { + console.log("No history data yet, skipping chart update"); + return; + } + + if (!requestsChart || !memoryChart || !uploadsChart) { + console.error("Charts not initialized"); + return; + } + + // Hide all fallback texts once charts are working + document.querySelectorAll('.chart-fallback').forEach(el => { + el.style.display = 'none'; + }); + + console.log("Chart fallback messages hidden"); + + // Format time labels for display + const timeLabels = historyData.timestamps.map(time => { + const date = new Date(time); + return date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit', second:'2-digit'}); + }); + + // Update Requests Chart + requestsChart.data.labels = timeLabels; + requestsChart.data.datasets[0].data = historyData.requests.total; + requestsChart.data.datasets[1].data = historyData.requests.successful; + requestsChart.data.datasets[2].data = historyData.requests.errors; + requestsChart.update(); + + // Update Memory Chart if available + if (historyData.memory.current.some(val => val !== null)) { + memoryChart.data.labels = timeLabels; + memoryChart.data.datasets[0].data = historyData.memory.current; + memoryChart.data.datasets[1].data = historyData.memory.peak; + memoryChart.update(); + } + + // Update Uploads Chart + uploadsChart.data.labels = timeLabels; + uploadsChart.data.datasets[0].data = historyData.uploads.total; + uploadsChart.data.datasets[1].data = historyData.uploads.successful; + uploadsChart.data.datasets[2].data = historyData.uploads.failed; + uploadsChart.data.datasets[3].data = historyData.uploads.bytes.map(b => b / 1024 / 1024); + uploadsChart.update(); +} + +// Add new data points to history +function addToHistory(data) { + // Add timestamp + historyData.timestamps.push(new Date().getTime()); + + // Add requests data + historyData.requests.total.push(data.requests.total); + historyData.requests.successful.push(data.requests.successful); + historyData.requests.errors.push(data.requests.errors); + + // Add memory data if available + if (data.memory && data.memory.available) { + historyData.memory.current.push(data.memory.current_mb); + historyData.memory.peak.push(data.memory.peak_mb); } else { - metricsGrid.classList.remove('loading-data'); - document.getElementById('status').classList.remove('refreshing'); + historyData.memory.current.push(null); + historyData.memory.peak.push(null); + } + + // Add upload data + historyData.uploads.total.push(data.uploads.total_uploads); + historyData.uploads.successful.push(data.uploads.successful_uploads); + historyData.uploads.failed.push(data.uploads.failed_uploads); + historyData.uploads.bytes.push(data.uploads.upload_bytes); + + // Trim history if needed + if (historyData.timestamps.length > MAX_HISTORY_POINTS) { + historyData.timestamps.shift(); + historyData.requests.total.shift(); + historyData.requests.successful.shift(); + historyData.requests.errors.shift(); + historyData.memory.current.shift(); + historyData.memory.peak.shift(); + historyData.uploads.total.shift(); + historyData.uploads.successful.shift(); + historyData.uploads.failed.shift(); + historyData.uploads.bytes.shift(); } } @@ -62,14 +203,54 @@ async function loadMetrics() { setLoadingState(true); try { - const response = await fetch('/_irondrop/monitor?json=1'); + // Log the current fetch URL for debugging + console.log("Fetching metrics from: /monitor?json=1"); + const response = await fetch('/monitor?json=1'); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } - const data = await response.json(); + // Debug the raw response + const rawText = await response.text(); + console.log("Raw response:", rawText); + + // Parse the JSON (separate step for better debugging) + let data; + try { + data = JSON.parse(rawText); + console.log("Data parsed successfully:", data); + } catch (parseErr) { + console.error("JSON parse error:", parseErr); + throw new Error("Failed to parse server response: " + parseErr.message); + } + + // Update metrics tables updateMetrics(data); + + // Update charts if Chart.js is loaded + if (chartJsLoaded) { + try { + // Ensure charts are initialized + if (!chartsInitialized && typeof initCharts === 'function') { + chartsInitialized = initCharts(); + if (chartsInitialized) { + console.log("Charts initialized on first data load"); + } + } + + // Add data to history and update charts + addToHistory(data); + updateCharts(); + console.log(`Charts updated with data points: ${dataPointsCollected}`); + } catch (chartErr) { + console.error("Error updating charts:", chartErr); + } + } else { + console.warn("Chart.js not loaded, skipping chart updates"); + } + + // Update status updateStatus('ok', 'Connected'); } catch (error) { @@ -177,8 +358,289 @@ function updateTimestamp() { document.getElementById('last_updated').textContent = timeString; } +// Initialize charts +function initCharts() { + try { + console.log("Initializing charts..."); + + if (typeof Chart !== 'function') { + console.error("Chart constructor not available"); + // Show error in all chart fallbacks + document.querySelectorAll('.chart-fallback').forEach(el => { + el.textContent = 'Chart.js failed to load'; + el.style.color = '#f87171'; + }); + return false; + } + + // Check if chart elements exist + const requestsElement = document.getElementById('requestsChart'); + const memoryElement = document.getElementById('memoryChart'); + const uploadsElement = document.getElementById('uploadsChart'); + + if (!requestsElement || !memoryElement || !uploadsElement) { + console.error("Chart elements not found in DOM:", { + requests: !!requestsElement, + memory: !!memoryElement, + uploads: !!uploadsElement + }); + return false; + } + + // Common chart options + const commonOptions = { + responsive: true, + maintainAspectRatio: false, + animation: { + duration: 600 + }, + elements: { + point: { + radius: 3, + hoverRadius: 5 + }, + line: { + tension: 0.3 + } + }, + plugins: { + legend: { + position: 'top', + labels: { + color: '#e5e5e5', + font: { + family: "'Inter', sans-serif", + size: 12 + } + } + }, + tooltip: { + backgroundColor: '#1a1a1a', + borderColor: 'rgba(64, 64, 64, 0.4)', + borderWidth: 1, + titleFont: { + family: "'Inter', sans-serif", + size: 12, + weight: 'normal' + }, + bodyFont: { + family: "'Inter', sans-serif", + size: 12 + }, + padding: 10, + boxPadding: 4 + } + }, + scales: { + x: { + grid: { + color: 'rgba(255, 255, 255, 0.05)', + borderColor: 'rgba(255, 255, 255, 0.1)' + }, + ticks: { + color: '#b0b0b0', + font: { + family: "'Inter', sans-serif", + size: 10 + }, + maxRotation: 0, + autoSkipPadding: 20 + } + }, + y: { + beginAtZero: true, + grid: { + color: 'rgba(255, 255, 255, 0.05)', + borderColor: 'rgba(255, 255, 255, 0.1)' + }, + ticks: { + color: '#b0b0b0', + font: { + family: "'Inter', sans-serif", + size: 10 + } + } + } + } + }; + + // Requests Chart + const requestsCtx = requestsElement.getContext('2d'); + requestsChart = new Chart(requestsCtx, { + type: 'line', + data: { + labels: [], + datasets: [ + { + label: 'Total', + data: [], + borderColor: '#9CC8FF', + backgroundColor: 'rgba(156, 200, 255, 0.1)', + fill: true + }, + { + label: 'Successful', + data: [], + borderColor: '#4ADE80', + backgroundColor: 'rgba(74, 222, 128, 0.1)', + fill: true + }, + { + label: 'Errors', + data: [], + borderColor: '#F87171', + backgroundColor: 'rgba(248, 113, 113, 0.1)', + fill: true + } + ] + }, + options: commonOptions + }); + + // Memory Usage Chart + const memoryCtx = memoryElement.getContext('2d'); + memoryChart = new Chart(memoryCtx, { + type: 'line', + data: { + labels: [], + datasets: [ + { + label: 'Current (MB)', + data: [], + borderColor: '#60A5FA', + backgroundColor: 'rgba(96, 165, 250, 0.1)', + fill: true + }, + { + label: 'Peak (MB)', + data: [], + borderColor: '#F59E0B', + backgroundColor: 'rgba(245, 158, 11, 0.1)', + fill: true + } + ] + }, + options: commonOptions + }); + + // Uploads Chart + const uploadsCtx = uploadsElement.getContext('2d'); + uploadsChart = new Chart(uploadsCtx, { + type: 'line', + data: { + labels: [], + datasets: [ + { + label: 'Total Uploads', + data: [], + borderColor: '#9CC8FF', + backgroundColor: 'rgba(156, 200, 255, 0.1)', + fill: false + }, + { + label: 'Successful', + data: [], + borderColor: '#4ADE80', + backgroundColor: 'rgba(74, 222, 128, 0.1)', + fill: false + }, + { + label: 'Failed', + data: [], + borderColor: '#F87171', + backgroundColor: 'rgba(248, 113, 113, 0.1)', + fill: false + }, + { + label: 'Upload Size (MB)', + data: [], + borderColor: '#A78BFA', + backgroundColor: 'rgba(167, 139, 250, 0.1)', + fill: false, + yAxisID: 'y1' + } + ] + }, + options: { + ...commonOptions, + scales: { + ...commonOptions.scales, + y1: { + position: 'right', + beginAtZero: true, + grid: { + drawOnChartArea: false + }, + ticks: { + color: '#A78BFA', + font: { + family: "'Inter', sans-serif", + size: 10 + } + } + } + } + } + }); + + console.log("Charts initialized successfully"); + return true; + } catch (err) { + console.error("Error initializing charts:", err); + return false; + } +} + // Initialize and start auto-refresh function init() { + // Check if we have the status element structure we expect + const statusEl = document.getElementById('status'); + if (!statusEl) { + console.error("Status element not found"); + } else { + // Make sure status element has content + if (!statusEl.textContent) { + statusEl.textContent = 'Initializing...'; + } + } + + // Check if chart container exists + const chartsContainer = document.getElementById('chartsContainer'); + if (!chartsContainer) { + console.error("Charts container not found!"); + // Try to find chart elements directly + const requestsChart = document.getElementById('requestsChart'); + const memoryChart = document.getElementById('memoryChart'); + const uploadsChart = document.getElementById('uploadsChart'); + console.log("Direct chart element checks:", { + requestsChart: !!requestsChart, + memoryChart: !!memoryChart, + uploadsChart: !!uploadsChart + }); + } else { + console.log("Charts container found with children:", chartsContainer.children.length); + // Log all chart canvas elements + const canvases = chartsContainer.querySelectorAll('canvas'); + console.log("Canvas elements found:", canvases.length); + canvases.forEach((canvas, i) => { + console.log(`Canvas #${i} id:`, canvas.id); + }); + + // Check fallback elements + const fallbacks = chartsContainer.querySelectorAll('.chart-fallback'); + console.log("Fallback elements found:", fallbacks.length); + } + + // Try to initialize charts if Chart.js is available + if (typeof Chart !== 'undefined') { + chartJsLoaded = true; + chartsInitialized = initCharts(); + console.log("Charts initialization result:", chartsInitialized); + } else { + console.warn("Chart.js not available during initialization"); + chartJsLoaded = false; + } + // Initial load loadMetrics(); @@ -192,6 +654,8 @@ function init() { loadMetrics(); } }); + + console.log("Monitor initialization complete"); } // Start when DOM is ready diff --git a/templates/monitor/styles.css b/templates/monitor/styles.css index 3e17a49..b85c6eb 100644 --- a/templates/monitor/styles.css +++ b/templates/monitor/styles.css @@ -8,6 +8,73 @@ margin-top: var(--space-md); } +/* Charts Container */ +.charts-container { + margin: var(--space-lg) 0 var(--space-xl); + display: grid; + gap: var(--space-lg); + grid-template-columns: repeat(2, 1fr); +} + +.chart-card { + background: var(--bg-secondary); + border: 1px solid var(--border); + border-radius: 8px; + padding: var(--space-lg); + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + transition: all 0.3s ease; + min-height: 280px; + display: flex; + flex-direction: column; +} + +.chart-card:hover { + transform: translateY(-2px); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); + border-color: rgba(100, 100, 100, 0.5); +} + +.chart-card.full-width { + grid-column: 1 / -1; + min-height: 320px; +} + +.chart-card h2 { + margin: 0 0 var(--space-md); + font-size: 1.05rem; + font-weight: 600; + color: #9cc8ff; + letter-spacing: 0.5px; +} + +.chart-wrapper { + position: relative; + width: 100%; + height: 240px; + min-height: 240px; + flex: 1; +} + +.chart-wrapper canvas { + width: 100% !important; + height: 100% !important; +} + +.chart-fallback { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + text-align: center; + padding: 20px; + font-size: var(--font-size-sm); +} + /* Monitor cards - extending base card system */ .monitor-card { background: var(--bg-secondary); @@ -91,6 +158,14 @@ gap: var(--space-sm); } + .charts-container { + grid-template-columns: 1fr; + } + + .chart-card { + min-height: 240px; + } + .monitor-card { padding: var(--space-sm) var(--space-md); }