// We'll fetch data from the API instead of hardcoding it
let allEndpointData = [];
let filteredData = [];
// We'll store these as global variables that get updated when we fetch data
let sortedData = [];
let totalEndpoints = 0;
let totalVisits = 0;
// Chart instance
let myChart;
// Function to get chart colors based on current theme
function getChartColors() {
var style = window.getComputedStyle(document.body)
const colours = {
textColor: style.getPropertyValue('--md-sys-color-on-surface') ,
primaryColor: style.getPropertyValue('--md-sys-color-primary'),
backgroundColor: style.getPropertyValue('--md-sys-color-background'),
gridColor: style.getPropertyValue('--md-sys-color-on-surface'),
tooltipBgColor: style.getPropertyValue('--md-sys-color-inverse-on-surface'),
tooltipTextColor: style.getPropertyValue('--md-sys-color-inverse-surface')
}
return colours;
}
// Watch for theme changes and update chart if needed
function setupThemeChangeListener() {
// Start observing theme changes
document.addEventListener("modeChanged", (event) => {
setTimeout(function() {
if (myChart) {
const currentLimit = document.getElementById('currentlyShowing').textContent;
const limit = (currentLimit === endpointStatsTranslations.all)
? filteredData.length
: (currentLimit === endpointStatsTranslations.top20 ? 20 : 10);
updateChart(limit);
}
}, 100);
});
// Also watch for system preference changes
window
.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', () => {
if (myChart) {
const currentLimit = document.getElementById('currentlyShowing').textContent;
const limit = (currentLimit === endpointStatsTranslations.all)
? filteredData.length
: (currentLimit === endpointStatsTranslations.top20 ? 20 : 10);
updateChart(limit);
}
});
}
// Function to filter data based on checkbox settings
function filterData() {
const includeHome = document.getElementById('hideHomeCheckbox').checked;
const includeLogin = document.getElementById('hideLoginCheckbox').checked;
filteredData = allEndpointData.filter(item => {
if (!includeHome && item.endpoint === '/') return false;
if (!includeLogin && item.endpoint === '/login') return false;
return true;
});
// Sort and calculate
sortedData = [...filteredData].sort((a, b) => b.count - a.count);
totalEndpoints = filteredData.length;
totalVisits = filteredData.reduce((sum, item) => sum + item.count, 0);
// Update stats
document.getElementById('totalEndpoints').textContent = totalEndpoints.toLocaleString();
document.getElementById('totalVisits').textContent = totalVisits.toLocaleString();
// Update the chart with current limit
const currentLimit = document.getElementById('currentlyShowing').textContent;
const limit = (currentLimit === endpointStatsTranslations.all)
? filteredData.length
: (currentLimit === endpointStatsTranslations.top20 ? 20 : 10);
updateChart(limit);
}
// Function to fetch data from the API
async function fetchEndpointData() {
try {
// Show loading state
const chartContainer = document.querySelector('.chart-container');
const loadingDiv = document.createElement('div');
loadingDiv.className = 'loading';
loadingDiv.innerHTML = `
${endpointStatsTranslations.loading}
`;
chartContainer.appendChild(loadingDiv);
// Also add animation to refresh button
const refreshBtn = document.getElementById('refreshBtn');
refreshBtn.classList.add('refreshing');
refreshBtn.disabled = true;
const response = await fetch('/api/v1/info/load/all');
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
allEndpointData = data;
// Apply filters
filterData();
// Remove loading state
chartContainer.removeChild(loadingDiv);
refreshBtn.classList.remove('refreshing');
refreshBtn.disabled = false;
} catch (error) {
console.error('Error fetching endpoint data:', error);
// Show error message to user
showError(endpointStatsTranslations.failedToLoad);
// Reset refresh button
const refreshBtn = document.getElementById('refreshBtn');
refreshBtn.classList.remove('refreshing');
refreshBtn.disabled = false;
}
}
// Function to format endpoint names
function formatEndpointName(endpoint) {
if (endpoint === '/') return endpointStatsTranslations.home;
if (endpoint === '/login') return endpointStatsTranslations.login;
return endpoint.replace('/', '').replace(/-/g, ' ');
}
// Function to update the table
function updateTable(data) {
const tableBody = document.getElementById('endpointTableBody');
tableBody.innerHTML = '';
data.forEach((item, index) => {
const percentage = ((item.count / totalVisits) * 100).toFixed(2);
const row = document.createElement('tr');
// Format endpoint for better readability
let displayEndpoint = item.endpoint;
if (displayEndpoint.length > 40) {
displayEndpoint = displayEndpoint.substring(0, 37) + '...';
}
row.innerHTML = `
${index + 1} |
${displayEndpoint} |
${item.count.toLocaleString()} |
${percentage}% |
`;
tableBody.appendChild(row);
});
}
// Function to update the chart
function updateChart(dataLimit) {
const chartData = sortedData.slice(0, dataLimit);
// Calculate displayed statistics
const displayedVisits = chartData.reduce((sum, item) => sum + item.count, 0);
const displayedPercentage = totalVisits > 0
? ((displayedVisits / totalVisits) * 100).toFixed(2)
: '0';
document.getElementById('displayedVisits').textContent = displayedVisits.toLocaleString();
document.getElementById('displayedPercentage').textContent = displayedPercentage;
// If the limit equals the total filtered items, show "All"; otherwise "Top X"
document.getElementById('currentlyShowing').textContent =
(dataLimit === filteredData.length)
? endpointStatsTranslations.all
: endpointStatsTranslations.top + dataLimit;
// Update the table with new data
updateTable(chartData);
// Prepare labels and datasets
const labels = chartData.map(item => formatEndpointName(item.endpoint));
const data = chartData.map(item => item.count);
// Get theme-specific colors
const colors = getChartColors();
// Destroy previous chart if it exists
if (myChart) {
myChart.destroy();
}
// Create chart context
const ctx = document.getElementById('endpointChart').getContext('2d');
// Create new chart with theme-appropriate colors
myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: endpointStatsTranslations.numberOfVisits,
data: data,
backgroundColor: colors.primaryColor.replace('rgb', 'rgba').replace(')', ', 0.6)'),
borderColor: colors.primaryColor,
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
indexAxis: dataLimit > 20 ? 'x' : 'y',
plugins: {
legend: {
display: true,
position: 'top',
labels: {
color: colors.primaryColor,
font: {
weight: 'bold'
}
}
},
tooltip: {
backgroundColor: colors.tooltipBgColor,
titleColor: colors.tooltipTextColor,
bodyColor: colors.tooltipTextColor,
borderColor: colors.tooltipBgColor,
borderWidth: 1,
padding: 12,
cornerRadius: 8,
titleFont: {
size: 14,
weight: 'bold'
},
bodyFont: {
size: 13
},
callbacks: {
label: (context) => {
const value = context.raw;
const percentage = totalVisits > 0
? ((value / totalVisits) * 100).toFixed(2)
: '0';
// Insert your i18n text in the final string:
// e.g. "Visits: 12 (34% of total)"
// If your translation includes placeholders, you'd parse them here:
return endpointStatsTranslations.visitsTooltip
.replace('{0}', value.toLocaleString())
.replace('{1}', percentage);
}
}
}
},
scales: {
x: {
border: {
color: colors.gridColor
},
ticks: {
color: colors.gridColor,
font: {
size: 12
},
callback: function(value, index, values) {
let label = this.getLabelForValue(value);
return label.length > 15 ? label.substr(0, 15) + '...' : label;
}
},
grid: {
color: `${colors.gridColor}`
}
},
y: {
border: {
color: colors.gridColor
},
min: 0,
ticks: {
color: colors.gridColor,
font: {
size: 12
},
precision: 0
},
grid: {
color: `${colors.gridColor}`
}
}
}
}
});
}
// Initialize with fetch and top 10
document.addEventListener('DOMContentLoaded', function() {
// Set up theme change listener
setupThemeChangeListener();
// Initial data fetch
fetchEndpointData();
// Set up button event listeners
document.getElementById('top10Btn').addEventListener('click', function() {
updateChart(10);
setActiveButton(this);
});
document.getElementById('top20Btn').addEventListener('click', function() {
updateChart(20);
setActiveButton(this);
});
document.getElementById('allBtn').addEventListener('click', function() {
updateChart(filteredData.length);
setActiveButton(this);
});
document.getElementById('refreshBtn').addEventListener('click', function() {
fetchEndpointData();
});
// Set up filter checkbox listeners
document.getElementById('hideHomeCheckbox').addEventListener('change', filterData);
document.getElementById('hideLoginCheckbox').addEventListener('change', filterData);
});
function setActiveButton(activeButton) {
// Remove active class from all buttons
document.querySelectorAll('.chart-controls button').forEach(button => {
button.classList.remove('active');
});
// Add active class to clicked button
activeButton.classList.add('active');
}
// Function to handle errors in a user-friendly way
function showError(message) {
const chartContainer = document.querySelector('.chart-container');
const errorDiv = document.createElement('div');
errorDiv.className = 'alert alert-danger';
errorDiv.innerHTML = `
error
${message}
`;
chartContainer.innerHTML = '';
chartContainer.appendChild(errorDiv);
// Add retry button functionality
document.getElementById('errorRetryBtn').addEventListener('click', fetchEndpointData);
}