diff --git a/backend/src/app.js b/backend/src/app.js index 4a14b19..51d0b37 100644 --- a/backend/src/app.js +++ b/backend/src/app.js @@ -233,10 +233,10 @@ app.delete('/notes/:id', function(request, response) { }); }); -// РОУТ 6: Корневой маршрут (для проверки работы сервера) +// РОУТ 7: Корневой маршрут (для проверки работы сервера) // Метод: GET // Адрес: / -app.get('/', function(request, response) { +app.get('/', function(_, response) { /* Простой маршрут для проверки, что сервер работает */ diff --git a/frontend/src/public/config.json b/frontend/src/public/config.json new file mode 100644 index 0000000..a8f3a2e --- /dev/null +++ b/frontend/src/public/config.json @@ -0,0 +1,5 @@ +{ + "backendUrl": "http://localhost:3000", + "apiPrefix": "", + "version": "1.0.0" +} diff --git a/frontend/src/public/html/status.html b/frontend/src/public/html/status.html index ac8e730..05dab41 100644 --- a/frontend/src/public/html/status.html +++ b/frontend/src/public/html/status.html @@ -1,12 +1,320 @@ - + - Document + Проверка связи с Backend + -

О нас

-

Мы разработали это приложение... бесплатно.

+
+
+

🔍 Проверка связи

+

Мониторинг подключения к Backend серверу

+
+ + + +
+
+
Статус Backend сервера
+
Проверка...
+
+ +
+
+ Backend URL: + Загрузка... +
+
+ Статус: + Проверяется... +
+
+ Время ответа: + +
+
+ Последняя проверка: + +
+
+
+ +
+ + +
+ +
+
Журнал проверок:
+
+
+
+ + diff --git a/frontend/src/public/js/status.js b/frontend/src/public/js/status.js new file mode 100644 index 0000000..06effd5 --- /dev/null +++ b/frontend/src/public/js/status.js @@ -0,0 +1,318 @@ +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(); +}); diff --git a/frontend/src/server.js b/frontend/src/server.js index f3196ff..543641d 100644 --- a/frontend/src/server.js +++ b/frontend/src/server.js @@ -5,7 +5,7 @@ import fs from 'fs'; const app = express(); const PORT = process.env.FRONTEND_PORT || 8080; - +const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:3000'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -14,6 +14,14 @@ const __dirname = dirname(__filename); // файлу в public, то буден выдан данный файл app.use(express.static(join(__dirname, 'public'))); +// Конфигурационный endpoint +app.get('/config.json', (_, res) => { + res.json({ + backendUrl: BACKEND_URL, + environment: process.env.NODE_ENV || 'development', + }); +}); + // Запросы на / выдают index.html по умолчанию app.get('/', function(_, res) { res.sendFile(join(__dirname, 'public', 'html', 'index.html'));