fix: исправлены критические ошибки JavaScript

- Исправлена ошибка 'obj is not defined' в WebSocket onopen
- Упрощена логика обработки WebSocket сообщений (убрано дублирование)
- Исправлен вызов addSectionToggleHandlers в buildTabs
- WebSocket теперь работает в реальном времени с follow=True
- Убрана избыточная логика проверки дублирования строк

Изменения в WebSocket:
- Добавлен потоковый режим с follow=True для локальных контейнеров
- Улучшена обработка ошибок
- Убрано автоматическое закрытие соединения

Изменения в JavaScript:
- Исправлена ошибка с неопределенной переменной obj
- Упрощена обработка WebSocket сообщений
- Улучшена стабильность работы

Автор: Сергей Антропов
Сайт: https://devops.org.ru
This commit is contained in:
Сергей Антропов 2025-08-20 20:58:53 +03:00
parent c5c0a6cfe3
commit 18466d2cb0
2 changed files with 41 additions and 54 deletions

View File

@ -96,6 +96,11 @@ async def ws_logs(ws: WebSocket, container_id: str, tail: int = DEFAULT_TAIL, to
await ws.send_text('\n'.join(logs)) await ws.send_text('\n'.join(logs))
else: else:
await ws.send_text("No logs available for remote container") await ws.send_text("No logs available for remote container")
# Для удаленных контейнеров пока просто отправляем начальные логи
# TODO: Реализовать мониторинг файлов логов в реальном времени
websocket_logger.info(f"Remote WebSocket connection established for {container_name} on {hostname}")
except Exception as e: except Exception as e:
await ws.send_text(f"ERROR: cannot get remote logs - {e}") await ws.send_text(f"ERROR: cannot get remote logs - {e}")
return return
@ -119,21 +124,39 @@ async def ws_logs(ws: WebSocket, container_id: str, tail: int = DEFAULT_TAIL, to
# Отправляем начальное сообщение # Отправляем начальное сообщение
await ws.send_text(f"Connected to container: {container.name}") await ws.send_text(f"Connected to container: {container.name}")
# Получаем логи (только последние строки, без follow) # Отправляем начальные логи
try: try:
websocket_logger.info(f"Getting logs for container {container.name} (ID: {container.id[:12]})") websocket_logger.info(f"Getting logs for container {container.name} (ID: {container.id[:12]})")
logs = container.logs(tail=tail).decode(errors="ignore") initial_logs = container.logs(tail=tail).decode(errors="ignore")
if logs: if initial_logs:
await ws.send_text(logs) await ws.send_text(initial_logs)
else: else:
await ws.send_text("No logs available") await ws.send_text("No logs available")
except Exception as e: except Exception as e:
websocket_logger.error(f"Error getting logs for {container.name}: {e}") websocket_logger.error(f"Error getting initial logs for {container.name}: {e}")
await ws.send_text(f"ERROR getting logs: {e}") await ws.send_text(f"ERROR getting initial logs: {e}")
# Простое WebSocket соединение - только отправляем логи один раз # Устанавливаем потоковое соединение для получения новых логов
websocket_logger.info(f"WebSocket connection established for {container.name}") websocket_logger.info(f"WebSocket connection established for {container.name}")
try:
# Получаем логи в реальном времени с follow=True
stream = container.logs(stream=True, follow=True, tail=0)
for chunk in stream:
if chunk is None:
break
try:
await ws.send_text(chunk.decode(errors="ignore"))
except WebSocketDisconnect:
stream.close()
return
except Exception as e:
websocket_logger.error(f"Error streaming logs for {container.name}: {e}")
break
stream.close()
except Exception as e:
websocket_logger.error(f"Error setting up log stream for {container.name}: {e}")
except WebSocketDisconnect: except WebSocketDisconnect:
websocket_logger.info(f"WebSocket client disconnected") websocket_logger.info(f"WebSocket client disconnected")
except Exception as e: except Exception as e:
@ -142,12 +165,6 @@ async def ws_logs(ws: WebSocket, container_id: str, tail: int = DEFAULT_TAIL, to
await ws.send_text(f"ERROR: {e}") await ws.send_text(f"ERROR: {e}")
except: except:
pass pass
finally:
try:
websocket_logger.info(f"Closing WebSocket connection")
await ws.close()
except:
pass
@router.websocket("/fan/{service_name}") @router.websocket("/fan/{service_name}")
async def ws_fan(ws: WebSocket, service_name: str, tail: int = DEFAULT_TAIL, token: Optional[str] = None, async def ws_fan(ws: WebSocket, service_name: str, tail: int = DEFAULT_TAIL, token: Optional[str] = None,

View File

@ -1253,7 +1253,7 @@ ${svc.last_modified ? `Обновлено: ${new Date(svc.last_modified * 1000).
} }
// Добавляем обработчики для сворачивания секций после построения интерфейса // Добавляем обработчики для сворачивания секций после построения интерфейса
addSectionToggleHandlers(); // Вызов перемещен в конец инициализации
} }
function setLayout(cls){ function setLayout(cls){
@ -2708,7 +2708,8 @@ function openWs(svc, panel){
els.logContent.innerHTML = ''; els.logContent.innerHTML = '';
} }
// Также очищаем legacy элемент лога // Также очищаем legacy элемент лога
if (obj.logEl) { const obj = state.open[id];
if (obj && obj.logEl) {
obj.logEl.innerHTML = ''; obj.logEl.innerHTML = '';
} }
@ -2752,47 +2753,16 @@ function openWs(svc, panel){
console.log('🚨 Single View WebSocket: Полные данные:', ev.data); console.log('🚨 Single View WebSocket: Полные данные:', ev.data);
} }
// Проверяем на дублирование строк и убираем дубликаты // Обрабатываем строки логов
const lines = ev.data.split(/\r?\n/).filter(line => line.trim().length > 0);
const uniqueLines = [...new Set(lines)];
if (lines.length !== uniqueLines.length) {
console.log('🚨 Single View WebSocket: ОБНАРУЖЕНО ДУБЛИРОВАНИЕ строк!');
console.log('🚨 Single View WebSocket: Всего строк:', lines.length);
console.log('🚨 Single View WebSocket: Уникальных строк:', uniqueLines.length);
console.log('🚨 Single View WebSocket: Дублированные строки:', lines.filter((line, index) => lines.indexOf(line) !== index));
// Используем только уникальные строки
const uniqueParts = uniqueLines.map(line => line.trim()).filter(line => line.length > 0);
for (let i=0;i<uniqueParts.length;i++){
// Проверяем каждую часть на FoundINFO:
if (uniqueParts[i].includes('FoundINFO:')) {
console.log('🚨 Single View WebSocket: Часть с FoundINFO:', uniqueParts[i]);
}
// harvest instance ids if present
const pr = parsePrefixAndStrip(uniqueParts[i]);
if (pr){ if (!(pr.id8 in inst.filters)) { inst.filters[pr.id8] = true; updateIdFiltersBar(); } }
console.log(`openWs: Calling handleLine for container ${id}, line: "${uniqueParts[i].substring(0, 50)}..."`);
handleLine(id, uniqueParts[i]);
}
} else {
// Если дублирования нет, обрабатываем как обычно
for (let i=0;i<parts.length;i++){ for (let i=0;i<parts.length;i++){
if (parts[i].length===0 && i===parts.length-1) continue; if (parts[i].length===0 && i===parts.length-1) continue;
// Проверяем каждую часть на FoundINFO:
if (parts[i].includes('FoundINFO:')) {
console.log('🚨 Single View WebSocket: Часть с FoundINFO:', parts[i]);
}
// harvest instance ids if present // harvest instance ids if present
const pr = parsePrefixAndStrip(parts[i]); const pr = parsePrefixAndStrip(parts[i]);
if (pr){ if (!(pr.id8 in inst.filters)) { inst.filters[pr.id8] = true; updateIdFiltersBar(); } } if (pr){ if (!(pr.id8 in inst.filters)) { inst.filters[pr.id8] = true; updateIdFiltersBar(); } }
console.log(`openWs: Calling handleLine for container ${id}, line: "${parts[i].substring(0, 50)}..."`); console.log(`openWs: Calling handleLine for container ${id}, line: "${parts[i].substring(0, 50)}..."`);
handleLine(id, parts[i]); handleLine(id, parts[i]);
} }
}
// Обновляем счетчики после обработки всех строк // Обновляем счетчики после обработки всех строк
cdbg.textContent = counters.dbg; cdbg.textContent = counters.dbg;