|
|
|
|
|
|
|
|
|
|
|
|
|
|
import { apiClient } from '../../shared/js/core/api-client.js';
|
|
|
|
|
|
class DiagnosticsPage {
|
|
|
constructor() {
|
|
|
this.isRunning = false;
|
|
|
this.requestLog = [];
|
|
|
}
|
|
|
|
|
|
async init() {
|
|
|
console.log('[Diagnostics] Initializing...');
|
|
|
|
|
|
this.bindEvents();
|
|
|
await this.loadHealthData();
|
|
|
await this.loadLogs();
|
|
|
this.startRequestTracking();
|
|
|
}
|
|
|
|
|
|
bindEvents() {
|
|
|
document.getElementById('health-refresh')?.addEventListener('click', () => {
|
|
|
this.loadHealthData();
|
|
|
});
|
|
|
|
|
|
document.getElementById('logs-refresh')?.addEventListener('click', () => {
|
|
|
this.loadLogs();
|
|
|
});
|
|
|
|
|
|
document.getElementById('logs-clear')?.addEventListener('click', () => {
|
|
|
this.clearLogs();
|
|
|
});
|
|
|
|
|
|
document.getElementById('refresh-btn')?.addEventListener('click', () => {
|
|
|
this.refreshAll();
|
|
|
});
|
|
|
|
|
|
document.getElementById('log-type')?.addEventListener('change', () => {
|
|
|
this.loadLogs();
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
async loadHealthData() {
|
|
|
const container = document.getElementById('health-grid');
|
|
|
if (!container) return;
|
|
|
|
|
|
container.innerHTML = '<div class="loading-container"><div class="spinner"></div></div>';
|
|
|
|
|
|
try {
|
|
|
const response = await apiClient.fetch('/api/health');
|
|
|
const data = await response.json();
|
|
|
|
|
|
const services = [
|
|
|
{ name: 'Backend Server', status: data.status === 'healthy' ? 'online' : 'offline', key: 'backend' },
|
|
|
{ name: 'CoinMarketCap', status: data.sources?.coinmarketcap || 'unknown', key: 'coinmarketcap' },
|
|
|
{ name: 'NewsAPI', status: data.sources?.newsapi || 'unknown', key: 'newsapi' },
|
|
|
{ name: 'Etherscan', status: data.sources?.etherscan || 'unknown', key: 'etherscan' },
|
|
|
{ name: 'BSCScan', status: data.sources?.bscscan || 'unknown', key: 'bscscan' },
|
|
|
{ name: 'TronScan', status: data.sources?.tronscan || 'unknown', key: 'tronscan' }
|
|
|
];
|
|
|
|
|
|
container.innerHTML = services.map(service => `
|
|
|
<div class="health-card ${service.status}">
|
|
|
<div class="health-icon ${service.status}">
|
|
|
${this.getStatusIcon(service.status)}
|
|
|
</div>
|
|
|
<div class="health-info">
|
|
|
<h4>${service.name}</h4>
|
|
|
<span class="status-badge ${service.status}">${service.status}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
`).join('');
|
|
|
|
|
|
this.updateLastUpdate();
|
|
|
} catch (error) {
|
|
|
console.error('Failed to load health data:', error);
|
|
|
container.innerHTML = `
|
|
|
<div class="error-message">
|
|
|
<p>Failed to load health data: ${error.message}</p>
|
|
|
</div>
|
|
|
`;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
async loadLogs() {
|
|
|
const container = document.getElementById('logs-container');
|
|
|
if (!container) return;
|
|
|
|
|
|
const logType = document.getElementById('log-type')?.value || 'recent';
|
|
|
const endpoint = logType === 'errors' ? '/api/logs/errors' : '/api/logs/recent';
|
|
|
|
|
|
container.innerHTML = '<div class="loading-container"><div class="spinner"></div></div>';
|
|
|
|
|
|
try {
|
|
|
const response = await apiClient.fetch(endpoint);
|
|
|
const data = await response.json();
|
|
|
const logs = data.logs || data.errors || [];
|
|
|
|
|
|
if (logs.length === 0) {
|
|
|
container.innerHTML = '<p class="text-center text-muted">No logs found</p>';
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
container.innerHTML = `
|
|
|
<div class="logs-list">
|
|
|
${logs.map(log => `
|
|
|
<div class="log-entry ${log.level?.toLowerCase() || 'info'}">
|
|
|
<span class="log-time">${log.timestamp ? new Date(log.timestamp).toLocaleTimeString() : 'N/A'}</span>
|
|
|
<span class="log-level ${log.level?.toLowerCase() || 'info'}">${log.level || 'INFO'}</span>
|
|
|
<span class="log-message">${log.message || log.msg || log.text || ''}</span>
|
|
|
</div>
|
|
|
`).join('')}
|
|
|
</div>
|
|
|
`;
|
|
|
} catch (error) {
|
|
|
console.error('Failed to load logs:', error);
|
|
|
container.innerHTML = `
|
|
|
<div class="error-message">
|
|
|
<p>Failed to load logs: ${error.message}</p>
|
|
|
</div>
|
|
|
`;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
async clearLogs() {
|
|
|
const container = document.getElementById('logs-container');
|
|
|
if (!container) return;
|
|
|
|
|
|
container.innerHTML = '<p class="text-center text-muted">Logs cleared</p>';
|
|
|
}
|
|
|
|
|
|
|
|
|
startRequestTracking() {
|
|
|
|
|
|
const originalFetch = apiClient.fetch.bind(apiClient);
|
|
|
apiClient.fetch = async (...args) => {
|
|
|
const startTime = Date.now();
|
|
|
const url = args[0];
|
|
|
|
|
|
try {
|
|
|
const response = await originalFetch(...args);
|
|
|
const duration = Date.now() - startTime;
|
|
|
|
|
|
this.logRequest({
|
|
|
time: new Date(),
|
|
|
method: 'GET',
|
|
|
endpoint: url,
|
|
|
status: response.status,
|
|
|
duration
|
|
|
});
|
|
|
|
|
|
return response;
|
|
|
} catch (error) {
|
|
|
const duration = Date.now() - startTime;
|
|
|
|
|
|
this.logRequest({
|
|
|
time: new Date(),
|
|
|
method: 'GET',
|
|
|
endpoint: url,
|
|
|
status: 'ERROR',
|
|
|
duration
|
|
|
});
|
|
|
|
|
|
throw error;
|
|
|
}
|
|
|
};
|
|
|
}
|
|
|
|
|
|
|
|
|
logRequest(request) {
|
|
|
this.requestLog.unshift(request);
|
|
|
if (this.requestLog.length > 50) {
|
|
|
this.requestLog = this.requestLog.slice(0, 50);
|
|
|
}
|
|
|
this.updateRequestsTable();
|
|
|
}
|
|
|
|
|
|
|
|
|
updateRequestsTable() {
|
|
|
const tbody = document.getElementById('requests-tbody');
|
|
|
if (!tbody) return;
|
|
|
|
|
|
if (this.requestLog.length === 0) {
|
|
|
tbody.innerHTML = '<tr><td colspan="5" class="text-center">No requests logged yet</td></tr>';
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
tbody.innerHTML = this.requestLog.map(req => `
|
|
|
<tr>
|
|
|
<td>${req.time.toLocaleTimeString()}</td>
|
|
|
<td><span class="method-badge">${req.method}</span></td>
|
|
|
<td>${req.endpoint}</td>
|
|
|
<td><span class="status-badge status-${Math.floor(req.status / 100)}xx">${req.status}</span></td>
|
|
|
<td>${req.duration}ms</td>
|
|
|
</tr>
|
|
|
`).join('');
|
|
|
}
|
|
|
|
|
|
|
|
|
async refreshAll() {
|
|
|
await Promise.all([
|
|
|
this.loadHealthData(),
|
|
|
this.loadLogs()
|
|
|
]);
|
|
|
}
|
|
|
|
|
|
|
|
|
updateLastUpdate() {
|
|
|
const elem = document.getElementById('last-update');
|
|
|
if (elem) {
|
|
|
elem.textContent = new Date().toLocaleTimeString();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
getStatusIcon(status) {
|
|
|
const normalized = status?.toLowerCase();
|
|
|
if (normalized === 'online' || normalized === 'healthy' || normalized === 'operational') {
|
|
|
return '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"></polyline></svg>';
|
|
|
} else if (normalized === 'degraded' || normalized === 'warning') {
|
|
|
return '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>';
|
|
|
} else {
|
|
|
return '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
const diagnosticsPage = new DiagnosticsPage();
|
|
|
diagnosticsPage.init();
|
|
|
|