277 lines
11 KiB
JavaScript
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/status'),
|
|
fetch('/api/database/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();
|
|
});
|
|
}
|
|
});
|