diff options
author | 2025-05-03 19:52:50 +0530 | |
---|---|---|
committer | 2025-05-03 19:52:50 +0530 | |
commit | d15b815abf0188d1e71cc65e70d606b4ea09e4f6 (patch) | |
tree | f8a0f7bffbfe7c8b5c078f4d0655585bf601d848 | |
parent | be2a5927e36c472c54b265cb3e1c5530f9ec7756 (diff) | |
download | chronos-d15b815abf0188d1e71cc65e70d606b4ea09e4f6.tar.gz chronos-d15b815abf0188d1e71cc65e70d606b4ea09e4f6.tar.bz2 chronos-d15b815abf0188d1e71cc65e70d606b4ea09e4f6.zip |
fix: fixed click graph feature
-rw-r--r-- | index.html | 505 | ||||
-rw-r--r-- | static/js/main.js | 164 |
2 files changed, 141 insertions, 528 deletions
@@ -6,14 +6,12 @@ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <title>My Hardware Setup</title> <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans&family=Roboto&family=Shrikhand&display=swap" rel="stylesheet"> - <link href="style.css" rel="stylesheet"/> + <link href="static/css/style.css" rel="stylesheet"/> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/crypto-js.min.js"></script> - <script src="server-stats.js" defer></script> - <script src="snmp-charts.js" defer></script> - <script src="metrics-access.js" defer></script> + <script src="static/js/main.js" defer></script> </head> <body> <header> @@ -152,504 +150,7 @@ <!-- Consolidated JavaScript --> <script> - // --- Server Stats Logic (from server-stats.js) --- - async function getServerStats() { - const statusElement = document.getElementById('server-status'); - const uptimeElement = document.getElementById('uptime'); - - if (!statusElement || !uptimeElement) { - console.error("Server status elements not found in the DOM"); - return; - } - - try { - // Fetch stats directly from the server endpoint - const response = await fetch('/stat/.server-stats.json'); - - if (!response || !response.ok) { - throw new Error(`Could not find server stats file`); - } - - const data = await response.json(); - - let status = "green"; - const load1 = parseFloat(data.load_1min); - const cores = parseInt(data.cores, 10) || 1; - - if (!isNaN(load1)) { - if (load1 > (cores * 0.7)) status = "yellow"; - if (load1 > (cores * 0.9)) status = "red"; - } else { - status = "yellow"; - console.warn("Invalid load value:", data.load_1min); - } - - statusElement.classList.remove('green', 'yellow', 'red'); - statusElement.classList.add(status); - - let tooltipText = "Unknown status"; - if (status === "green") tooltipText = "Server is running normally"; - else if (status === "yellow") tooltipText = "Server is under medium load"; - else if (status === "red") tooltipText = "Server is under heavy load"; - statusElement.title = tooltipText; - - uptimeElement.textContent = `Uptime: ${data.uptime || 'Unknown'} (Load: ${data.load_1min || '?'}, ${data.load_5min || '?'}, ${data.load_15min || '?'})`; - - } catch (error) { - console.error("Error fetching server stats:", error); - statusElement.classList.remove('green', 'yellow', 'red'); - statusElement.classList.add('yellow'); - statusElement.title = "Status check failed - server may still be operational"; - uptimeElement.textContent = 'Status: Available (Stats unavailable)'; - } - } - - // --- Metrics Access Logic (from metrics-access.js) --- - const viewMetricsBtn = document.getElementById('view-metrics-btn'); - const passwordModal = document.getElementById('password-modal'); - const passwordInput = document.getElementById('metrics-password'); - const submitPasswordBtn = document.getElementById('submit-password'); - const closeModalBtn = document.querySelector('.close-button'); - const passwordError = document.getElementById('password-error'); - const metricsSection = document.getElementById('metrics-section'); - const AUTH_SESSION_KEY = 'metrics_authenticated'; - const METRICS_PASSWORD_HASH = "3a7526cc5662c9d46d9458a07ed6608689006a64f095838cefd244fb9da20780"; // Default: "metrics2023" - - function checkAuthentication() { - return sessionStorage.getItem(AUTH_SESSION_KEY) === 'true'; - } - - function setAuthenticated(state) { - sessionStorage.setItem(AUTH_SESSION_KEY, state ? 'true' : 'false'); - } - - function showMetrics() { - if (!metricsSection) return; - metricsSection.style.display = 'block'; - if (typeof updateCharts === 'function') { - setTimeout(updateCharts, 100); - } - if (!document.getElementById('hide-metrics-btn')) { - createHideMetricsButton(); - } - } - - function hideMetrics() { - if (!metricsSection) return; - metricsSection.style.display = 'none'; - if (viewMetricsBtn) { - viewMetricsBtn.textContent = 'View Server Metrics'; - } - const hideBtn = document.getElementById('hide-metrics-btn'); - if (hideBtn) hideBtn.remove(); // Remove hide button when hiding - } - - function createHideMetricsButton() { - const hideBtn = document.createElement('button'); - hideBtn.id = 'hide-metrics-btn'; - hideBtn.className = 'button'; - hideBtn.textContent = 'Hide Server Metrics'; - hideBtn.style.marginLeft = '10px'; - hideBtn.addEventListener('click', hideMetrics); - - if (viewMetricsBtn && viewMetricsBtn.parentNode) { - viewMetricsBtn.parentNode.appendChild(hideBtn); - } - } - - function openPasswordModal() { - if (!passwordModal || !passwordInput) return; - passwordInput.value = ''; - passwordError.textContent = ''; - passwordError.style.opacity = 0; - passwordModal.style.display = 'block'; - setTimeout(() => { - passwordModal.classList.add('show'); - passwordInput.focus(); - }, 10); - } - - function closePasswordModal() { - if (!passwordModal) return; - passwordModal.classList.remove('show'); - setTimeout(() => { - passwordModal.style.display = 'none'; - }, 300); - } - - function validatePassword(password) { - if (typeof CryptoJS === 'undefined') { - console.error("CryptoJS library not loaded!"); - showError("Authentication library error."); - return false; - } - const hash = CryptoJS.SHA256(password).toString(); - return hash === METRICS_PASSWORD_HASH; - } - - function showError(message) { - if (!passwordError || !passwordInput) return; - passwordError.textContent = message; - passwordError.style.opacity = 0; - setTimeout(() => { - passwordError.style.opacity = 1; - }, 10); - passwordInput.classList.add('shake'); - setTimeout(() => { - passwordInput.classList.remove('shake'); - }, 500); - } - - if (viewMetricsBtn) { - viewMetricsBtn.addEventListener('click', function() { - if (checkAuthentication()) { - showMetrics(); - } else { - openPasswordModal(); - } - }); - } - - if (submitPasswordBtn) { - submitPasswordBtn.addEventListener('click', function() { - if (!passwordInput) return; - const password = passwordInput.value; - if (!password) { - showError('Please enter a password'); - return; - } - if (validatePassword(password)) { - setAuthenticated(true); - closePasswordModal(); - showMetrics(); - } else { - passwordInput.value = ''; - showError('Incorrect password. Please try again.'); - } - }); - } - - if (closeModalBtn) { - closeModalBtn.addEventListener('click', closePasswordModal); - } - - window.addEventListener('click', function(event) { - if (event.target === passwordModal) { - closePasswordModal(); - } - }); - - if (passwordInput) { - passwordInput.addEventListener('keyup', function(event) { - if (event.key === 'Enter') { - submitPasswordBtn.click(); - } - }); - } - - // --- SNMP Charts Logic (from snmp-charts.js) --- - const API_ENDPOINT = '/api/metrics'; - const UPDATE_INTERVAL = 10000; - const CHART_HISTORY = 60; - let charts = {}; - let chartContainers = {}; - let chartsInitialized = false; - let updateIntervalId; - let metricsDefinitions = {}; - - const chartColors = { - networkIn: '#88B7B5', networkOut: '#FDCFF3', cpu: '#88B7B5', - memory: '#FDCFF3', system: '#88B7B5', generic: '#88B7B5' - }; - - function formatBytes(bytes, decimals = 2) { - if (bytes === 0) return '0 Bytes'; - const k = 1024, dm = decimals < 0 ? 0 : decimals, 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 formatTime(centiseconds) { - const totalSeconds = Math.floor(centiseconds / 100); - const days = Math.floor(totalSeconds / 86400), hours = Math.floor((totalSeconds % 86400) / 3600); - const minutes = Math.floor((totalSeconds % 3600) / 60), seconds = totalSeconds % 60; - if (days > 0) return `${days}d ${hours}h ${minutes}m`; - if (hours > 0) return `${hours}h ${minutes}m ${seconds}s`; - if (minutes > 0) return `${minutes}m ${seconds}s`; - return `${seconds}s`; - } - - function calculateRates(data) { - if (data.length < 2) return []; - const rates = []; - for (let i = 1; i < data.length; i++) { - const timeDiff = (data[i].timestamp - data[i-1].timestamp) / 1000; - const valueDiff = data[i].value - data[i-1].value; - if (timeDiff < 0.001) continue; - const rate = valueDiff >= 0 ? valueDiff / timeDiff : (4294967295 + valueDiff) / timeDiff; - rates.push({ timestamp: data[i].timestamp, value: rate }); - } - return rates; - } - - function createChartContainers(metrics) { - const metricGrid = document.querySelector('.metric-grid'); - if (!metricGrid) return; - metricGrid.innerHTML = ''; - for (const [metricName, definition] of Object.entries(metrics)) { - const displayName = definition.label || metricName; - const metricCard = document.createElement('div'); - metricCard.className = 'metric-card'; - metricCard.innerHTML = `<h5>${displayName}</h5><div class="chart-container" data-metric="${metricName}" data-label="${displayName}"><canvas id="${metricName}Chart"></canvas></div>`; - metricGrid.appendChild(metricCard); - chartContainers[metricName] = metricCard; - } - - // Add click events to all chart containers after they're created - document.querySelectorAll('.chart-container').forEach(container => { - container.addEventListener('click', function() { - const metricName = this.getAttribute('data-metric'); - const displayName = this.getAttribute('data-label'); - if (metricName && displayName) { - expandChart(metricName, displayName); - } - }); - }); - } - - function initCharts(metrics) { - if (chartsInitialized) return; - const commonOptions = { - responsive: true, maintainAspectRatio: false, animation: { duration: 300 }, - scales: { - x: { type: 'time', time: { unit: 'minute', tooltipFormat: 'HH:mm:ss' }, grid: { color: 'rgba(255, 255, 255, 0.1)' }, ticks: { color: '#E8F1F2', maxTicksLimit: 5 } }, - y: { beginAtZero: true, grid: { color: 'rgba(255, 255, 255, 0.1)' }, ticks: { color: '#E8F1F2' } } - }, - plugins: { - legend: { labels: { color: '#E8F1F2', font: { family: "'IBM Plex Sans', sans-serif" } } }, - tooltip: { mode: 'index', intersect: false, backgroundColor: 'rgba(17, 17, 17, 0.8)', titleColor: '#FDCFF3', bodyColor: '#E8F1F2', borderColor: '#333', borderWidth: 1 } - } - }; - - for (const [metricName, definition] of Object.entries(metrics)) { - const canvas = document.getElementById(`${metricName}Chart`); - if (!canvas) continue; - const ctx = canvas.getContext('2d'); - let chartOptions = { ...commonOptions }; - let datasets = []; - let label = definition.label || metricName; - let color = chartColors.generic; - - if (metricName === 'network_in' || metricName === 'network_out') { - chartOptions.scales.y.title = { display: true, text: 'Bytes/sec', color: '#E8F1F2' }; - chartOptions.scales.y.ticks.callback = value => formatBytes(value, 0); - label = metricName === 'network_in' ? 'In' : 'Out'; - color = metricName === 'network_in' ? chartColors.networkIn : chartColors.networkOut; - } else if (metricName === 'cpu_load') { - chartOptions.scales.y.title = { display: true, text: 'Load/Usage', color: '#E8F1F2' }; - label = 'CPU'; color = chartColors.cpu; - } else if (metricName.includes('memory')) { - chartOptions.scales.y.title = { display: true, text: 'Memory (KB)', color: '#E8F1F2' }; - chartOptions.scales.y.ticks.callback = value => formatBytes(value * 1024, 0); - label = metricName.includes('total') ? 'Total' : metricName.includes('size') ? 'Size' : 'Used'; - color = chartColors.memory; - } else if (metricName === 'system_uptime') { - chartOptions.scales.y.title = { display: true, text: 'Uptime', color: '#E8F1F2' }; - chartOptions.scales.y.ticks.callback = value => formatTime(value); - label = 'Uptime'; color = chartColors.system; - } else if (metricName === 'system_processes') { - chartOptions.scales.y.title = { display: true, text: 'Count', color: '#E8F1F2' }; - label = 'Processes'; color = chartColors.system; - } else { - chartOptions.scales.y.title = { display: true, text: 'Value', color: '#E8F1F2' }; - } - - datasets = [{ - label: label, - borderColor: color, backgroundColor: `${color}33`, borderWidth: 2, - data: [], pointRadius: 0, fill: true - }]; - - charts[metricName] = new Chart(ctx, { type: 'line', data: { datasets }, options: chartOptions }); - } - chartsInitialized = true; - startDataUpdates(); - } - - function startDataUpdates() { - updateCharts(); - if (updateIntervalId) clearInterval(updateIntervalId); - updateIntervalId = setInterval(updateCharts, UPDATE_INTERVAL); - } - - function stopDataUpdates() { - if (updateIntervalId) { - clearInterval(updateIntervalId); - updateIntervalId = null; - } - } - - async function updateCharts() { - if (!chartsInitialized || !document.getElementById('metrics-section').offsetParent) return; - try { - const response = await fetch(API_ENDPOINT); - if (!response.ok) { - console.error('Failed to fetch metrics:', response.statusText); - return; - } - const data = await response.json(); - metricsDefinitions = data.definitions || {}; - for (const [metricName, metricData] of Object.entries(data.metrics)) { - if (!charts[metricName] || !metricData || metricData.length === 0) continue; - let chartData = (metricName === 'network_in' || metricName === 'network_out') ? calculateRates(metricData) : metricData; - if (!chartData || chartData.length === 0) continue; - - chartData.forEach(point => { - if (typeof point.value === 'string') { - const match = point.value.match(/\d+/); - point.value = match ? parseFloat(match[0]) : 0; - } - }); - - charts[metricName].data.datasets[0].data = chartData.map(point => ({ x: point.timestamp, y: point.value })); - charts[metricName].update('none'); - } - } catch (error) { - console.error('Error updating charts:', error); - } - } - - async function initializeMetricsUI() { - try { - const response = await fetch(API_ENDPOINT); - if (!response.ok) { - console.error('Failed to fetch metrics definitions:', response.statusText); - return; - } - const data = await response.json(); - metricsDefinitions = data.definitions || {}; - createChartContainers(metricsDefinitions); - initCharts(metricsDefinitions); - } catch (error) { - console.error('Error initializing metrics UI:', error); - } - } - - function handleMetricsVisibilityChange() { - if (!metricsSection) return; - if (metricsSection.offsetParent !== null) { - if (!chartsInitialized) { - initializeMetricsUI(); - } else { - startDataUpdates(); - } - } else { - stopDataUpdates(); - } - } - - // Initial Load - document.addEventListener('DOMContentLoaded', function() { - // Start server stats check - getServerStats(); - setInterval(getServerStats, 30000); - - // Apply styles to submit button - if (submitPasswordBtn) submitPasswordBtn.classList.add('submit-button'); - - // Check if already authenticated and show metrics if so - if (checkAuthentication()) showMetrics(); - - // Observer for metrics section visibility - if (metricsSection) { - const observer = new MutationObserver(handleMetricsVisibilityChange); - observer.observe(metricsSection, { attributes: true, attributeFilter: ['style'] }); - } - }); - - // Chart expansion functionality - const chartModal = document.getElementById('chart-modal'); - const chartModalTitle = document.getElementById('chart-modal-title'); - const closeChartModalBtn = document.querySelector('#chart-modal .close-button'); - const closeChartBtn = document.getElementById('close-chart-modal'); - let expandedChart = null; - - function expandChart(metricName, displayName) { - // Set the modal title - chartModalTitle.textContent = displayName; - - // Show the modal - chartModal.style.display = 'block'; - setTimeout(() => chartModal.classList.add('show'), 10); - - // Create an expanded version of the chart - const expandedChartCanvas = document.getElementById('expandedChart'); - const ctx = expandedChartCanvas.getContext('2d'); - - // If there's already an expanded chart, destroy it first - if (expandedChart) { - expandedChart.destroy(); - } - - // Clone the options and data from the original chart - const originalChart = charts[metricName]; - if (!originalChart) return; - - const chartOptions = JSON.parse(JSON.stringify(originalChart.options)); - // Adjust options for the expanded view - chartOptions.maintainAspectRatio = false; - if (chartOptions.scales && chartOptions.scales.y) { - chartOptions.scales.y.ticks.maxTicksLimit = 10; // More ticks for expanded view - } - - // Create the expanded chart with cloned data - const chartData = { - datasets: originalChart.data.datasets.map(dataset => ({ - ...dataset, - data: [...dataset.data], - pointRadius: 3 // Show points in expanded view - })) - }; - - expandedChart = new Chart(ctx, { - type: 'line', - data: chartData, - options: chartOptions - }); - } - - // Close modal events - function closeChartModal() { - chartModal.classList.remove('show'); - setTimeout(() => chartModal.style.display = 'none', 300); - } - - if (closeChartModalBtn) { - closeChartModalBtn.addEventListener('click', closeChartModal); - } - - if (closeChartBtn) { - closeChartBtn.addEventListener('click', closeChartModal); - } - - // Close modal when clicking outside of it - chartModal.addEventListener('click', (event) => { - if (event.target === chartModal) { - closeChartModal(); - } - }); - - // Close modal with Escape key - document.addEventListener('keydown', (event) => { - if (event.key === 'Escape') { - if (chartModal.classList.contains('show')) { - closeChartModal(); - } else if (passwordModal.style.display === 'block') { - closePasswordModal(); - } - } - }); - + // Intentionally left empty - all JavaScript now in external files </script> </body> diff --git a/static/js/main.js b/static/js/main.js index 74cd9c7..5460247 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -254,7 +254,7 @@ const chartColors = { memory: '#FDCFF3', system: '#88B7B5', generic: '#88B7B5', - // New colors for application performance metrics + // Colors for application performance metrics appResponse: '#4CAF50', // Green for response time appError: '#F44336', // Red for error rate appRequests: '#2196F3', // Blue for request count @@ -369,8 +369,20 @@ function createMetricCard(container, metricName, definition) { const displayName = definition.label || metricName; const metricCard = document.createElement('div'); metricCard.className = 'metric-card'; - metricCard.innerHTML = `<h5>${displayName}</h5><div class="chart-container"><canvas id="${metricName}Chart"></canvas></div>`; + metricCard.innerHTML = `<h5>${displayName}</h5><div class="chart-container" data-metric="${metricName}" data-label="${displayName}"><canvas id="${metricName}Chart"></canvas></div>`; container.appendChild(metricCard); + + // Add click event for chart expansion + const chartContainer = metricCard.querySelector('.chart-container'); + if (chartContainer && typeof expandChart === 'function') { + chartContainer.addEventListener('click', function() { + const metricAttr = this.getAttribute('data-metric'); + const labelAttr = this.getAttribute('data-label'); + if (metricAttr && labelAttr) { + expandChart(metricAttr, labelAttr); + } + }); + } } function startDataUpdates() { @@ -702,49 +714,149 @@ function initializeMetricsUI() { }); } -// --- Initial Load Logic --- +// --- Initialize everything when DOM is loaded --- document.addEventListener('DOMContentLoaded', function() { - // Start server stats check immediately and then interval + // Start server stats check getServerStats(); setInterval(getServerStats, 30000); - // Style the submit button + // Apply styles to submit button if (submitPasswordBtn) submitPasswordBtn.classList.add('submit-button'); - // Event listeners for authentication + // Set up password input event listener + if (passwordInput) { + passwordInput.addEventListener('keyup', function(event) { + if (event.key === 'Enter') { + submitPasswordBtn.click(); + } + }); + } + + // Set up view metrics button if (viewMetricsBtn) { - viewMetricsBtn.addEventListener('click', () => { - // Authenticate and show metrics - const token = getToken(); - if (token) { - // Try to initialize/show metrics, fetchWithAuth will handle invalid tokens - showMetrics(); + viewMetricsBtn.addEventListener('click', function() { + if (getToken()) { + showMetrics(); } else { openPasswordModal(); } }); } + + // Set up password modal submit button if (submitPasswordBtn) { submitPasswordBtn.addEventListener('click', login); } - if (passwordInput) { - passwordInput.addEventListener('keyup', (event) => { - if (event.key === 'Enter') login(); - }); - } + + // Set up password modal close button if (closeModalBtn) { closeModalBtn.addEventListener('click', closePasswordModal); } - window.addEventListener('click', (event) => { - if (event.target === passwordModal) closePasswordModal(); + + // Close password modal when clicking outside + if (passwordModal) { + passwordModal.addEventListener('click', function(event) { + if (event.target === passwordModal) { + closePasswordModal(); + } + }); + } + + // Check if already authenticated and show metrics if so + if (getToken()) showMetrics(); + + // Observer for metrics section visibility + if (metricsSection) { + const observer = new MutationObserver(handleMetricsVisibilityChange); + observer.observe(metricsSection, { attributes: true, attributeFilter: ['style'] }); + } +}); + +// Chart expansion functionality +const chartModal = document.getElementById('chart-modal'); +const chartModalTitle = document.getElementById('chart-modal-title'); +const closeChartModalBtn = document.querySelector('#chart-modal .close-button'); +const closeChartBtn = document.getElementById('close-chart-modal'); +let expandedChart = null; + +function expandChart(metricName, displayName) { + // Set the modal title + chartModalTitle.textContent = displayName; + + // Show the modal + chartModal.style.display = 'block'; + setTimeout(() => chartModal.classList.add('show'), 10); + + // Create an expanded version of the chart + const expandedChartCanvas = document.getElementById('expandedChart'); + const ctx = expandedChartCanvas.getContext('2d'); + + // If there's already an expanded chart, destroy it first + if (expandedChart) { + expandedChart.destroy(); + } + + // Clone the options and data from the original chart + const originalChart = charts[metricName]; + if (!originalChart) return; + + const chartOptions = JSON.parse(JSON.stringify(originalChart.options)); + // Adjust options for the expanded view + chartOptions.maintainAspectRatio = false; + if (chartOptions.scales && chartOptions.scales.y) { + chartOptions.scales.y.ticks.maxTicksLimit = 10; // More ticks for expanded view + } + + // Create the expanded chart with cloned data + const chartData = { + datasets: originalChart.data.datasets.map(dataset => ({ + ...dataset, + data: [...dataset.data], + pointRadius: 3 // Show points in expanded view + })) + }; + + expandedChart = new Chart(ctx, { + type: 'line', + data: chartData, + options: chartOptions }); +} - // Check token on load - attempt to show metrics if token exists - // initializeMetricsUI() will handle token validation via fetchWithAuth - if (getToken()) { - showMetrics(); - } else { - // Ensure metrics are hidden if no token - hideMetrics(); +// Close modal events +function closeChartModal() { + chartModal.classList.remove('show'); + setTimeout(() => chartModal.style.display = 'none', 300); +} + +// Initialize event listeners when DOM is loaded +document.addEventListener('DOMContentLoaded', function() { + // Chart modal close buttons + if (closeChartModalBtn) { + closeChartModalBtn.addEventListener('click', closeChartModal); + } + + if (closeChartBtn) { + closeChartBtn.addEventListener('click', closeChartModal); + } + + // Close modal when clicking outside of it + if (chartModal) { + chartModal.addEventListener('click', (event) => { + if (event.target === chartModal) { + closeChartModal(); + } + }); } + + // Close modal with Escape key + document.addEventListener('keydown', (event) => { + if (event.key === 'Escape') { + if (chartModal && chartModal.classList.contains('show')) { + closeChartModal(); + } else if (passwordModal && passwordModal.style.display === 'block') { + closePasswordModal(); + } + } + }); });
\ No newline at end of file |