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 => `
[${log.timestamp}] ${log.success ? `✅ Успешно (${log.responseTime}ms)` : `❌ Ошибка: ${log.errorMessage}` }
`).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 = ' Проверка...'; 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(); });