mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-05-04 14:55:22 +00:00
a2a121b4e7
- Added 'X-Requested-With' header to various API requests in channel_operations.js, cache.html, config.html, contacts.html, feeds.html, greeter.html, mesh.html, radio.html, and other templates to improve request handling and prevent potential issues with cross-origin requests. - Ensured consistent header usage across all relevant fetch calls to enhance security and compatibility. These changes improve the robustness of API interactions within the web viewer.
314 lines
11 KiB
HTML
314 lines
11 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Database Information - MeshCore Bot Data Viewer{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<h1 class="mb-4">
|
|
<i class="fas fa-database"></i> Database Information
|
|
</h1>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Database Overview -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-3">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<i class="fas fa-table"></i> Total Tables
|
|
</div>
|
|
<div class="card-body">
|
|
<h3 id="total-tables">0</h3>
|
|
<small class="text-muted">Database tables</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-3">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<i class="fas fa-list"></i> Total Records
|
|
</div>
|
|
<div class="card-body">
|
|
<h3 id="total-records">0</h3>
|
|
<small class="text-muted">All records</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-3">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<i class="fas fa-clock"></i> Last Updated
|
|
</div>
|
|
<div class="card-body">
|
|
<h6 id="last-updated">-</h6>
|
|
<small class="text-muted">Database activity</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-3">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<i class="fas fa-hdd"></i> Database Size
|
|
</div>
|
|
<div class="card-body">
|
|
<h6 id="db-size">-</h6>
|
|
<small class="text-muted">Storage used</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Database Controls -->
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<i class="fas fa-cog"></i> Database Operations
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="btn-group" role="group">
|
|
<button class="btn btn-primary" id="refresh-db">
|
|
<i class="fas fa-sync"></i> Refresh
|
|
</button>
|
|
<button class="btn btn-info" id="export-db">
|
|
<i class="fas fa-download"></i> Export
|
|
</button>
|
|
<button class="btn btn-warning" id="optimize-db">
|
|
<i class="fas fa-tools"></i> Optimize
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Database Tables -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<i class="fas fa-list"></i> Database Tables
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="tables-info">
|
|
<div class="loading">Loading database information...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
class DatabaseInfoManager {
|
|
constructor() {
|
|
this.dbData = {};
|
|
this.initializeDatabase();
|
|
}
|
|
|
|
async initializeDatabase() {
|
|
await this.loadDatabaseData();
|
|
this.setupEventHandlers();
|
|
this.updateStatistics();
|
|
this.renderTablesInfo();
|
|
}
|
|
|
|
async loadDatabaseData() {
|
|
try {
|
|
const response = await fetch('/api/database');
|
|
const data = await response.json();
|
|
|
|
if (data.error) {
|
|
this.showError('Failed to load database data: ' + data.error);
|
|
return;
|
|
}
|
|
|
|
this.dbData = data;
|
|
|
|
} catch (error) {
|
|
console.error('Error loading database data:', error);
|
|
this.showError('Failed to load database data: ' + error.message);
|
|
}
|
|
}
|
|
|
|
setupEventHandlers() {
|
|
document.getElementById('refresh-db').addEventListener('click', () => {
|
|
this.loadDatabaseData().then(() => {
|
|
this.updateStatistics();
|
|
this.renderTablesInfo();
|
|
});
|
|
});
|
|
|
|
document.getElementById('export-db').addEventListener('click', () => {
|
|
this.exportDatabase();
|
|
});
|
|
|
|
document.getElementById('optimize-db').addEventListener('click', () => {
|
|
this.optimizeDatabase();
|
|
});
|
|
}
|
|
|
|
updateStatistics() {
|
|
document.getElementById('total-tables').textContent = this.dbData.total_tables || 0;
|
|
document.getElementById('total-records').textContent = this.dbData.total_records || 0;
|
|
document.getElementById('last-updated').textContent = this.dbData.last_updated || '-';
|
|
document.getElementById('db-size').textContent = this.dbData.db_size || '-';
|
|
}
|
|
|
|
renderTablesInfo() {
|
|
const infoElement = document.getElementById('tables-info');
|
|
|
|
if (!this.dbData.tables || this.dbData.tables.length === 0) {
|
|
infoElement.innerHTML = '<div class="text-muted">No tables found</div>';
|
|
return;
|
|
}
|
|
|
|
const tablesHtml = this.dbData.tables.map(table => `
|
|
<div class="row mb-3 p-3 border rounded">
|
|
<div class="col-md-4">
|
|
<h6 class="mb-1">
|
|
<i class="fas fa-table text-primary"></i> ${table.name}
|
|
</h6>
|
|
<small class="text-muted">${table.description || 'Database table'}</small>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="text-center">
|
|
<h5 class="mb-0 text-primary">${table.record_count || 0}</h5>
|
|
<small class="text-muted">Records</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="text-center">
|
|
<h6 class="mb-0">${table.size || 'Unknown'}</h6>
|
|
<small class="text-muted">Size</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="text-center">
|
|
<span class="badge bg-${this.getTableStatusClass(table)}">${this.getTableStatus(table)}</span>
|
|
<br>
|
|
<small class="text-muted">Status</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
infoElement.innerHTML = tablesHtml;
|
|
}
|
|
|
|
getTableStatus(table) {
|
|
const count = table.record_count || 0;
|
|
if (count === 0) return 'Empty';
|
|
if (count < 100) return 'Small';
|
|
if (count < 1000) return 'Medium';
|
|
if (count < 10000) return 'Large';
|
|
return 'Very Large';
|
|
}
|
|
|
|
getTableStatusClass(table) {
|
|
const count = table.record_count || 0;
|
|
if (count === 0) return 'secondary';
|
|
if (count < 100) return 'success';
|
|
if (count < 1000) return 'info';
|
|
if (count < 10000) return 'warning';
|
|
return 'danger';
|
|
}
|
|
|
|
exportDatabase() {
|
|
const csvContent = this.generateDatabaseCSV();
|
|
const blob = new Blob([csvContent], { type: 'text/csv' });
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = `database_export_${new Date().toISOString().split('T')[0]}.csv`;
|
|
a.click();
|
|
window.URL.revokeObjectURL(url);
|
|
}
|
|
|
|
generateDatabaseCSV() {
|
|
const headers = ['Table Name', 'Record Count', 'Size', 'Status', 'Timestamp'];
|
|
const rows = (this.dbData.tables || []).map(table => [
|
|
table.name,
|
|
table.record_count || 0,
|
|
table.size || 'Unknown',
|
|
this.getTableStatus(table),
|
|
new Date().toISOString()
|
|
]);
|
|
|
|
return [headers, ...rows].map(row =>
|
|
row.map(field => `"${field}"`).join(',')
|
|
).join('\n');
|
|
}
|
|
|
|
async optimizeDatabase() {
|
|
if (confirm('Are you sure you want to optimize the database? This may take a few moments.')) {
|
|
try {
|
|
// Show loading state
|
|
const optimizeBtn = document.getElementById('optimize-db');
|
|
const originalText = optimizeBtn.innerHTML;
|
|
optimizeBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Optimizing...';
|
|
optimizeBtn.disabled = true;
|
|
|
|
// Call backend optimization
|
|
const response = await fetch('/api/optimize-database', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
}
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
alert(`Database optimization completed successfully!\n\nResults:\n- ${result.vacuum_result}\n- ${result.analyze_result}\n- ${result.reindex_result}`);
|
|
// Refresh the database information
|
|
await this.loadDatabaseData();
|
|
this.updateStatistics();
|
|
this.renderTablesInfo();
|
|
} else {
|
|
alert('Database optimization failed: ' + (result.error || 'Unknown error'));
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Error optimizing database:', error);
|
|
alert('Database optimization failed: ' + error.message);
|
|
} finally {
|
|
// Restore button state
|
|
const optimizeBtn = document.getElementById('optimize-db');
|
|
optimizeBtn.innerHTML = '<i class="fas fa-tools"></i> Optimize';
|
|
optimizeBtn.disabled = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
showError(message) {
|
|
const errorDiv = document.createElement('div');
|
|
errorDiv.className = 'alert alert-danger';
|
|
errorDiv.textContent = message;
|
|
|
|
const content = document.querySelector('.container-fluid');
|
|
if (content) {
|
|
content.insertBefore(errorDiv, content.firstChild);
|
|
|
|
setTimeout(() => {
|
|
if (errorDiv.parentNode) {
|
|
errorDiv.parentNode.removeChild(errorDiv);
|
|
}
|
|
}, 5000);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize database manager when page loads
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
window.databaseManager = new DatabaseInfoManager();
|
|
});
|
|
</script>
|
|
{% endblock %}
|