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
+
- О нас
- Мы разработали это приложение... бесплатно.
+
+
+
+
+ ⚠️ Конфигурация загружена с запасного URL. Проверьте подключение.
+
+
+
+
+
Статус 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'));