aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatarLibravatar Biswa Kalyan Bhuyan <[email protected]> 2025-05-03 19:52:50 +0530
committerLibravatarLibravatar Biswa Kalyan Bhuyan <[email protected]> 2025-05-03 19:52:50 +0530
commitd15b815abf0188d1e71cc65e70d606b4ea09e4f6 (patch)
treef8a0f7bffbfe7c8b5c078f4d0655585bf601d848
parentbe2a5927e36c472c54b265cb3e1c5530f9ec7756 (diff)
downloadchronos-d15b815abf0188d1e71cc65e70d606b4ea09e4f6.tar.gz
chronos-d15b815abf0188d1e71cc65e70d606b4ea09e4f6.tar.bz2
chronos-d15b815abf0188d1e71cc65e70d606b4ea09e4f6.zip
fix: fixed click graph feature
-rw-r--r--index.html505
-rw-r--r--static/js/main.js164
2 files changed, 141 insertions, 528 deletions
diff --git a/index.html b/index.html
index 92554ac..5961496 100644
--- a/index.html
+++ b/index.html
@@ -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