This commit is contained in:
fabriziosalmi
2025-02-03 00:39:15 +01:00
parent f6031094a9
commit 6b94875c0a

View File

@@ -426,377 +426,377 @@
</div> </div>
<script> <script>
// Get DOM elements window.addEventListener('DOMContentLoaded', function() {
const totalRequestsElement = document.getElementById('total-requests'); // Get DOM elements
const allowedRequestsElement = document.getElementById('allowed-requests'); const totalRequestsElement = document.getElementById('total-requests');
const blockedRequestsElement = document.getElementById('blocked-requests'); const allowedRequestsElement = document.getElementById('allowed-requests');
const blockedPercentageElement = document.getElementById('blocked-percentage'); const blockedRequestsElement = document.getElementById('blocked-requests');
const phase1HitsElement = document.getElementById('phase-1-hits'); const blockedPercentageElement = document.getElementById('blocked-percentage');
const phase2HitsElement = document.getElementById('phase-2-hits'); const phase1HitsElement = document.getElementById('phase-1-hits');
const phase3HitsElement = document.getElementById('phase-3-hits'); const phase2HitsElement = document.getElementById('phase-2-hits');
const phase4HitsElement = document.getElementById('phase-4-hits'); const phase3HitsElement = document.getElementById('phase-3-hits');
const dnsBlacklistHitsElement = document.getElementById('dns-blacklist-hits'); const phase4HitsElement = document.getElementById('phase-4-hits');
const ipBlacklistHitsElement = document.getElementById('ip-blacklist-hits'); const dnsBlacklistHitsElement = document.getElementById('dns-blacklist-hits');
const rateLimitHitsElement = document.getElementById('rate-limit-hits'); const ipBlacklistHitsElement = document.getElementById('ip-blacklist-hits');
const geoipHitsElement = document.getElementById('geoip-hits'); const rateLimitHitsElement = document.getElementById('rate-limit-hits');
const geoipStatsContainer = document.getElementById('geoip-stats-container'); const geoipHitsElement = document.getElementById('geoip-hits');
const rulesTableBody = document.getElementById('rules-table-body'); const geoipStatsContainer = document.getElementById('geoip-stats-container');
const timelineChartElement = document.getElementById('timeline-chart').getContext('2d'); const rulesTableBody = document.getElementById('rules-table-body');
const logScaleToggle = document.getElementById('log-scale-toggle'); const timelineChartElement = document.getElementById('timeline-chart').getContext('2d');
const themeToggle = document.getElementById('theme-toggle'); const logScaleToggle = document.getElementById('log-scale-toggle');
const root = document.documentElement; const themeToggle = document.getElementById('theme-toggle');
let allRuleHitsData = {}; // Store all rule hits const root = document.documentElement;
let hasGeoIPData = false; // Flag to track if geoip data has loaded let allRuleHitsData = {}; // Store all rule hits
const localStorageThemeKey = 'dashboardTheme'; let hasGeoIPData = false; // Flag to track if geoip data has loaded
const localStorageThemeKey = 'dashboardTheme';
// --- Placeholder Timeline Data --- // --- Placeholder Timeline Data ---
const timelineLabels = ['Now']; // Initial label const timelineLabels = ['Now']; // Initial label
const totalRequestsData = [0]; // Initial data point const totalRequestsData = [0]; // Initial data point
const blockedRequestsData = [0]; // Initial data point const blockedRequestsData = [0]; // Initial data point
const dnsBlacklistHitsData = [0]; const dnsBlacklistHitsData = [0];
const ipBlacklistHitsData = [0]; const ipBlacklistHitsData = [0];
const rateLimitHitsData = [0]; const rateLimitHitsData = [0];
const geoipHitsData = [0]; const geoipHitsData = [0];
const phase1HitsData = [0]; const phase1HitsData = [0];
const phase2HitsData = [0]; const phase2HitsData = [0];
const phase3HitsData = [0]; const phase3HitsData = [0];
const phase4HitsData = [0]; const phase4HitsData = [0];
// --- Initialize Chart --- // --- Initialize Chart ---
const initialDatasets = [{ const initialDatasets = [{
label: 'Total Requests', label: 'Total Requests',
data: totalRequestsData, data: totalRequestsData,
borderColor: '#367edb', borderColor: '#367edb',
backgroundColor: '#367edb', backgroundColor: '#367edb',
tension: 0.4 tension: 0.4
}, { }, {
label: 'Blocked Requests', label: 'Blocked Requests',
data: blockedRequestsData, data: blockedRequestsData,
borderColor: '#e74c3c', borderColor: '#e74c3c',
backgroundColor: '#e74c3c', backgroundColor: '#e74c3c',
tension: 0.4 tension: 0.4
}]; }];
// Helper function to check if any element in an array is non-zero // Helper function to check if any element in an array is non-zero
function hasNonZeroValue(arr) { function hasNonZeroValue(arr) {
return arr.some(val => val !== 0); return arr.some(val => val !== 0);
} }
// Create the chart // Create the chart
const timelineChart = new Chart(timelineChartElement, { const timelineChart = new Chart(timelineChartElement, {
type: 'line', type: 'line',
data: { data: {
labels: timelineLabels, labels: timelineLabels,
datasets: initialDatasets, datasets: initialDatasets,
}, },
options: { options: {
responsive: true, responsive: true,
maintainAspectRatio: false, maintainAspectRatio: false,
scales: { scales: {
y: { y: {
type: 'linear', type: 'linear',
beginAtZero: true, beginAtZero: true,
grid: { color: '#444' }, grid: { color: '#444' },
ticks: { ticks: {
color: '#eee', callback: function (value, index, ticks) { color: '#eee', callback: function (value, index, ticks) {
if (value === 0) return 0; if (value === 0) return 0;
return value >= 1000 ? value / 1000 + "k" : value; return value >= 1000 ? value / 1000 + "k" : value;
}
} }
},
x: {
grid: { color: '#444' },
ticks: { color: '#eee' }
} }
}, },
x: { plugins: {
grid: { color: '#444' }, legend: {
ticks: { color: '#eee' } labels: { color: '#eee' }
}
} }
},
plugins: {
legend: {
labels: { color: '#eee' }
}
}
}
});
function updateChartOptions() {
const currentTheme = root.getAttribute('data-theme');
timelineChart.options.plugins.legend.labels.color = currentTheme === 'light' ? '#333' : '#eee';
timelineChart.update()
}
// Event listener for the log scale toggle
logScaleToggle.addEventListener('change', function () {
timelineChart.options.scales.y.type = this.checked ? 'logarithmic' : 'linear';
timelineChart.update();
});
// Function to manage chart dataset visibility
function updateChartDatasets() {
const datasetsToRemove = [];
timelineChart.data.datasets.forEach((dataset, index) => {
if (dataset.label === 'DNS Blacklist Hits' && !hasNonZeroValue(dnsBlacklistHitsData)) datasetsToRemove.push(index);
if (dataset.label === 'IP Blacklist Hits' && !hasNonZeroValue(ipBlacklistHitsData)) datasetsToRemove.push(index);
if (dataset.label === 'Rate Limit Hits' && !hasNonZeroValue(rateLimitHitsData)) datasetsToRemove.push(index);
if (dataset.label === 'GeoIP Hits' && !hasNonZeroValue(geoipHitsData)) datasetsToRemove.push(index);
if (dataset.label === 'Phase 1 Hits' && !hasNonZeroValue(phase1HitsData)) datasetsToRemove.push(index);
if (dataset.label === 'Phase 2 Hits' && !hasNonZeroValue(phase2HitsData)) datasetsToRemove.push(index);
if (dataset.label === 'Phase 3 Hits' && !hasNonZeroValue(phase3HitsData)) datasetsToRemove.push(index);
if (dataset.label === 'Phase 4 Hits' && !hasNonZeroValue(phase4HitsData)) datasetsToRemove.push(index);
});
// Remove datasets in reverse order to avoid index issues
for (let i = datasetsToRemove.length - 1; i >= 0; i--) {
timelineChart.data.datasets.splice(datasetsToRemove[i], 1);
}
// Add data sets only if needed
if (hasNonZeroValue(dnsBlacklistHitsData) && !timelineChart.data.datasets.find(dataset => dataset.label === 'DNS Blacklist Hits')) {
timelineChart.data.datasets.push({
label: 'DNS Blacklist Hits',
data: dnsBlacklistHitsData,
borderColor: '#FFA500',
backgroundColor: '#FFA500',
tension: 0.4
})
}
if (hasNonZeroValue(ipBlacklistHitsData) && !timelineChart.data.datasets.find(dataset => dataset.label === 'IP Blacklist Hits')) {
timelineChart.data.datasets.push({
label: 'IP Blacklist Hits',
data: ipBlacklistHitsData,
borderColor: '#800080',
backgroundColor: '#800080',
tension: 0.4
});
}
if (hasNonZeroValue(rateLimitHitsData) && !timelineChart.data.datasets.find(dataset => dataset.label === 'Rate Limit Hits')) {
timelineChart.data.datasets.push({
label: 'Rate Limit Hits',
data: rateLimitHitsData,
borderColor: '#008000',
backgroundColor: '#008000',
tension: 0.4
});
}
if (hasNonZeroValue(geoipHitsData) && !timelineChart.data.datasets.find(dataset => dataset.label === 'GeoIP Hits')) {
timelineChart.data.datasets.push({
label: 'GeoIP Hits',
data: geoipHitsData,
borderColor: '#8B4513',
backgroundColor: '#8B4513',
tension: 0.4
});
}
if (hasNonZeroValue(phase1HitsData) && !timelineChart.data.datasets.find(dataset => dataset.label === 'Phase 1 Hits')) {
timelineChart.data.datasets.push({
label: 'Phase 1 Hits',
data: phase1HitsData,
borderColor: '#0000FF',
backgroundColor: '#0000FF',
tension: 0.4
});
}
if (hasNonZeroValue(phase2HitsData) && !timelineChart.data.datasets.find(dataset => dataset.label === 'Phase 2 Hits')) {
timelineChart.data.datasets.push({
label: 'Phase 2 Hits',
data: phase2HitsData,
borderColor: '#FF0000',
backgroundColor: '#FF0000',
tension: 0.4
})
}
if (hasNonZeroValue(phase3HitsData) && !timelineChart.data.datasets.find(dataset => dataset.label === 'Phase 3 Hits')) {
timelineChart.data.datasets.push({
label: 'Phase 3 Hits',
data: phase3HitsData,
borderColor: '#00FFFF',
backgroundColor: '#00FFFF',
tension: 0.4
})
}
if (hasNonZeroValue(phase4HitsData) && !timelineChart.data.datasets.find(dataset => dataset.label === 'Phase 4 Hits')) {
timelineChart.data.datasets.push({
label: 'Phase 4 Hits',
data: phase4HitsData,
borderColor: '#FF00FF',
backgroundColor: '#FF00FF',
tension: 0.4
});
}
timelineChart.update();
}
// Function to fetch and update metrics
function updateMetrics() {
// change this to your own metrics endpoint
fetch('http://localhost:8080/waf_metrics', {
headers: {
'User-Agent': 'caddy-waf-ui'
}
})
.then(response => response.json())
.then(data => {
updateValue(totalRequestsElement, data.total_requests);
updateValue(allowedRequestsElement, data.allowed_requests);
updateValue(blockedRequestsElement, data.blocked_requests);
updateValue(dnsBlacklistHitsElement, data.dns_blacklist_hits);
updateValue(ipBlacklistHitsElement, data.ip_blacklist_hits);
// Update the rate-limit-hits with rate_limiter_blocked_requests
updateValue(rateLimitHitsElement, data.rate_limiter_blocked_requests);
// Use the length of the geoip_stats object
updateValue(geoipHitsElement, Object.keys(data.geoip_stats || {}).length);
const totalRequests = data.total_requests;
const blockedRequests = data.blocked_requests;
let blockedPercent = 0;
if (totalRequests > 0) {
blockedPercent = ((blockedRequests / totalRequests) * 100).toFixed(1);
}
updateValue(blockedPercentageElement, `${blockedPercent}%`);
allRuleHitsData = data.rule_hits;
updateRulesTableDisplay();
phase1HitsElement.textContent = data.rule_hits_by_phase['1'] || 0;
phase2HitsElement.textContent = data.rule_hits_by_phase['2'] || 0;
phase3HitsElement.textContent = data.rule_hits_by_phase['3'] || 0;
phase4HitsElement.textContent = data.rule_hits_by_phase['4'] || 0;
updateGeoIPStatsDisplay(data.geoip_stats);
const now = new Date().toLocaleTimeString();
timelineLabels.push(now);
totalRequestsData.push(data.total_requests);
blockedRequestsData.push(data.blocked_requests);
dnsBlacklistHitsData.push(data.dns_blacklist_hits);
ipBlacklistHitsData.push(data.ip_blacklist_hits);
// Update timeline for rateLimitHitsData with rate_limiter_blocked_requests
rateLimitHitsData.push(data.rate_limiter_blocked_requests);
geoipHitsData.push(Object.keys(data.geoip_stats || {}).length);
phase1HitsData.push(data.rule_hits_by_phase['1'] || 0);
phase2HitsData.push(data.rule_hits_by_phase['2'] || 0);
phase3HitsData.push(data.rule_hits_by_phase['3'] || 0);
phase4HitsData.push(data.rule_hits_by_phase['4'] || 0);
if (timelineLabels.length > 10) {
timelineLabels.shift();
totalRequestsData.shift();
blockedRequestsData.shift();
dnsBlacklistHitsData.shift();
ipBlacklistHitsData.shift();
rateLimitHitsData.shift();
geoipHitsData.shift();
phase1HitsData.shift();
phase2HitsData.shift();
phase3HitsData.shift();
phase4HitsData.shift();
}
updateChartDatasets();
})
.catch(error => {
console.error('Error fetching metrics data:', error);
rulesTableBody.innerHTML = `<tr><td colspan="2" style="text-align: center; padding: 20px; color: var(--error-color);">Error loading data.</td></tr>`;
geoipStatsContainer.innerHTML = `<p style="color: var(--error-color);">Error loading GeoIP data.</p>`;
});
}
// Function to update rule table display
function updateRulesTableDisplay() {
const previousRuleHits = allRuleHitsData;
rulesTableBody.innerHTML = '';
const sortedRuleEntries = Object.entries(allRuleHitsData)
.sort(([, countA], [, countB]) => countB - countA);
if (sortedRuleEntries.length === 0) {
rulesTableBody.innerHTML = `<tr><td colspan="2" style="text-align: center; padding: 20px; color: var(--label-color);">No rule hits yet.</td></tr>`;
return;
}
sortedRuleEntries.forEach(([ruleName, hitCount]) => {
const row = rulesTableBody.insertRow();
const nameCell = row.insertCell();
const hitsCell = row.insertCell();
nameCell.textContent = ruleName;
hitsCell.textContent = hitCount;
if (previousRuleHits[ruleName] !== undefined && previousRuleHits[ruleName] !== hitCount) {
hitsCell.classList.add('rule-hits-updating');
setTimeout(() => {
hitsCell.classList.remove('rule-hits-updating');
}, 300)
} }
}); });
}
// Function to update GeoIP Stats display function updateChartOptions() {
function updateGeoIPStatsDisplay(geoipStats) { const currentTheme = root.getAttribute('data-theme');
geoipStatsContainer.innerHTML = ''; timelineChart.options.plugins.legend.labels.color = currentTheme === 'light' ? '#333' : '#eee';
if (!geoipStats || Object.keys(geoipStats).length === 0) { timelineChart.update()
geoipStatsContainer.innerHTML = `<p style="color: var(--label-color);">No GeoIP data yet.</p>`; }
// Event listener for the log scale toggle
logScaleToggle.addEventListener('change', function () {
timelineChart.options.scales.y.type = this.checked ? 'logarithmic' : 'linear';
timelineChart.update();
});
// Function to manage chart dataset visibility
function updateChartDatasets() {
const datasetsToRemove = [];
timelineChart.data.datasets.forEach((dataset, index) => {
if (dataset.label === 'DNS Blacklist Hits' && !hasNonZeroValue(dnsBlacklistHitsData)) datasetsToRemove.push(index);
if (dataset.label === 'IP Blacklist Hits' && !hasNonZeroValue(ipBlacklistHitsData)) datasetsToRemove.push(index);
if (dataset.label === 'Rate Limit Hits' && !hasNonZeroValue(rateLimitHitsData)) datasetsToRemove.push(index);
if (dataset.label === 'GeoIP Hits' && !hasNonZeroValue(geoipHitsData)) datasetsToRemove.push(index);
if (dataset.label === 'Phase 1 Hits' && !hasNonZeroValue(phase1HitsData)) datasetsToRemove.push(index);
if (dataset.label === 'Phase 2 Hits' && !hasNonZeroValue(phase2HitsData)) datasetsToRemove.push(index);
if (dataset.label === 'Phase 3 Hits' && !hasNonZeroValue(phase3HitsData)) datasetsToRemove.push(index);
if (dataset.label === 'Phase 4 Hits' && !hasNonZeroValue(phase4HitsData)) datasetsToRemove.push(index);
});
// Remove datasets in reverse order to avoid index issues
for (let i = datasetsToRemove.length - 1; i >= 0; i--) {
timelineChart.data.datasets.splice(datasetsToRemove[i], 1);
}
// Add data sets only if needed
if (hasNonZeroValue(dnsBlacklistHitsData) && !timelineChart.data.datasets.find(dataset => dataset.label === 'DNS Blacklist Hits')) {
timelineChart.data.datasets.push({
label: 'DNS Blacklist Hits',
data: dnsBlacklistHitsData,
borderColor: '#FFA500',
backgroundColor: '#FFA500',
tension: 0.4
})
}
if (hasNonZeroValue(ipBlacklistHitsData) && !timelineChart.data.datasets.find(dataset => dataset.label === 'IP Blacklist Hits')) {
timelineChart.data.datasets.push({
label: 'IP Blacklist Hits',
data: ipBlacklistHitsData,
borderColor: '#800080',
backgroundColor: '#800080',
tension: 0.4
});
}
if (hasNonZeroValue(rateLimitHitsData) && !timelineChart.data.datasets.find(dataset => dataset.label === 'Rate Limit Hits')) {
timelineChart.data.datasets.push({
label: 'Rate Limit Hits',
data: rateLimitHitsData,
borderColor: '#008000',
backgroundColor: '#008000',
tension: 0.4
});
}
if (hasNonZeroValue(geoipHitsData) && !timelineChart.data.datasets.find(dataset => dataset.label === 'GeoIP Hits')) {
timelineChart.data.datasets.push({
label: 'GeoIP Hits',
data: geoipHitsData,
borderColor: '#8B4513',
backgroundColor: '#8B4513',
tension: 0.4
});
}
if (hasNonZeroValue(phase1HitsData) && !timelineChart.data.datasets.find(dataset => dataset.label === 'Phase 1 Hits')) {
timelineChart.data.datasets.push({
label: 'Phase 1 Hits',
data: phase1HitsData,
borderColor: '#0000FF',
backgroundColor: '#0000FF',
tension: 0.4
});
}
if (hasNonZeroValue(phase2HitsData) && !timelineChart.data.datasets.find(dataset => dataset.label === 'Phase 2 Hits')) {
timelineChart.data.datasets.push({
label: 'Phase 2 Hits',
data: phase2HitsData,
borderColor: '#FF0000',
backgroundColor: '#FF0000',
tension: 0.4
})
}
if (hasNonZeroValue(phase3HitsData) && !timelineChart.data.datasets.find(dataset => dataset.label === 'Phase 3 Hits')) {
timelineChart.data.datasets.push({
label: 'Phase 3 Hits',
data: phase3HitsData,
borderColor: '#00FFFF',
backgroundColor: '#00FFFF',
tension: 0.4
})
}
if (hasNonZeroValue(phase4HitsData) && !timelineChart.data.datasets.find(dataset => dataset.label === 'Phase 4 Hits')) {
timelineChart.data.datasets.push({
label: 'Phase 4 Hits',
data: phase4HitsData,
borderColor: '#FF00FF',
backgroundColor: '#FF00FF',
tension: 0.4
});
}
timelineChart.update();
}
// Function to fetch and update metrics
function updateMetrics() {
// change this to your own metrics endpoint
fetch('http://localhost:8080/waf_metrics', {
headers: {
'User-Agent': 'caddy-waf-ui'
}
})
.then(response => response.json())
.then(data => {
updateValue(totalRequestsElement, data.total_requests);
updateValue(allowedRequestsElement, data.allowed_requests);
updateValue(blockedRequestsElement, data.blocked_requests);
updateValue(dnsBlacklistHitsElement, data.dns_blacklist_hits);
updateValue(ipBlacklistHitsElement, data.ip_blacklist_hits);
// Update the rate-limit-hits with rate_limiter_blocked_requests
updateValue(rateLimitHitsElement, data.rate_limiter_blocked_requests);
// Use the length of the geoip_stats object
updateValue(geoipHitsElement, Object.keys(data.geoip_stats || {}).length);
const totalRequests = data.total_requests;
const blockedRequests = data.blocked_requests;
let blockedPercent = 0;
if (totalRequests > 0) {
blockedPercent = ((blockedRequests / totalRequests) * 100).toFixed(1);
}
updateValue(blockedPercentageElement, `${blockedPercent}%`);
allRuleHitsData = data.rule_hits;
updateRulesTableDisplay();
phase1HitsElement.textContent = data.rule_hits_by_phase['1'] || 0;
phase2HitsElement.textContent = data.rule_hits_by_phase['2'] || 0;
phase3HitsElement.textContent = data.rule_hits_by_phase['3'] || 0;
phase4HitsElement.textContent = data.rule_hits_by_phase['4'] || 0;
updateGeoIPStatsDisplay(data.geoip_stats);
const now = new Date().toLocaleTimeString();
timelineLabels.push(now);
totalRequestsData.push(data.total_requests);
blockedRequestsData.push(data.blocked_requests);
dnsBlacklistHitsData.push(data.dns_blacklist_hits);
ipBlacklistHitsData.push(data.ip_blacklist_hits);
// Update timeline for rateLimitHitsData with rate_limiter_blocked_requests
rateLimitHitsData.push(data.rate_limiter_blocked_requests);
geoipHitsData.push(Object.keys(data.geoip_stats || {}).length);
phase1HitsData.push(data.rule_hits_by_phase['1'] || 0);
phase2HitsData.push(data.rule_hits_by_phase['2'] || 0);
phase3HitsData.push(data.rule_hits_by_phase['3'] || 0);
phase4HitsData.push(data.rule_hits_by_phase['4'] || 0);
if (timelineLabels.length > 10) {
timelineLabels.shift();
totalRequestsData.shift();
blockedRequestsData.shift();
dnsBlacklistHitsData.shift();
ipBlacklistHitsData.shift();
rateLimitHitsData.shift();
geoipHitsData.shift();
phase1HitsData.shift();
phase2HitsData.shift();
phase3HitsData.shift();
phase4HitsData.shift();
}
updateChartDatasets();
})
.catch(error => {
console.error('Error fetching metrics data:', error);
rulesTableBody.innerHTML = `<tr><td colspan="2" style="text-align: center; padding: 20px; color: var(--error-color);">Error loading data.</td></tr>`;
geoipStatsContainer.innerHTML = `<p style="color: var(--error-color);">Error loading GeoIP data.</p>`;
});
}
// Function to update rule table display
function updateRulesTableDisplay() {
const previousRuleHits = allRuleHitsData;
rulesTableBody.innerHTML = '';
const sortedRuleEntries = Object.entries(allRuleHitsData)
.sort(([, countA], [, countB]) => countB - countA);
if (sortedRuleEntries.length === 0) {
rulesTableBody.innerHTML = `<tr><td colspan="2" style="text-align: center; padding: 20px; color: var(--label-color);">No rule hits yet.</td></tr>`;
return;
}
sortedRuleEntries.forEach(([ruleName, hitCount]) => {
const row = rulesTableBody.insertRow();
const nameCell = row.insertCell();
const hitsCell = row.insertCell();
nameCell.textContent = ruleName;
hitsCell.textContent = hitCount;
if (previousRuleHits[ruleName] !== undefined && previousRuleHits[ruleName] !== hitCount) {
hitsCell.classList.add('rule-hits-updating');
setTimeout(() => {
hitsCell.classList.remove('rule-hits-updating');
}, 300)
}
});
}
// Function to update GeoIP Stats display
function updateGeoIPStatsDisplay(geoipStats) {
geoipStatsContainer.innerHTML = '';
if (!geoipStats || Object.keys(geoipStats).length === 0) {
geoipStatsContainer.innerHTML = `<p style="color: var(--label-color);">No GeoIP data yet.</p>`;
hasGeoIPData = true; // Set the flag to prevent repeated updates of initial text
return;
}
hasGeoIPData = true; // Set the flag to prevent repeated updates of initial text hasGeoIPData = true; // Set the flag to prevent repeated updates of initial text
return; for (const countryCode in geoipStats) {
} if (geoipStats.hasOwnProperty(countryCode)) {
hasGeoIPData = true; // Set the flag to prevent repeated updates of initial text const hits = geoipStats[countryCode];
for (const countryCode in geoipStats) { const countryName = getCountryName(countryCode);
if (geoipStats.hasOwnProperty(countryCode)) { const p = document.createElement('p');
const hits = geoipStats[countryCode]; p.textContent = `${countryName} (${countryCode.toUpperCase()}): ${hits} hits`;
const countryName = getCountryName(countryCode); geoipStatsContainer.appendChild(p);
const p = document.createElement('p'); }
p.textContent = `${countryName} (${countryCode.toUpperCase()}): ${hits} hits`;
geoipStatsContainer.appendChild(p);
} }
} }
}
// Helper function to update value with animation // Helper function to update value with animation
function updateValue(element, newValue) { function updateValue(element, newValue) {
element.classList.add('updating'); element.classList.add('updating');
setTimeout(() => { setTimeout(() => {
element.textContent = newValue; element.textContent = newValue;
element.classList.remove('updating'); element.classList.remove('updating');
}, 100); }, 100);
}
// Helper function to get country name from code
function getCountryName(countryCode) {
const countryNames = {
"US": "United States",
"CA": "Canada",
"GB": "United Kingdom",
"DE": "Germany",
"FR": "France",
};
return countryNames[countryCode.toUpperCase()] || countryCode.toUpperCase();
}
function setInitialTheme() {
const savedTheme = localStorage.getItem(localStorageThemeKey);
if (savedTheme) {
root.setAttribute('data-theme', savedTheme);
} }
else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) {
root.setAttribute('data-theme', 'light'); // Helper function to get country name from code
} else { function getCountryName(countryCode) {
root.setAttribute('data-theme', 'dark'); // Default to dark const countryNames = {
"US": "United States",
"CA": "Canada",
"GB": "United Kingdom",
"DE": "Germany",
"FR": "France",
};
return countryNames[countryCode.toUpperCase()] || countryCode.toUpperCase();
} }
updateThemeToggleIcon();
}
function updateThemeToggleIcon() { function setInitialTheme() {
const currentTheme = root.getAttribute('data-theme'); const savedTheme = localStorage.getItem(localStorageThemeKey);
themeToggle.innerHTML = currentTheme === 'light' ? '<i class="fas fa-moon"></i>' : '<i class="fas fa-sun"></i>'; if (savedTheme) {
} root.setAttribute('data-theme', savedTheme);
themeToggle.addEventListener('click', () => { }
const currentTheme = root.getAttribute('data-theme'); else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) {
const newTheme = currentTheme === 'light' ? 'dark' : 'light'; root.setAttribute('data-theme', 'light');
root.setAttribute('data-theme', newTheme); } else {
localStorage.setItem(localStorageThemeKey, newTheme); root.setAttribute('data-theme', 'dark'); // Default to dark
updateThemeToggleIcon(); }
updateThemeToggleIcon();
}
function updateThemeToggleIcon() {
const currentTheme = root.getAttribute('data-theme');
themeToggle.innerHTML = currentTheme === 'light' ? '<i class="fas fa-moon"></i>' : '<i class="fas fa-sun"></i>';
}
themeToggle.addEventListener('click', () => {
const currentTheme = root.getAttribute('data-theme');
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
root.setAttribute('data-theme', newTheme);
localStorage.setItem(localStorageThemeKey, newTheme);
updateThemeToggleIcon();
updateChartOptions();
});
// Initial metrics update and set interval
setInitialTheme();
updateMetrics();
updateChartOptions(); updateChartOptions();
setInterval(updateMetrics, 5000);
}); });
// Initial metrics update and set interval
setInitialTheme();
updateMetrics();
updateChartOptions();
setInterval(updateMetrics, 5000);
</script> </script>
</body> </body>