Update workers.js

This commit is contained in:
DJObleezy 2025-03-28 19:12:37 -07:00 committed by GitHub
parent d4981d91e2
commit 514644a3ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -3,9 +3,9 @@
// Global variables for workers dashboard // Global variables for workers dashboard
let workerData = null; let workerData = null;
let refreshTimer; let refreshTimer;
let pageLoadTime = Date.now(); const pageLoadTime = Date.now();
let lastManualRefreshTime = 0; let lastManualRefreshTime = 0;
let filterState = { const filterState = {
currentFilter: 'all', currentFilter: 'all',
searchTerm: '' searchTerm: ''
}; };
@ -21,70 +21,54 @@ const MIN_REFRESH_INTERVAL = 10000; // Minimum 10 seconds between refreshes
// Hashrate Normalization Utilities // Hashrate Normalization Utilities
// Helper function to normalize hashrate to TH/s for consistent graphing // Helper function to normalize hashrate to TH/s for consistent graphing
function normalizeHashrate(value, unit) { function normalizeHashrate(value, unit = 'th/s') {
if (!value || isNaN(value)) return 0; if (!value || isNaN(value)) return 0;
unit = (unit || 'th/s').toLowerCase(); unit = unit.toLowerCase();
if (unit.includes('ph/s')) { const unitConversion = {
return value * 1000; // Convert PH/s to TH/s 'ph/s': 1000,
} else if (unit.includes('eh/s')) { 'eh/s': 1000000,
return value * 1000000; // Convert EH/s to TH/s 'gh/s': 1 / 1000,
} else if (unit.includes('gh/s')) { 'mh/s': 1 / 1000000,
return value / 1000; // Convert GH/s to TH/s 'kh/s': 1 / 1000000000,
} else if (unit.includes('mh/s')) { 'h/s': 1 / 1000000000000
return value / 1000000; // Convert MH/s to TH/s };
} else if (unit.includes('kh/s')) {
return value / 1000000000; // Convert KH/s to TH/s return unitConversion[unit] !== undefined ? value * unitConversion[unit] : value;
} else if (unit.includes('h/s') && !unit.includes('th/s') && !unit.includes('ph/s') &&
!unit.includes('eh/s') && !unit.includes('gh/s') && !unit.includes('mh/s') &&
!unit.includes('kh/s')) {
return value / 1000000000000; // Convert H/s to TH/s
} else {
// Assume TH/s if unit is not recognized
return value;
}
} }
// Helper function to format hashrate values for display // Helper function to format hashrate values for display
function formatHashrateForDisplay(value, unit) { function formatHashrateForDisplay(value, unit) {
if (isNaN(value) || value === null || value === undefined) return "N/A"; if (isNaN(value) || value === null || value === undefined) return "N/A";
// Always normalize to TH/s first if unit is provided const normalizedValue = unit ? normalizeHashrate(value, unit) : value;
let normalizedValue = unit ? normalizeHashrate(value, unit) : value; const unitRanges = [
{ threshold: 1000000, unit: 'EH/s', divisor: 1000000 },
{ threshold: 1000, unit: 'PH/s', divisor: 1000 },
{ threshold: 1, unit: 'TH/s', divisor: 1 },
{ threshold: 0.001, unit: 'GH/s', divisor: 1 / 1000 },
{ threshold: 0, unit: 'MH/s', divisor: 1 / 1000000 }
];
// Select appropriate unit based on magnitude for (const range of unitRanges) {
if (normalizedValue >= 1000000) { // EH/s range if (normalizedValue >= range.threshold) {
return (normalizedValue / 1000000).toFixed(2) + ' EH/s'; return (normalizedValue / range.divisor).toFixed(2) + ' ' + range.unit;
} else if (normalizedValue >= 1000) { // PH/s range }
return (normalizedValue / 1000).toFixed(2) + ' PH/s';
} else if (normalizedValue >= 1) { // TH/s range
return normalizedValue.toFixed(2) + ' TH/s';
} else if (normalizedValue >= 0.001) { // GH/s range
return (normalizedValue * 1000).toFixed(2) + ' GH/s';
} else { // MH/s range or smaller
return (normalizedValue * 1000000).toFixed(2) + ' MH/s';
} }
return (normalizedValue * 1000000).toFixed(2) + ' MH/s';
} }
// Initialize the page // Initialize the page
$(document).ready(function () { $(document).ready(function () {
console.log("Worker page initializing..."); console.log("Worker page initializing...");
// Initialize notification badge
initNotificationBadge(); initNotificationBadge();
// Set up initial UI
initializePage(); initializePage();
// Get server time for uptime calculation
updateServerTime(); updateServerTime();
// Define global refresh function for BitcoinMinuteRefresh
window.manualRefresh = fetchWorkerData; window.manualRefresh = fetchWorkerData;
// Wait before initializing BitcoinMinuteRefresh to ensure DOM is ready setTimeout(() => {
setTimeout(function () {
// Initialize BitcoinMinuteRefresh with our refresh function
if (typeof BitcoinMinuteRefresh !== 'undefined' && BitcoinMinuteRefresh.initialize) { if (typeof BitcoinMinuteRefresh !== 'undefined' && BitcoinMinuteRefresh.initialize) {
BitcoinMinuteRefresh.initialize(window.manualRefresh); BitcoinMinuteRefresh.initialize(window.manualRefresh);
console.log("BitcoinMinuteRefresh initialized with refresh function"); console.log("BitcoinMinuteRefresh initialized with refresh function");
@ -93,10 +77,8 @@ $(document).ready(function () {
} }
}, 500); }, 500);
// Fetch worker data immediately on page load
fetchWorkerData(); fetchWorkerData();
// Set up filter button click handlers
$('.filter-button').click(function () { $('.filter-button').click(function () {
$('.filter-button').removeClass('active'); $('.filter-button').removeClass('active');
$(this).addClass('active'); $(this).addClass('active');
@ -104,7 +86,6 @@ $(document).ready(function () {
filterWorkers(); filterWorkers();
}); });
// Set up search input handler
$('#worker-search').on('input', function () { $('#worker-search').on('input', function () {
filterState.searchTerm = $(this).val().toLowerCase(); filterState.searchTerm = $(this).val().toLowerCase();
filterWorkers(); filterWorkers();
@ -115,15 +96,12 @@ $(document).ready(function () {
function initializePage() { function initializePage() {
console.log("Initializing page elements..."); console.log("Initializing page elements...");
// Initialize mini chart for total hashrate if the element exists
if (document.getElementById('total-hashrate-chart')) { if (document.getElementById('total-hashrate-chart')) {
initializeMiniChart(); initializeMiniChart();
} }
// Show loading state
$('#worker-grid').html('<div class="text-center p-5"><i class="fas fa-spinner fa-spin"></i> Loading worker data...</div>'); $('#worker-grid').html('<div class="text-center p-5"><i class="fas fa-spinner fa-spin"></i> Loading worker data...</div>');
// Add retry button (hidden by default)
if (!$('#retry-button').length) { if (!$('#retry-button').length) {
$('body').append('<button id="retry-button" style="position: fixed; bottom: 20px; left: 20px; z-index: 1000; background: #f7931a; color: black; border: none; padding: 8px 16px; display: none; border-radius: 4px; cursor: pointer;">Retry Loading Data</button>'); $('body').append('<button id="retry-button" style="position: fixed; bottom: 20px; left: 20px; z-index: 1000; background: #f7931a; color: black; border: none; padding: 8px 16px; display: none; border-radius: 4px; cursor: pointer;">Retry Loading Data</button>');
@ -157,10 +135,7 @@ function updateNotificationBadge() {
// Initialize notification badge checking // Initialize notification badge checking
function initNotificationBadge() { function initNotificationBadge() {
// Update immediately
updateNotificationBadge(); updateNotificationBadge();
// Update every 60 seconds
setInterval(updateNotificationBadge, 60000); setInterval(updateNotificationBadge, 60000);
} }
@ -168,7 +143,6 @@ function initNotificationBadge() {
function updateServerTime() { function updateServerTime() {
console.log("Updating server time..."); console.log("Updating server time...");
// First try to get stored values
try { try {
const storedOffset = localStorage.getItem('serverTimeOffset'); const storedOffset = localStorage.getItem('serverTimeOffset');
const storedStartTime = localStorage.getItem('serverStartTime'); const storedStartTime = localStorage.getItem('serverStartTime');
@ -178,31 +152,26 @@ function updateServerTime() {
serverStartTime = parseFloat(storedStartTime); serverStartTime = parseFloat(storedStartTime);
console.log("Using stored server time offset:", serverTimeOffset, "ms"); console.log("Using stored server time offset:", serverTimeOffset, "ms");
// Only update BitcoinMinuteRefresh if it's initialized
if (typeof BitcoinMinuteRefresh !== 'undefined' && BitcoinMinuteRefresh.updateServerTime) { if (typeof BitcoinMinuteRefresh !== 'undefined' && BitcoinMinuteRefresh.updateServerTime) {
BitcoinMinuteRefresh.updateServerTime(serverTimeOffset, serverStartTime); BitcoinMinuteRefresh.updateServerTime(serverTimeOffset, serverStartTime);
} }
return; // Don't fetch if we have valid values return;
} }
} catch (e) { } catch (e) {
console.error("Error reading stored server time:", e); console.error("Error reading stored server time:", e);
} }
// Fetch from API if needed
$.ajax({ $.ajax({
url: "/api/time", url: "/api/time",
method: "GET", method: "GET",
timeout: 5000, timeout: 5000,
success: function (data) { success: function (data) {
// Calculate the offset between server time and local time
serverTimeOffset = new Date(data.server_timestamp).getTime() - Date.now(); serverTimeOffset = new Date(data.server_timestamp).getTime() - Date.now();
serverStartTime = new Date(data.server_start_time).getTime(); serverStartTime = new Date(data.server_start_time).getTime();
// Store in localStorage for cross-page sharing
localStorage.setItem('serverTimeOffset', serverTimeOffset.toString()); localStorage.setItem('serverTimeOffset', serverTimeOffset.toString());
localStorage.setItem('serverStartTime', serverStartTime.toString()); localStorage.setItem('serverStartTime', serverStartTime.toString());
// Only update BitcoinMinuteRefresh if it's initialized
if (typeof BitcoinMinuteRefresh !== 'undefined' && BitcoinMinuteRefresh.updateServerTime) { if (typeof BitcoinMinuteRefresh !== 'undefined' && BitcoinMinuteRefresh.updateServerTime) {
BitcoinMinuteRefresh.updateServerTime(serverTimeOffset, serverStartTime); BitcoinMinuteRefresh.updateServerTime(serverTimeOffset, serverStartTime);
} }
@ -219,19 +188,16 @@ function updateServerTime() {
function fetchWorkerData(forceRefresh = false) { function fetchWorkerData(forceRefresh = false) {
console.log("Fetching worker data..."); console.log("Fetching worker data...");
// Track this as a manual refresh for throttling purposes
lastManualRefreshTime = Date.now(); lastManualRefreshTime = Date.now();
$('#worker-grid').addClass('loading-fade'); $('#worker-grid').addClass('loading-fade');
// Choose API URL based on whether we're forcing a refresh
const apiUrl = `/api/workers${forceRefresh ? '?force=true' : ''}`; const apiUrl = `/api/workers${forceRefresh ? '?force=true' : ''}`;
$.ajax({ $.ajax({
url: apiUrl, url: apiUrl,
method: 'GET', method: 'GET',
dataType: 'json', dataType: 'json',
timeout: 15000, // 15 second timeout timeout: 15000,
success: function (data) { success: function (data) {
if (!data || !data.workers || data.workers.length === 0) { if (!data || !data.workers || data.workers.length === 0) {
console.warn("No workers found in data response"); console.warn("No workers found in data response");
@ -245,21 +211,16 @@ function fetchWorkerData(forceRefresh = false) {
workerData = data; workerData = data;
// Notify BitcoinMinuteRefresh that we've refreshed the data
if (typeof BitcoinMinuteRefresh !== 'undefined' && BitcoinMinuteRefresh.notifyRefresh) { if (typeof BitcoinMinuteRefresh !== 'undefined' && BitcoinMinuteRefresh.notifyRefresh) {
BitcoinMinuteRefresh.notifyRefresh(); BitcoinMinuteRefresh.notifyRefresh();
} }
// Update UI with new data
updateWorkerGrid(); updateWorkerGrid();
updateSummaryStats(); updateSummaryStats();
updateMiniChart(); updateMiniChart();
updateLastUpdated(); updateLastUpdated();
// Hide retry button
$('#retry-button').hide(); $('#retry-button').hide();
// Reset connection retry count
connectionRetryCount = 0; connectionRetryCount = 0;
console.log("Worker data updated successfully"); console.log("Worker data updated successfully");
@ -267,7 +228,6 @@ function fetchWorkerData(forceRefresh = false) {
error: function (xhr, status, error) { error: function (xhr, status, error) {
console.error("Error fetching worker data:", error); console.error("Error fetching worker data:", error);
// Show error in worker grid
$('#worker-grid').html(` $('#worker-grid').html(`
<div class="text-center p-5 text-danger"> <div class="text-center p-5 text-danger">
<i class="fas fa-exclamation-triangle"></i> <i class="fas fa-exclamation-triangle"></i>
@ -275,16 +235,14 @@ function fetchWorkerData(forceRefresh = false) {
</div> </div>
`); `);
// Show retry button
$('#retry-button').show(); $('#retry-button').show();
// Implement exponential backoff for automatic retry
connectionRetryCount++; connectionRetryCount++;
const delay = Math.min(30000, 1000 * Math.pow(1.5, Math.min(5, connectionRetryCount))); const delay = Math.min(30000, 1000 * Math.pow(1.5, Math.min(5, connectionRetryCount)));
console.log(`Will retry in ${delay / 1000} seconds (attempt ${connectionRetryCount})`); console.log(`Will retry in ${delay / 1000} seconds (attempt ${connectionRetryCount})`);
setTimeout(() => { setTimeout(() => {
fetchWorkerData(true); // Force refresh on retry fetchWorkerData(true);
}, delay); }, delay);
}, },
complete: function () { complete: function () {
@ -305,7 +263,6 @@ function updateWorkerGrid() {
const workerGrid = $('#worker-grid'); const workerGrid = $('#worker-grid');
workerGrid.empty(); workerGrid.empty();
// Apply current filters before rendering
const filteredWorkers = filterWorkersData(workerData.workers); const filteredWorkers = filterWorkersData(workerData.workers);
if (filteredWorkers.length === 0) { if (filteredWorkers.length === 0) {
@ -318,107 +275,75 @@ function updateWorkerGrid() {
return; return;
} }
// Generate worker cards
filteredWorkers.forEach(worker => { filteredWorkers.forEach(worker => {
// Create worker card const card = createWorkerCard(worker);
const card = $('<div class="worker-card"></div>');
// Add class based on status
if (worker.status === 'online') {
card.addClass('worker-card-online');
} else {
card.addClass('worker-card-offline');
}
// Add worker type badge
card.append(`<div class="worker-type">${worker.type}</div>`);
// Add worker name
card.append(`<div class="worker-name">${worker.name}</div>`);
// Add status badge
if (worker.status === 'online') {
card.append('<div class="status-badge status-badge-online">ONLINE</div>');
} else {
card.append('<div class="status-badge status-badge-offline">OFFLINE</div>');
}
// Add hashrate bar with normalized values for consistent display
const maxHashrate = 200; // TH/s - adjust based on your fleet
const normalizedHashrate = normalizeHashrate(
worker.hashrate_3hr,
worker.hashrate_3hr_unit || 'th/s'
);
const hashratePercent = Math.min(100, (normalizedHashrate / maxHashrate) * 100);
// Format hashrate for display with appropriate unit
const formattedHashrate = formatHashrateForDisplay(
worker.hashrate_3hr,
worker.hashrate_3hr_unit || 'th/s'
);
card.append(`
<div class="worker-stats-row">
<div class="worker-stats-label">Hashrate (3hr):</div>
<div class="white-glow">${formattedHashrate}</div>
</div>
<div class="stats-bar-container">
<div class="stats-bar" style="width: ${hashratePercent}%"></div>
</div>
`);
// Add additional stats
card.append(`
<div class="worker-stats">
<div class="worker-stats-row">
<div class="worker-stats-label">Last Share:</div>
<div class="blue-glow">${typeof worker.last_share === 'string' ? worker.last_share.split(' ')[1] || worker.last_share : 'N/A'}</div>
</div>
<div class="worker-stats-row">
<div class="worker-stats-label">Earnings:</div>
<div class="green-glow">${worker.earnings.toFixed(8)}</div>
</div>
<div class="worker-stats-row">
<div class="worker-stats-label">Accept Rate:</div>
<div class="white-glow">${worker.acceptance_rate}%</div>
</div>
<div class="worker-stats-row">
<div class="worker-stats-label">Temp:</div>
<div class="${worker.temperature > 65 ? 'red-glow' : 'white-glow'}">${worker.temperature > 0 ? worker.temperature + '°C' : 'N/A'}</div>
</div>
</div>
`);
// Add card to grid
workerGrid.append(card); workerGrid.append(card);
}); });
} }
// Create worker card element
function createWorkerCard(worker) {
const card = $('<div class="worker-card"></div>');
card.addClass(worker.status === 'online' ? 'worker-card-online' : 'worker-card-offline');
card.append(`<div class="worker-type">${worker.type}</div>`);
card.append(`<div class="worker-name">${worker.name}</div>`);
card.append(`<div class="status-badge ${worker.status === 'online' ? 'status-badge-online' : 'status-badge-offline'}">${worker.status.toUpperCase()}</div>`);
const maxHashrate = 125; // TH/s - adjust based on your fleet
const normalizedHashrate = normalizeHashrate(worker.hashrate_3hr, worker.hashrate_3hr_unit || 'th/s');
const hashratePercent = Math.min(100, (normalizedHashrate / maxHashrate) * 100);
const formattedHashrate = formatHashrateForDisplay(worker.hashrate_3hr, worker.hashrate_3hr_unit || 'th/s');
card.append(`
<div class="worker-stats-row">
<div class="worker-stats-label">Hashrate (3hr):</div>
<div class="white-glow">${formattedHashrate}</div>
</div>
<div class="stats-bar-container">
<div class="stats-bar" style="width: ${hashratePercent}%"></div>
</div>
`);
card.append(`
<div class="worker-stats">
<div class="worker-stats-row">
<div class="worker-stats-label">Last Share:</div>
<div class="blue-glow">${typeof worker.last_share === 'string' ? worker.last_share.split(' ')[1] || worker.last_share : 'N/A'}</div>
</div>
<div class="worker-stats-row">
<div class="worker-stats-label">Earnings:</div>
<div class="green-glow">${worker.earnings.toFixed(8)}</div>
</div>
<div class="worker-stats-row">
<div class="worker-stats-label">Accept Rate:</div>
<div class="white-glow">${worker.acceptance_rate}%</div>
</div>
<div class="worker-stats-row">
<div class="worker-stats-label">Temp:</div>
<div class="${worker.temperature > 65 ? 'red-glow' : 'white-glow'}">${worker.temperature > 0 ? worker.temperature + '°C' : 'N/A'}</div>
</div>
</div>
`);
return card;
}
// Filter worker data based on current filter state // Filter worker data based on current filter state
function filterWorkersData(workers) { function filterWorkersData(workers) {
if (!workers) return []; if (!workers) return [];
return workers.filter(worker => { return workers.filter(worker => {
// Default to empty string if name is undefined
const workerName = (worker.name || '').toLowerCase(); const workerName = (worker.name || '').toLowerCase();
const isOnline = worker.status === 'online'; const isOnline = worker.status === 'online';
const workerType = (worker.type || '').toLowerCase(); const workerType = (worker.type || '').toLowerCase();
// Check if worker matches filter const matchesFilter = filterState.currentFilter === 'all' ||
let matchesFilter = false; (filterState.currentFilter === 'online' && isOnline) ||
if (filterState.currentFilter === 'all') { (filterState.currentFilter === 'offline' && !isOnline) ||
matchesFilter = true; (filterState.currentFilter === 'asic' && workerType === 'asic') ||
} else if (filterState.currentFilter === 'online' && isOnline) { (filterState.currentFilter === 'fpga' && workerType === 'fpga');
matchesFilter = true;
} else if (filterState.currentFilter === 'offline' && !isOnline) {
matchesFilter = true;
} else if (filterState.currentFilter === 'asic' && workerType === 'asic') {
matchesFilter = true;
} else if (filterState.currentFilter === 'fpga' && workerType === 'fpga') {
matchesFilter = true;
}
// Check if worker matches search term
const matchesSearch = filterState.searchTerm === '' || workerName.includes(filterState.searchTerm); const matchesSearch = filterState.searchTerm === '' || workerName.includes(filterState.searchTerm);
return matchesFilter && matchesSearch; return matchesFilter && matchesSearch;
@ -428,38 +353,25 @@ function filterWorkersData(workers) {
// Apply filter to rendered worker cards // Apply filter to rendered worker cards
function filterWorkers() { function filterWorkers() {
if (!workerData || !workerData.workers) return; if (!workerData || !workerData.workers) return;
// Re-render the worker grid with current filters
updateWorkerGrid(); updateWorkerGrid();
} }
// Modified updateSummaryStats function with normalized hashrate display // Update summary stats with normalized hashrate display
function updateSummaryStats() { function updateSummaryStats() {
if (!workerData) return; if (!workerData) return;
// Update worker counts
$('#workers-count').text(workerData.workers_total || 0); $('#workers-count').text(workerData.workers_total || 0);
$('#workers-online').text(workerData.workers_online || 0); $('#workers-online').text(workerData.workers_online || 0);
$('#workers-offline').text(workerData.workers_offline || 0); $('#workers-offline').text(workerData.workers_offline || 0);
// Update worker ring percentage const onlinePercent = workerData.workers_total > 0 ? workerData.workers_online / workerData.workers_total : 0;
const onlinePercent = workerData.workers_total > 0 ?
workerData.workers_online / workerData.workers_total : 0;
$('.worker-ring').css('--online-percent', onlinePercent); $('.worker-ring').css('--online-percent', onlinePercent);
// Display normalized hashrate with appropriate unit const formattedHashrate = workerData.total_hashrate !== undefined ?
if (workerData.total_hashrate !== undefined) { formatHashrateForDisplay(workerData.total_hashrate, workerData.hashrate_unit || 'TH/s') :
// Format with proper unit conversion '0.0 TH/s';
const formattedHashrate = formatHashrateForDisplay( $('#total-hashrate').text(formattedHashrate);
workerData.total_hashrate,
workerData.hashrate_unit || 'TH/s'
);
$('#total-hashrate').text(formattedHashrate);
} else {
$('#total-hashrate').text(`0.0 TH/s`);
}
// Update other summary stats
$('#total-earnings').text(`${(workerData.total_earnings || 0).toFixed(8)} BTC`); $('#total-earnings').text(`${(workerData.total_earnings || 0).toFixed(8)} BTC`);
$('#daily-sats').text(`${numberWithCommas(workerData.daily_sats || 0)} sats`); $('#daily-sats').text(`${numberWithCommas(workerData.daily_sats || 0)} sats`);
$('#avg-acceptance-rate').text(`${(workerData.avg_acceptance_rate || 0).toFixed(2)}%`); $('#avg-acceptance-rate').text(`${(workerData.avg_acceptance_rate || 0).toFixed(2)}%`);
@ -475,7 +387,6 @@ function initializeMiniChart() {
return; return;
} }
// Generate some sample data to start
const labels = Array(24).fill('').map((_, i) => i); const labels = Array(24).fill('').map((_, i) => i);
const data = Array(24).fill(0).map(() => Math.random() * 100 + 700); const data = Array(24).fill(0).map(() => Math.random() * 100 + 700);
@ -525,32 +436,23 @@ function updateMiniChart() {
return; return;
} }
// Extract hashrate data from history
const historyData = workerData.hashrate_history; const historyData = workerData.hashrate_history;
if (!historyData || historyData.length === 0) { if (!historyData || historyData.length === 0) {
console.log("No hashrate history data available"); console.log("No hashrate history data available");
return; return;
} }
// Get the normalized values for the chart const values = historyData.map(item => normalizeHashrate(parseFloat(item.value) || 0, item.unit || workerData.hashrate_unit || 'th/s'));
const values = historyData.map(item => {
const val = parseFloat(item.value) || 0;
const unit = item.unit || workerData.hashrate_unit || 'th/s';
return normalizeHashrate(val, unit);
});
const labels = historyData.map(item => item.time); const labels = historyData.map(item => item.time);
// Update chart data
miniChart.data.labels = labels; miniChart.data.labels = labels;
miniChart.data.datasets[0].data = values; miniChart.data.datasets[0].data = values;
// Update y-axis range
const min = Math.min(...values.filter(v => v > 0)) || 0; const min = Math.min(...values.filter(v => v > 0)) || 0;
const max = Math.max(...values) || 1; const max = Math.max(...values) || 1;
miniChart.options.scales.y.min = min * 0.9; miniChart.options.scales.y.min = min * 0.9;
miniChart.options.scales.y.max = max * 1.1; miniChart.options.scales.y.max = max * 1.1;
// Update the chart
miniChart.update('none'); miniChart.update('none');
} }