Files
notes_app/frontend/src/public/js/status.js

319 lines
9.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
class BackendMonitor {
constructor() {
this.config = null;
this.isChecking = false;
this.autoCheckInterval = null;
this.autoCheckEnabled = false;
this.checkInterval = 30000; // 30 секунд
this.logs = [];
this.init();
}
async init() {
await this.loadConfig();
await this.checkBackend();
// Начинаем автопроверку если в конфиге указано
if (this.config?.autoCheck === true) {
this.enableAutoCheck();
}
}
async loadConfig() {
try {
// Пробуем загрузить конфиг с сервера
const response = await fetch('/config.json');
if (!response.ok) throw new Error('Failed to load config');
this.config = await response.json();
this.updateUI('configLoaded');
} catch (error) {
console.warn('Не удалось загрузить конфигурацию:', error);
// Используем запасную конфигурацию
this.config = {
backendUrl: 'http://localhost:3000',
healthCheckEndpoint: '/',
timeout: 5000,
environment: 'fallback',
autoCheck: false
};
this.showConfigWarning();
this.updateUI('configFallback');
}
}
async checkBackend() {
if (this.isChecking) return;
this.isChecking = true;
this.updateUI('checking');
const startTime = Date.now();
let isSuccess = false;
let responseTime = null;
let errorMessage = null;
let details = {};
try {
// Проверяем доступность сервера
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout || 10000);
const response = await fetch(this.config.backendUrl + (this.config.healthCheckEndpoint || ''), {
method: 'GET',
headers: {
'Accept': 'application/json',
},
signal: controller.signal
});
clearTimeout(timeoutId);
responseTime = Date.now() - startTime;
if (response.ok) {
isSuccess = true;
try {
const data = await response.json();
details = data;
} catch (e) {
details = { message: 'Сервер доступен' };
}
} else {
errorMessage = `HTTP ${response.status}: ${response.statusText}`;
}
} catch (error) {
responseTime = Date.now() - startTime;
errorMessage = this.getErrorMessage(error);
}
this.isChecking = false;
// Записываем лог
this.addLog(isSuccess, responseTime, errorMessage, details);
// Обновляем UI
this.updateUI(isSuccess ? 'success' : 'error', {
responseTime,
errorMessage,
details
});
}
getErrorMessage(error) {
if (error.name === 'AbortError') {
return 'Таймаут запроса';
}
if (error.message.includes('Failed to fetch')) {
return 'Сеть недоступна';
}
if (error.message.includes('net::ERR_CONNECTION_REFUSED')) {
return 'Соединение отклонено';
}
return error.message || 'Неизвестная ошибка';
}
addLog(isSuccess, responseTime, errorMessage, details) {
const timestamp = new Date().toLocaleTimeString();
const logEntry = {
timestamp,
success: isSuccess,
responseTime,
errorMessage,
details,
type: isSuccess ? 'success' : 'error'
};
this.logs.unshift(logEntry); // Добавляем в начало
// Храним только последние 10 записей
if (this.logs.length > 10) {
this.logs.pop();
}
this.updateLogsUI();
}
updateLogsUI() {
const logContainer = document.getElementById('logEntries');
if (!logContainer) return;
logContainer.innerHTML = this.logs.map(log => `
<div class="log-entry ${log.type}">
<span class="timestamp">[${log.timestamp}]</span>
${log.success
? `✅ Успешно (${log.responseTime}ms)`
: `❌ Ошибка: ${log.errorMessage}`
}
</div>
`).join('');
}
updateUI(state, data = {}) {
const statusCard = document.getElementById('statusCard');
const statusIndicator = document.getElementById('statusIndicator');
const backendUrl = document.getElementById('backendUrl');
const backendStatus = document.getElementById('backendStatus');
const responseTime = document.getElementById('responseTime');
const lastCheck = document.getElementById('lastCheck');
const checkBtn = document.getElementById('checkBtn');
if (!statusCard) return;
switch(state) {
case 'configLoaded':
backendUrl.textContent = this.config.backendUrl;
backendUrl.className = 'detail-value success';
break;
case 'configFallback':
backendUrl.textContent = `${this.config.backendUrl} (запасной)`;
backendUrl.className = 'detail-value error';
break;
case 'checking':
statusCard.className = 'status-card';
statusIndicator.textContent = 'Проверка...';
statusIndicator.className = 'status-indicator offline';
backendStatus.textContent = 'Выполняется проверка...';
backendStatus.className = 'detail-value';
checkBtn.disabled = true;
checkBtn.innerHTML = '<span class="loading"></span> Проверка...';
break;
case 'success':
statusCard.className = 'status-card active';
statusIndicator.textContent = 'Online';
statusIndicator.className = 'status-indicator online';
backendStatus.textContent = '✅ Сервер доступен';
backendStatus.className = 'detail-value success';
responseTime.textContent = `${data.responseTime} ms`;
lastCheck.textContent = new Date().toLocaleTimeString();
checkBtn.disabled = false;
checkBtn.innerHTML = '🔄 Проверить соединение';
break;
case 'error':
statusCard.className = 'status-card error';
statusIndicator.textContent = 'Offline';
statusIndicator.className = 'status-indicator offline';
backendStatus.textContent = `${data.errorMessage}`;
backendStatus.className = 'detail-value error';
responseTime.textContent = data.responseTime ? `${data.responseTime} ms` : '—';
lastCheck.textContent = new Date().toLocaleTimeString();
checkBtn.disabled = false;
checkBtn.innerHTML = '🔄 Проверить соединение';
break;
}
}
showConfigWarning() {
const warning = document.getElementById('configWarning');
if (warning) {
warning.style.display = 'block';
}
}
toggleAutoCheck() {
if (this.autoCheckEnabled) {
this.disableAutoCheck();
} else {
this.enableAutoCheck();
}
}
enableAutoCheck() {
this.autoCheckEnabled = true;
this.autoCheckInterval = setInterval(() => {
this.checkBackend();
}, this.checkInterval);
const btn = document.getElementById('autoCheckBtn');
if (btn) {
btn.innerHTML = '⏸️ Автопроверка: ВКЛ';
}
this.addLog(false, null, null, { type: 'info', message: 'Автопроверка включена' });
}
disableAutoCheck() {
this.autoCheckEnabled = false;
if (this.autoCheckInterval) {
clearInterval(this.autoCheckInterval);
this.autoCheckInterval = null;
}
const btn = document.getElementById('autoCheckBtn');
if (btn) {
btn.innerHTML = '⏱️ Автопроверка: ВЫКЛ';
}
this.addLog(false, null, null, { type: 'info', message: 'Автопроверка выключена' });
}
// Дополнительные проверки
async runExtendedTests() {
const tests = [
{ name: 'Пинг сервера', endpoint: '' },
{ name: 'API Health', endpoint: this.config.healthCheckEndpoint || '/' },
{ name: 'Версия API', endpoint: '/api/version' }
];
const results = [];
for (const test of tests) {
try {
const start = Date.now();
const response = await fetch(this.config.backendUrl + test.endpoint, {
signal: AbortSignal.timeout(5000)
});
const time = Date.now() - start;
results.push({
name: test.name,
success: response.ok,
time,
status: response.status
});
} catch (error) {
results.push({
name: test.name,
success: false,
time: null,
error: error.message
});
}
}
return results;
}
}
// Глобальные функции для вызова из HTML
let monitor;
function checkBackend() {
if (!monitor) {
monitor = new BackendMonitor();
} else {
monitor.checkBackend();
}
}
function toggleAutoCheck() {
if (!monitor) {
monitor = new BackendMonitor();
}
monitor.toggleAutoCheck();
}
// Инициализация при загрузке страницы
document.addEventListener('DOMContentLoaded', () => {
monitor = new BackendMonitor();
});