first commit
This commit is contained in:
276
static/js/cache.js
Normal file
276
static/js/cache.js
Normal file
@@ -0,0 +1,276 @@
|
||||
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();
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user