feat: frontend & backend connectivity
This commit is contained in:
318
frontend/src/public/js/status.js
Normal file
318
frontend/src/public/js/status.js
Normal file
@@ -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 => `
|
||||
<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();
|
||||
});
|
||||
Reference in New Issue
Block a user