Files
geoip_block_generator/static/js/cache.js
Mateusz Gruszczyński 1d8071966b api update
2026-02-25 10:02:37 +01:00

277 lines
11 KiB
JavaScript

async function loadCacheStats() {
const container = document.getElementById('cacheStatsContent');
if (!container) return;
container.innerHTML = `
<div class="text-center py-3">
<div class="spinner-border spinner-border-sm text-primary" role="status"></div>
<span class="ms-2">Loading statistics...</span>
</div>
`;
try {
const [cacheResponse, sqliteResponse] = await Promise.all([
fetch('/api/cache/redis/status'),
fetch('/api/cache/sqlite/status')
]);
const cacheData = await cacheResponse.json();
const sqliteData = await sqliteResponse.json();
if (!cacheData.success) {
container.innerHTML = `<div class="alert alert-warning mb-0">Redis cache unavailable: ${cacheData.error || 'Unknown error'}</div>`;
return;
}
const stats = cacheData.stats || {};
const health = cacheData.health || {};
let html = `
<h6 class="mb-3"><i class="fas fa-bolt text-warning me-2"></i>Redis Cache</h6>
<div class="row g-3 mb-4">
<div class="col-md-3">
<div class="text-center p-3 bg-light rounded">
<h4 class="mb-1">${stats.country_keys || 0}</h4>
<small class="text-muted">Country Keys</small>
</div>
</div>
<div class="col-md-3">
<div class="text-center p-3 bg-light rounded">
<h4 class="mb-1">${stats.config_keys || 0}</h4>
<small class="text-muted">Config Keys</small>
</div>
</div>
<div class="col-md-3">
<div class="text-center p-3 bg-light rounded">
<h4 class="mb-1">${stats.total_size_mb || 0} MB</h4>
<small class="text-muted">Cache Size</small>
</div>
</div>
<div class="col-md-3">
<div class="text-center p-3 bg-light rounded">
<h4 class="mb-1">${health.memory_used_mb || 0} MB</h4>
<small class="text-muted">Memory Used</small>
</div>
</div>
</div>
`;
if (sqliteData.success && sqliteData.exists) {
const modifiedDate = new Date(sqliteData.modified).toLocaleString();
html += `
<h6 class="mb-3"><i class="fas fa-database text-primary me-2"></i>SQLite Cache Database</h6>
<div class="row g-3 mb-3">
<div class="col-md-3">
<div class="text-center p-3 bg-light rounded">
<h4 class="mb-1">${sqliteData.total_countries || 0}</h4>
<small class="text-muted">Countries</small>
</div>
</div>
<div class="col-md-3">
<div class="text-center p-3 bg-light rounded">
<h4 class="mb-1">${(sqliteData.total_networks || 0).toLocaleString()}</h4>
<small class="text-muted">Total Networks</small>
</div>
</div>
<div class="col-md-3">
<div class="text-center p-3 bg-light rounded">
<h4 class="mb-1">${sqliteData.file_size_mb || 0} MB</h4>
<small class="text-muted">Database Size</small>
</div>
</div>
<div class="col-md-3">
<div class="text-center p-3 bg-light rounded">
<small class="text-muted d-block mb-1">Last Modified</small>
<small><strong>${modifiedDate}</strong></small>
</div>
</div>
</div>
`;
if (sqliteData.top_countries && sqliteData.top_countries.length > 0) {
html += `
<div class="alert alert-info mb-0">
<strong><i class="fas fa-star me-1"></i>Top countries:</strong>
${sqliteData.top_countries.map(c =>
`<span class="badge bg-secondary ms-2">${c.code}: ${c.networks.toLocaleString()}</span>`
).join('')}
</div>
`;
}
} else {
html += `
<div class="alert alert-warning mb-0">
<i class="fas fa-exclamation-triangle me-2"></i>
SQLite cache database not available
</div>
`;
}
container.innerHTML = html;
} catch (error) {
console.error('Error loading cache stats:', error);
container.innerHTML = `
<div class="alert alert-danger mb-0">
<i class="fas fa-times-circle me-2"></i>
Failed to load statistics: ${error.message}
</div>
`;
}
}
async function flushCache() {
const confirmed = await showConfirmModal(
'Flush Redis Cache',
'Are you sure you want to flush ALL Redis cache?<br><br>' +
'<strong>This will delete:</strong><br>' +
'• All cached country data<br>' +
'• All cached configurations<br>' +
'• Force regeneration for future requests<br><br>' +
'<span class="text-danger">This action cannot be undone!</span>'
);
if (!confirmed) return;
try {
showFlushingIndicator();
const response = await fetch('/api/cache/flush', {
method: 'POST',
headers: {'Content-Type': 'application/json'}
});
const data = await response.json();
hideFlushingIndicator();
if (data.success) {
showToast('success', 'Cache Flushed', 'All Redis cache has been cleared successfully!');
loadCacheStats();
} else {
showToast('danger', 'Error', 'Failed to flush cache: ' + (data.error || 'Unknown error'));
}
} catch (error) {
hideFlushingIndicator();
showToast('danger', 'Error', 'Network error: ' + error.message);
}
}
function showConfirmModal(title, message) {
return new Promise((resolve) => {
const modalId = 'confirmModal_' + Date.now();
const modalHtml = `
<div class="modal fade" id="${modalId}" tabindex="-1" data-bs-backdrop="static">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-warning text-dark">
<h5 class="modal-title">
<i class="fas fa-exclamation-triangle me-2"></i>${title}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
${message}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="fas fa-times me-1"></i>Cancel
</button>
<button type="button" class="btn btn-danger" id="confirmBtn">
<i class="fas fa-trash me-1"></i>Flush Cache
</button>
</div>
</div>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', modalHtml);
const modalEl = document.getElementById(modalId);
const modal = new bootstrap.Modal(modalEl);
modalEl.querySelector('#confirmBtn').addEventListener('click', () => {
modal.hide();
resolve(true);
});
modalEl.addEventListener('hidden.bs.modal', () => {
modalEl.remove();
resolve(false);
});
modal.show();
});
}
function showFlushingIndicator() {
const indicator = document.createElement('div');
indicator.id = 'flushingIndicator';
indicator.className = 'position-fixed top-50 start-50 translate-middle';
indicator.style.zIndex = '9999';
indicator.innerHTML = `
<div class="card shadow-lg">
<div class="card-body text-center p-4">
<div class="spinner-border text-warning mb-3" role="status" style="width: 3rem; height: 3rem;">
<span class="visually-hidden">Flushing...</span>
</div>
<h5>Flushing Cache...</h5>
<p class="text-muted mb-0">Please wait</p>
</div>
</div>
`;
document.body.appendChild(indicator);
}
function hideFlushingIndicator() {
const indicator = document.getElementById('flushingIndicator');
if (indicator) {
indicator.remove();
}
}
function showToast(type, title, message) {
const toastId = 'toast_' + Date.now();
const bgClass = type === 'success' ? 'bg-success' : type === 'danger' ? 'bg-danger' : 'bg-warning';
const toastHtml = `
<div class="position-fixed top-0 end-0 p-3" style="z-index: 9999">
<div id="${toastId}" class="toast ${bgClass} text-white" role="alert">
<div class="toast-header ${bgClass} text-white">
<i class="fas fa-${type === 'success' ? 'check-circle' : 'exclamation-circle'} me-2"></i>
<strong class="me-auto">${title}</strong>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast"></button>
</div>
<div class="toast-body">
${message}
</div>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', toastHtml);
const toastEl = document.getElementById(toastId);
const toast = new bootstrap.Toast(toastEl, { delay: 5000 });
toast.show();
toastEl.addEventListener('hidden.bs.toast', () => {
toastEl.parentElement.remove();
});
}
function showAlert(type, message) {
showToast(type, type === 'success' ? 'Success' : 'Error', message);
}
document.addEventListener('DOMContentLoaded', function() {
const statsPanel = document.getElementById('cacheStatsPanel');
if (statsPanel) {
statsPanel.addEventListener('shown.bs.collapse', function() {
loadCacheStats();
});
}
});