/**
 * LogBoard+ - Веб-панель для просмотра логов микросервисов
 * Автор: Сергей Антропов
 * Сайт: https://devops.org.ru
 * Версия: 2.0
 */

console.log('LogBoard+ script loaded - VERSION 2');

/**
 * Глобальное состояние приложения
 * Содержит все данные о контейнерах, настройках и режимах отображения
 */
const state = {
  services: [],                    // Список всех доступных сервисов
  current: null,                   // Текущий выбранный контейнер для single view
  open: {},                        // Открытые WebSocket соединения: id -> {ws, logEl, wrapEl, counters, pausedBuffer:[], serviceName}
  layout: 'tabs',                  // Режим отображения: 'tabs' | 'grid2' | 'grid3' | 'grid4'
  filter: null,                    // Текущий фильтр для логов
  levels: {debug:true, info:true, warn:true, err:true, other:true}, // Уровни логирования для отображения
  selectedContainers: [],          // Массив ID выбранных контейнеров для мультипросмотра
  multiViewMode: false,            // Режим мультипросмотра (true = multi-view, false = single-view)
};

/**
 * Ссылки на DOM элементы интерфейса
 * Содержит все элементы управления и отображения
 */
const els = {
  // Legacy elements (старые элементы для обратной совместимости)
  tabs: document.getElementById('tabs'),                    // Контейнер с вкладками
  grid: document.getElementById('grid'),                    // Контейнер с сеткой
  tail: document.getElementById('tail'),                    // Поле ввода количества строк логов
  autoscroll: document.getElementById('autoscroll'),        // Чекбокс автопрокрутки
  wrapToggle: document.getElementById('wrap'),              // Переключатель переноса строк
  
  filter: document.getElementById('filter'),                // Поле фильтра логов
  wsstate: document.getElementById('wsstate'),              // Индикатор состояния WebSocket
  ajaxUpdateBtn: document.getElementById('ajaxUpdateBtn'),  // Кнопка AJAX обновления
  projectBadge: document.getElementById('projectBadge'),    // Бейдж текущего проекта
  
  clearBtn: document.getElementById('clear'),               // Кнопка очистки логов
  refreshBtn: document.getElementById('refresh'),           // Кнопка обновления
  snapshotBtn: document.getElementById('snapshot'),         // Кнопка создания снимка
  lvlDebug: document.getElementById('lvlDebug'),            // Кнопка уровня DEBUG
  lvlInfo: document.getElementById('lvlInfo'),              // Кнопка уровня INFO
  lvlWarn: document.getElementById('lvlWarn'),              // Кнопка уровня WARN
  lvlErr: document.getElementById('lvlErr'),                // Кнопка уровня ERROR
  lvlOther: document.getElementById('lvlOther'),            // Кнопка уровня OTHER
  layoutBadge: document.getElementById('layoutBadge') || { textContent: '' }, // Бейдж режима отображения
  aggregate: document.getElementById('aggregate') || { checked: false },      // Чекбокс агрегации
  themeSwitch: document.getElementById('themeSwitch'),      // Переключатель темы
  copyFab: document.getElementById('copyFab'),              // Кнопка копирования
  groupBtn: document.getElementById('groupBtn') || { onclick: null },        // Кнопка группировки
  
  // New modern elements (новые элементы современного интерфейса)
  containerList: document.getElementById('containerList'),  // Список контейнеров
  logContent: document.getElementById('logContent'),        // Основной контент логов
  mobileToggle: document.getElementById('mobileToggle'),    // Переключатель мобильного режима
  optionsBtn: document.getElementById('optionsBtn'),        // Кнопка настроек
  helpBtn: document.getElementById('helpBtn'),              // Кнопка помощи
  logoutBtn: document.getElementById('logoutBtn'),          // Кнопка выхода
  sidebar: document.getElementById('sidebar'),              // Боковая панель
  sidebarToggle: document.getElementById('sidebarToggle'),  // Переключатель боковой панели
  header: document.getElementById('header'),                // Заголовок
  hotkeysModal: document.getElementById('hotkeysModal'),    // Модальное окно горячих клавиш
  hotkeysModalClose: document.getElementById('hotkeysModalClose'), // Кнопка закрытия модального окна
  multiViewPanel: document.getElementById('multiViewPanel'), // Панель мультипросмотра
  multiViewPanelTitle: document.getElementById('multiViewPanelTitle'), // Заголовок мультипросмотра
  singleViewPanel: document.getElementById('singleViewPanel'), // Панель одиночного просмотра
  singleViewTitle: document.getElementById('singleViewTitle'), // Заголовок одиночного просмотра
};

/**
 * Инициализация переключателя темы
 * Загружает сохраненную тему из localStorage и настраивает переключатель
 */
(function initTheme(){
  const saved = localStorage.lb_theme || 'dark';
  document.documentElement.setAttribute('data-theme', saved);
  els.themeSwitch.checked = (saved==='light');
  els.themeSwitch.addEventListener('change', ()=>{
    const t = els.themeSwitch.checked ? 'light' : 'dark';
    document.documentElement.setAttribute('data-theme', t);
    localStorage.lb_theme = t;
  });
})();

/**
 * Фильтрует контейнеры, убирая остановленные
 * @param {Array} containers - Массив контейнеров для фильтрации
 * @returns {Array} Отфильтрованный массив только работающих контейнеров
 */
function filterStoppedContainers(containers) {
  return containers.filter(container => {
    // Оставляем только работающие контейнеры
    return container.status === 'running';
  });
}

/**
 * Загружает состояние свернутых секций из localStorage
 * @returns {Object} Объект с состоянием секций
 */
function loadCollapsedSections() {
  try {
    const saved = localStorage.getItem('lb_collapsed_sections');
    return saved ? JSON.parse(saved) : {
      local: false,
      remote: false,
      hosts: {}
    };
  } catch (e) {
    console.warn('Ошибка загрузки состояния секций:', e);
    return {
      local: false,
      remote: false,
      hosts: {}
    };
  }
}

/**
 * Сохраняет состояние свернутых секций в localStorage
 * @param {Object} collapsedState - Объект с состоянием секций
 */
function saveCollapsedSections(collapsedState) {
  try {
    localStorage.setItem('lb_collapsed_sections', JSON.stringify(collapsedState));
  } catch (e) {
    console.warn('Ошибка сохранения состояния секций:', e);
  }
}

/**
 * Обновляет состояние свернутой секции
 * @param {string} sectionType - Тип секции ('local', 'remote', 'host-{hostname}')
 * @param {boolean} isCollapsed - Свернута ли секция
 */
function updateCollapsedSection(sectionType, isCollapsed) {
  const collapsedState = loadCollapsedSections();
  
  if (sectionType.startsWith('host-')) {
    const hostname = sectionType.replace('host-', '');
    collapsedState.hosts[hostname] = isCollapsed;
  } else {
    collapsedState[sectionType] = isCollapsed;
  }
  
  saveCollapsedSections(collapsedState);
}

/**
 * Устанавливает состояние WebSocket соединения в интерфейсе
 * @param {string} s - Состояние: 'on', 'off', 'err', 'available'
 */
function setWsState(s){ 
  console.log('setWsState: Устанавливаем состояние', s);
  console.log('setWsState: Текущие соединения:', Object.keys(state.open));
  
  els.wsstate.textContent = 'ws: ' + s; 
  
  // Удаляем все классы состояний
  els.wsstate.classList.remove('ws-on', 'ws-off', 'ws-err', 'ws-available');
  
  // Добавляем соответствующий класс
  if (s === 'on') {
    els.wsstate.classList.add('ws-on');
  } else if (s === 'off') {
    els.wsstate.classList.add('ws-off');
  } else if (s === 'err') {
    els.wsstate.classList.add('ws-err');
  } else if (s === 'available') {
    els.wsstate.classList.add('ws-available');
  }
}

/**
 * Определяет общее состояние WebSocket соединений
 * Проверяет все открытые соединения и устанавливает соответствующее состояние
 */
function determineWsState() {
  const openConnections = Object.keys(state.open);
  
  console.log('determineWsState: Проверяем', openConnections.length, 'соединений');
  console.log('determineWsState: Все соединения:', openConnections);
  
  // Если нет открытых соединений, проверяем сервер через AJAX
  if (openConnections.length === 0) {
    console.log('determineWsState: Нет соединений, проверяем сервер');
    // Асинхронно проверяем сервер, но возвращаем 'off' для немедленного отображения
    // Если сервер доступен, checkWebSocketStatus установит 'on'
    setTimeout(() => {
      checkWebSocketStatus();
    }, 100);
    return 'off';
  }
  
  // Проверяем состояние всех соединений
  let hasActiveConnection = false;
  let hasConnecting = false;
  let closedConnections = [];
  let errorConnections = [];
  
  for (const id of openConnections) {
    const obj = state.open[id];
    if (obj && obj.ws) {
      console.log(`determineWsState: Соединение ${id}, readyState:`, obj.ws.readyState, 'WebSocket:', obj.ws);
      
      if (obj.ws.readyState === WebSocket.OPEN) {
        hasActiveConnection = true;
        console.log(`determineWsState: Соединение ${id} активно`);
      } else if (obj.ws.readyState === WebSocket.CONNECTING) {
        hasConnecting = true;
        console.log(`determineWsState: Соединение ${id} подключается`);
      } else if (obj.ws.readyState === WebSocket.CLOSED || obj.ws.readyState === WebSocket.CLOSING) {
        closedConnections.push(id);
        console.log(`determineWsState: Соединение ${id} закрыто/закрывается`);
      }
    } else {
      console.log(`determineWsState: Соединение ${id} не найдено или нет WebSocket, obj:`, obj);
      closedConnections.push(id);
    }
  }
  
  // Удаляем закрытые соединения
  closedConnections.forEach(id => {
    console.log(`determineWsState: Удаляем закрытое соединение ${id}`);
    delete state.open[id];
  });
  
  // Если есть активные соединения или есть соединения в процессе установки
  if (hasActiveConnection || hasConnecting) {
    console.log('determineWsState: Есть активные/подключающиеся соединения, возвращаем on');
    return 'on';
  } else {
    console.log('determineWsState: Нет активных соединений, проверяем сервер');
    // Асинхронно проверяем сервер, но возвращаем 'off' для немедленного отображения
    // Если сервер доступен, checkWebSocketStatus установит 'on'
    setTimeout(() => {
      checkWebSocketStatus();
    }, 100);
    return 'off';
  }
}

// Функция для проверки состояния WebSocket через AJAX
async function checkWebSocketStatus() {
  try {
    const token = localStorage.getItem('access_token');
    if (!token) {
      console.log('checkWebSocketStatus: Нет токена, устанавливаем off');
      setWsState('off');
      return;
    }
    
    console.log('checkWebSocketStatus: Отправляем запрос к /api/websocket/status');
    const response = await fetch('/api/websocket/status', {
      headers: {
        'Authorization': `Bearer ${token}`
      }
    });
    
    console.log('checkWebSocketStatus: Получен ответ, статус:', response.status, response.statusText);
    
    if (response.ok) {
      const data = await response.json();
      console.log('checkWebSocketStatus: Получен ответ от сервера:', data);
      
      if (data.status === 'available') {
        // Проверяем активные клиентские соединения
        const openConnections = Object.keys(state.open);
        let hasActiveConnection = false;
        
        for (const id of openConnections) {
          const obj = state.open[id];
          if (obj && obj.ws && obj.ws.readyState === WebSocket.OPEN) {
            hasActiveConnection = true;
            break;
          }
        }
        
        // Если сервер доступен, всегда показываем 'on'
        console.log('checkWebSocketStatus: Сервер доступен, устанавливаем on');
        setWsState('on');
      } else if (data.status === 'no_containers') {
        console.log('checkWebSocketStatus: Нет контейнеров, устанавливаем off');
        setWsState('off');
      } else {
        console.log('checkWebSocketStatus: Ошибка сервера, устанавливаем err');
        setWsState('err');
      }
    } else {
      console.log('checkWebSocketStatus: HTTP ошибка, устанавливаем err');
      setWsState('err');
    }
  } catch (error) {
    console.error('checkWebSocketStatus: Ошибка запроса:', error);
    setWsState('err');
  }
}

// Интервал для автоматической проверки состояния WebSocket
let wsStatusInterval = null;

// Функция для запуска автоматической проверки состояния WebSocket
function startWebSocketStatusCheck() {
  if (wsStatusInterval) {
    clearInterval(wsStatusInterval);
  }
  
  // Проверяем каждые 3 секунды
  wsStatusInterval = setInterval(() => {
    console.log('Автоматическая проверка состояния WebSocket');
    checkWebSocketStatus();
  }, 3000);
  
  console.log('Запущена автоматическая проверка состояния WebSocket');
}

// Функция для остановки автоматической проверки
function stopWebSocketStatusCheck() {
  if (wsStatusInterval) {
    clearInterval(wsStatusInterval);
    wsStatusInterval = null;
    console.log('Остановлена автоматическая проверка состояния WebSocket');
  }
}

/**
 * Устанавливает визуальное состояние кнопки AJAX обновления
 * @param {boolean} enabled - Включено ли AJAX обновление
 */
function setAjaxUpdateState(enabled) {
  console.log('setAjaxUpdateState: enabled =', enabled, 'els.ajaxUpdateBtn =', !!els.ajaxUpdateBtn);
  
  if (els.ajaxUpdateBtn) {
    // Удаляем все классы состояний
    els.ajaxUpdateBtn.classList.remove('ajax-on', 'ajax-off');
    
    // Добавляем соответствующий класс
    if (enabled) {
      els.ajaxUpdateBtn.classList.add('ajax-on');
      els.ajaxUpdateBtn.textContent = 'update';
      console.log('setAjaxUpdateState: Устанавливаем зеленый цвет (ajax-on)');
    } else {
      els.ajaxUpdateBtn.classList.add('ajax-off');
      els.ajaxUpdateBtn.textContent = 'update';
      console.log('setAjaxUpdateState: Устанавливаем красный цвет (ajax-off)');
    }
  } else {
    console.error('setAjaxUpdateState: Кнопка ajaxUpdateBtn не найдена!');
  }
}

/**
 * Обновляет отображение всех логов при изменении фильтров
 * Перерисовывает логи с учетом текущих настроек фильтрации и уровней
 */
function refreshAllLogs() {
  // Обновляем обычный просмотр
  Object.keys(state.open).forEach(id => {
    const obj = state.open[id];
    if (!obj || !obj.logEl) return;
    
    // Получаем все логи из буфера
    const allLogs = obj.allLogs || [];
    const filteredHtml = [];
    
    allLogs.forEach(logEntry => {
      // Проверяем уровень логирования
      if (!allowedByLevel(logEntry.cls)) return;
      
      // Проверяем фильтр
      if (!applyFilter(logEntry.line)) return;
      
      filteredHtml.push(logEntry.html);
    });
    
    // Обновляем отображение
    obj.logEl.innerHTML = filteredHtml.join('');
    
    // Сразу очищаем пустые строки в legacy панели
    cleanSingleViewEmptyLines(obj.logEl);
    cleanDuplicateLines(obj.logEl);
    
    // Обновляем современный интерфейс
    if (state.current && state.current.id === id && els.logContent) {
      els.logContent.innerHTML = obj.logEl.innerHTML;
      
      // Очищаем дублированные строки в Single View после обновления
      cleanSingleViewEmptyLines(els.logContent);
      cleanDuplicateLines(els.logContent);
    }
  });
  
  // Обновляем мультипросмотр
  if (state.multiViewMode) {
    state.selectedContainers.forEach(containerId => {
      const obj = state.open[containerId];
      if (!obj || !obj.logEl) return;
      
      // Получаем все логи из буфера
      const allLogs = obj.allLogs || [];
      const filteredHtml = [];
      
      allLogs.forEach(logEntry => {
        // Проверяем уровень логирования
        if (!allowedByLevel(logEntry.cls)) return;
        
        // Проверяем фильтр
        if (!applyFilter(logEntry.line)) return;
        
        filteredHtml.push(logEntry.html);
      });
      
      // Обновляем отображение в мультипросмотре
      const multiViewLog = document.querySelector(`.multi-view-log[data-container-id="${containerId}"]`);
      if (multiViewLog) {
        multiViewLog.innerHTML = filteredHtml.join('');
        
        // Сразу очищаем пустые строки в мультипросмотре
        cleanMultiViewEmptyLines(multiViewLog);
        cleanMultiViewDuplicateLines(multiViewLog);
      }
    });
  }
  
  // Пересчитываем счетчики в зависимости от режима после обновления логов
  setTimeout(() => {
    if (state.multiViewMode) {
      recalculateMultiViewCounters();
    } else {
      recalculateCounters();
    }
  }, 100);
}
/**
 * Экранирует HTML символы для безопасного отображения
 * @param {string} s - Строка для экранирования
 * @returns {string} Экранированная строка
 */
function escapeHtml(s){ return s.replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m])); }

/**
 * Классифицирует строку лога по уровню логирования
 * Определяет уровень на основе ключевых слов и паттернов в строке
 * @param {string} line - Строка лога для классификации
 * @returns {string} Класс уровня: 'dbg', 'err', 'warn', 'ok', 'other'
 */
function classify(line){
  const l = line.toLowerCase();
  
  // Проверяем различные форматы уровней логирования (более специфичные сначала)
  
  // DEBUG - ищем точное совпадение уровня логирования
  if (/\s- DEBUG -|\s\[debug\]|level=debug|\bdebug\b(?=\s|$)/.test(l)) {
    return 'dbg';
  }
  
  // ERROR - ищем точное совпадение уровня логирования
  if (/\s- ERROR -|\s\[error\]|level=error/.test(l)) {
    return 'err';
  }
  
  // WARNING - ищем точное совпадение уровня логирования
  if (/\s- WARNING -|\s\[warn\]|level=warn/.test(l)) {
    return 'warn';
  }
  
  // INFO - ищем точное совпадение уровня логирования
  if (/\s- INFO -|\s\[info\]|level=info/.test(l)) {
    return 'ok';
  }
  
  // Дополнительные проверки для других форматов (только если не найдены точные совпадения)
  if (/\bdebug\b/i.test(l)) return 'dbg';
  if (/\berror\b/i.test(l)) return 'err';
  if (/\bwarning\b/i.test(l)) return 'warn';
  if (/\binfo\b/i.test(l)) return 'ok';
  
  return 'other';
}
/**
 * Проверяет, разрешен ли отображение лога данного уровня
 * @param {string} cls - Класс уровня лога ('dbg', 'err', 'warn', 'ok', 'other')
 * @returns {boolean} Разрешен ли отображение
 */
function allowedByLevel(cls){
  if (cls==='dbg') return state.levels.debug;
  if (cls==='err') return state.levels.err;
  if (cls==='warn') return state.levels.warn;
  if (cls==='ok') return state.levels.info;
  if (cls==='other') return state.levels.other; // Показываем неклассифицированные строки в зависимости от настройки
  return true;
}

/**
 * Проверяет, разрешен ли отображение лога данного уровня для конкретного контейнера
 * Используется в режиме мультипросмотра для индивидуальных настроек контейнеров
 * @param {string} cls - Класс уровня лога ('dbg', 'err', 'warn', 'ok', 'other')
 * @param {string} containerId - ID контейнера
 * @returns {boolean} Разрешен ли отображение
 */
function allowedByContainerLevel(cls, containerId) {
  // Если настройки контейнера не инициализированы, инициализируем их
  if (!state.containerLevels) {
    state.containerLevels = {};
  }
  if (!state.containerLevels[containerId]) {
    state.containerLevels[containerId] = {debug: true, info: true, warn: true, err: true, other: true};
  }
  
  const containerLevels = state.containerLevels[containerId];
  let result;
  
  if (cls==='dbg') result = containerLevels.debug;
  else if (cls==='err') result = containerLevels.err;
  else if (cls==='warn') result = containerLevels.warn;
  else if (cls==='ok') result = containerLevels.info;
  else if (cls==='other') result = containerLevels.other;
  else result = true;
  
  console.log(`allowedByContainerLevel: containerId=${containerId}, cls=${cls}, result=${result}, levels=`, containerLevels);
  
  return result;
}

/**
 * Обновляет видимость логов в Single View режиме
 * Перерисовывает логи с учетом текущих фильтров и настроек уровней
 * @param {HTMLElement} logElement - Элемент для обновления
 */
function updateLogVisibility(logElement) {
  if (!logElement || !state.current) return;
  
  const containerId = state.current.id;
  const obj = state.open[containerId];
  if (!obj || !obj.allLogs) return;
  
  // Пересоздаем содержимое лога с учетом фильтров, сохраняя HTML-разметку
  let visibleHtml = '';
  obj.allLogs.forEach(logEntry => {
    const shouldShow = allowedByLevel(logEntry.cls) && applyFilter(logEntry.line);
    if (shouldShow) {
      visibleHtml += logEntry.html + '\n';
    }
  });
  
  logElement.innerHTML = visibleHtml;
  
  // Обновляем счетчики
  recalculateCounters();
  
  // Обновляем состояние кнопок уровней логирования только для single-view
  const singleLevelBtns = document.querySelectorAll('.single-view-levels .level-btn');
  singleLevelBtns.forEach(btn => {
    const level = btn.getAttribute('data-level');
    const isActive = state.levels[level];
    btn.classList.toggle('active', isActive);
    btn.classList.toggle('disabled', !isActive);
  });
}

// Функция для обновления видимости логов конкретного контейнера в Multi View
function updateContainerLogVisibility(containerId) {
  if (!state.multiViewMode) return;
  
  console.log(`updateContainerLogVisibility: Обновляем видимость логов для контейнера ${containerId}`);
  
  const logElement = document.querySelector(`.multi-view-log[data-container-id="${containerId}"]`);
  if (!logElement) return;
  
  const obj = state.open[containerId];
  if (!obj || !obj.allLogs) return;
  
  // Пересоздаем содержимое лога с учетом фильтров контейнера, сохраняя HTML-разметку
  let visibleHtml = '';
  obj.allLogs.forEach(logEntry => {
    const shouldShow = allowedByContainerLevel(logEntry.cls, containerId) && applyFilter(logEntry.line);
    if (shouldShow) {
      visibleHtml += logEntry.html + '\n';
    }
  });
  
  logElement.innerHTML = visibleHtml;
  
  // Обновляем счетчики для этого контейнера
  updateContainerCounters(containerId);
  
  // Обновляем состояние кнопок уровней логирования только для этого контейнера
  const levelBtns = document.querySelectorAll(`.level-btn[data-container-id="${containerId}"]`);
  levelBtns.forEach(btn => {
    const level = btn.getAttribute('data-level');
    // Используем настройки контейнера, если они есть
    const containerLevels = state.containerLevels && state.containerLevels[containerId] ? 
      state.containerLevels[containerId] : {debug: true, info: true, warn: true, err: true, other: true};
    const isActive = containerLevels[level];
    btn.classList.toggle('active', isActive);
    btn.classList.toggle('disabled', !isActive);
  });
}

// Функция для обновления счетчиков конкретного контейнера
function updateContainerCounters(containerId) {
  const obj = state.open[containerId];
  if (!obj || !obj.allLogs) return;
  
  // Получаем значение Tail Lines
  const tailLines = parseInt(els.tail.value) || 50;
  
  // Берем только последние N логов
  const visibleLogs = obj.allLogs.slice(-tailLines);
  
  // Сбрасываем счетчики
  obj.counters = {dbg: 0, info: 0, warn: 0, err: 0, other: 0};
  
  // Пересчитываем счетчики только для отображаемых логов
  visibleLogs.forEach(logEntry => {
    const shouldShow = allowedByContainerLevel(logEntry.cls, containerId) && applyFilter(logEntry.line);
    if (shouldShow) {
      if (logEntry.cls === 'dbg') obj.counters.dbg++;
      if (logEntry.cls === 'ok') obj.counters.info++;
      if (logEntry.cls === 'warn') obj.counters.warn++;
      if (logEntry.cls === 'err') obj.counters.err++;
      if (logEntry.cls === 'other') obj.counters.other++;
    }
  });
  
  // Обновляем отображение счетчиков в кнопках заголовка
  const levelBtns = document.querySelectorAll(`.level-btn[data-container-id="${containerId}"]`);
  levelBtns.forEach(btn => {
    const level = btn.getAttribute('data-level');
    const valueEl = btn.querySelector('.level-value');
    if (valueEl) {
      switch (level) {
        case 'debug': valueEl.textContent = obj.counters.dbg; break;
        case 'info': valueEl.textContent = obj.counters.info; break;
        case 'warn': valueEl.textContent = obj.counters.warn; break;
        case 'err': valueEl.textContent = obj.counters.err; break;
        case 'other': valueEl.textContent = obj.counters.other; break;
      }
    }
  });
}

// Функция для обновления счетчиков в кнопках заголовков
function updateHeaderCounters(containerId, counters) {
  // Обновляем счетчики для single-view (если это текущий контейнер)
  if (state.current && state.current.id === containerId) {
    const singleLevelBtns = document.querySelectorAll('.single-view-levels .level-btn');
    singleLevelBtns.forEach(btn => {
      const level = btn.getAttribute('data-level');
      const valueEl = btn.querySelector('.level-value');
      if (valueEl) {
        switch (level) {
          case 'debug': valueEl.textContent = counters.dbg; break;
          case 'info': valueEl.textContent = counters.info; break;
          case 'warn': valueEl.textContent = counters.warn; break;
          case 'err': valueEl.textContent = counters.err; break;
          case 'other': valueEl.textContent = counters.other; break;
        }
      }
    });
  }
  
  // Обновляем счетчики для multi-view (только для конкретного контейнера)
  if (state.multiViewMode && state.selectedContainers.includes(containerId)) {
    const multiLevelBtns = document.querySelectorAll(`.level-btn[data-container-id="${containerId}"]`);
    multiLevelBtns.forEach(btn => {
      const level = btn.getAttribute('data-level');
      const valueEl = btn.querySelector('.level-value');
      if (valueEl) {
        switch (level) {
          case 'debug': valueEl.textContent = counters.dbg; break;
          case 'info': valueEl.textContent = counters.info; break;
          case 'warn': valueEl.textContent = counters.warn; break;
          case 'err': valueEl.textContent = counters.err; break;
          case 'other': valueEl.textContent = counters.other; break;
        }
      }
    });
  }
}

// Функция для инициализации состояния кнопок уровней логирования
function initializeLevelButtons() {
  // Восстанавливаем состояние кнопок loglevels из localStorage
  const savedLevelsState = getLogLevelsStateFromStorage();
  if (savedLevelsState) {
    console.log('Restoring log levels state from localStorage');
    
    // Восстанавливаем глобальные настройки для single-view
    if (savedLevelsState.globalLevels) {
      state.levels = { ...state.levels, ...savedLevelsState.globalLevels };
    }
    
    // Восстанавливаем настройки контейнеров для multi-view
    if (savedLevelsState.containerLevels) {
      state.containerLevels = { ...state.containerLevels, ...savedLevelsState.containerLevels };
    }
  }
  
  // Инициализируем кнопки для single-view
  const singleLevelBtns = document.querySelectorAll('.single-view-levels .level-btn');
  singleLevelBtns.forEach(btn => {
    const level = btn.getAttribute('data-level');
    const isActive = state.levels[level];
    btn.classList.toggle('active', isActive);
    btn.classList.toggle('disabled', !isActive);
  });
  
  // Инициализируем кнопки для multi-view (если есть)
  const multiLevelBtns = document.querySelectorAll('.multi-view-levels .level-btn');
  multiLevelBtns.forEach(btn => {
    const level = btn.getAttribute('data-level');
    const containerId = btn.getAttribute('data-container-id');
    
    // Инициализируем настройки контейнера, если их нет
    if (containerId && (!state.containerLevels || !state.containerLevels[containerId])) {
      if (!state.containerLevels) {
        state.containerLevels = {};
      }
      state.containerLevels[containerId] = {debug: true, info: true, warn: true, err: true, other: true};
    }
    
    // Используем настройки контейнера
    const isActive = state.containerLevels && state.containerLevels[containerId] ? 
      state.containerLevels[containerId][level] : true;
    
    btn.classList.toggle('active', isActive);
    btn.classList.toggle('disabled', !isActive);
  });
  
  // Обновляем стили логов после инициализации кнопок
  updateLogStyles();
  
  // Применяем настройки wrap text
  applyWrapSettings();
}
/**
 * Применяет фильтр к строке лога
 * Проверяет, соответствует ли строка текущему фильтру (безопасный regex поиск)
 * @param {string} line - Строка лога для проверки
 * @returns {boolean} Проходит ли строка фильтр
 */
function applyFilter(line){
  if(!state.filter) return true;
  try{ 
    // Экранируем специальные символы regex для безопасного поиска
    const escapedFilter = state.filter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    return new RegExp(escapedFilter, 'i').test(line); 
  }catch(e){ 
    console.error('Filter error:', e);
    return true; 
  }
}

/**
 * Константы и настройки для работы с ANSI цветами
 * SGR (Select Graphic Rendition): 0/1/3/4, 30-37
 */

/**
 * Настройки экземпляров контейнеров
 * Содержит цвета, фильтры и палитру для визуального различия контейнеров
 */
const inst = { 
  colors: {},           // Кэш цветов для контейнеров
  filters: {},          // Фильтры для экземпляров
  palette: [            // Палитра цветов для контейнеров
    '#7aa2f7','#9ece6a','#e0af68','#f7768e','#bb9af7','#7dcfff','#c0caf5','#f6bd60',
    '#84cc16','#06b6d4','#fb923c','#ef4444','#22c55e','#a855f7'
  ]
};

/**
 * Генерирует уникальный цвет для контейнера на основе его ID
 * Использует хеш-функцию для детерминированного выбора цвета из палитры
 * @param {string} id8 - Первые 8 символов ID контейнера
 * @returns {string} HEX цвет для контейнера
 */
function idColor(id8){
  if (inst.colors[id8]) return inst.colors[id8];
  // simple hash to pick from palette
  let h = 0; for (let i=0;i<id8.length;i++){ h = (h*31 + id8.charCodeAt(i))>>>0; }
  const color = inst.palette[h % inst.palette.length];
  inst.colors[id8] = color;
  return color;
}

function updateIdFiltersBar(){
  const bar = document.getElementById('idFilters');
  bar.innerHTML = '';
  const ids = Object.keys(inst.filters);
  if (!ids.length){ bar.style.display='none'; return; }
  bar.style.display='flex';
  ids.forEach(id8=>{
    const wrap = document.createElement('label');
    wrap.style.display='inline-flex'; wrap.style.alignItems='center'; wrap.style.gap='6px';
    const cb = document.createElement('input'); cb.type='checkbox'; cb.checked = inst.filters[id8] !== false;
    cb.onchange = ()=> inst.filters[id8] = cb.checked;
    const chip = document.createElement('span');
    chip.className='inst-tag';
    chip.style.borderColor = idColor(id8);
    chip.style.color = idColor(id8);
    chip.textContent = id8;
    wrap.appendChild(cb); wrap.appendChild(chip);
    bar.appendChild(wrap);
  });
}

function shouldShowInstance(id8){
  if (!Object.keys(inst.filters).length) return true;
  const val = inst.filters[id8];
  return val !== false;
}

function parsePrefixAndStrip(line){
  // Accept "[id]" or "[id service]" prefixes from fan/fan_group
  const m = line.match(/^\[([0-9a-f]{8})(?:\s+[^\]]+)?\]\s(.*)$/i);
  if (!m) return null;
  return {id8: m[1], rest: m[2]};
}

/**
 * Конвертирует ANSI escape-последовательности в HTML
 * Поддерживает цвета (30-37), жирный (1), курсив (3), подчеркивание (4)
 * @param {string} text - Текст с ANSI кодами
 * @returns {string} HTML с CSS классами для стилизации
 */
function ansiToHtml(text){
  const ESC = '\u001b[';
  const parts = text.split(ESC);
  if (parts.length === 1) return escapeHtml(text);
  let html = escapeHtml(parts[0]);
  let classes = [];
  for (let i=1;i<parts.length;i++){
    const seg = parts[i];
    const m = seg.match(/^([0-9;]+)m(.*)$/s);
    if(!m){ html += escapeHtml(seg); continue; }
    const codes = m[1].split(';').map(Number);
    let rest = m[2];
    for(const c of codes){
      if (c===0) classes = [];
      else if (c===1) classes.push('ansi-bold');
      else if (c===3) classes.push('ansi-italic');
      else if (c===4) classes.push('ansi-underline');
      else if (c>=30 && c<=37){
        classes = classes.filter(x=>!x.startsWith('ansi-'));
        const map = {30:'black',31:'red',32:'green',33:'yellow',34:'blue',35:'magenta',36:'cyan',37:'white'};
        classes.push('ansi-'+map[c]);
      }
    }
    if (classes.length) html += `<span class="${classes.join(' ')}">` + escapeHtml(rest) + `</span>`;
    else html += escapeHtml(rest);
  }
  return html;
}

function panelTemplate(svc){
  const div = document.createElement('div'); div.className='panel'; div.dataset.cid = svc.id;
  div.innerHTML = `
    <div class="title">
      <span>${escapeHtml((svc.project?`[${svc.project}] `:'')+(svc.service||svc.name))}</span>
      <span class="counter">dbg:<b class="cdbg">0</b> info:<b class="cinfo">0</b> warn:<b class="cwarn">0</b> err:<b class="cerr">0</b></span>
      <div class="toolbar">
        <button class="primary t-reconnect">reconnect</button>
        <button class="t-snapshot">snapshot</button>
        <button class="t-close">close</button>
      </div>
    </div>
    <div class="logwrap"><pre class="log"></pre></div>`;
  return div;
}

function buildTabs(){
  // Legacy tabs (hidden)
  els.tabs.innerHTML='';
  state.services.forEach(svc=>{
    const b = document.createElement('button');
    b.className='tab' + ((state.current && svc.id===state.current.id) ? ' active':'');
    b.textContent = (svc.project?(`[${svc.project}] `):'') + (svc.service||svc.name);
    b.title = `${svc.name} • ${svc.image} • ${svc.status}`;
    b.onclick = async ()=> await switchToSingle(svc);
    els.tabs.appendChild(b);
  });
  
  // Modern container list
  els.containerList.innerHTML = '';
  
  // Миникарточки контейнеров
  const miniContainerList = document.getElementById('miniContainerList');
  if (miniContainerList) {
    miniContainerList.innerHTML = '';
  }
  
  // Группируем контейнеры по типу (локальные и удаленные)
  const localContainers = state.services.filter(svc => !svc.is_remote);
  const remoteContainers = state.services.filter(svc => svc.is_remote);
  
  // Создаем секцию для локальных контейнеров
  if (localContainers.length > 0) {
    const localSection = document.createElement('div');
    localSection.className = 'container-section local-section';
    localSection.innerHTML = `
      <div class="section-header collapsible-section" data-section="local">
        <div class="section-header-left">
          <i class="fas fa-server"></i>
          <span>Локальные контейнеры</span>
          <span class="section-count">${localContainers.length}</span>
        </div>
        <button class="section-toggle-btn" data-target="local" title="Свернуть/развернуть секцию">
          <i class="fas fa-chevron-down"></i>
        </button>
      </div>
      <div class="section-content" id="local-content">
    `;
    els.containerList.appendChild(localSection);
    
    localContainers.forEach(svc => {
      const item = document.createElement('div');
      item.className = 'container-item local-container';
      if (state.current && svc.id === state.current.id) {
        item.classList.add('active');
      }
      item.setAttribute('data-cid', svc.id);
      
      const statusClass = svc.status === 'running' ? 'running' : 
                         svc.status === 'stopped' ? 'stopped' : 'paused';
      
      // Создаем tooltip для локального контейнера
      const tooltipText = `Локальный контейнер
Имя: ${svc.name}
Сервис: ${svc.service || svc.name}
Проект: ${svc.project || 'standalone'}
Статус: ${svc.status}
Хост: localhost
${svc.url ? `URL: ${svc.url}` : ''}
${svc.status === 'running' && svc.host_port ? `Порт: ${svc.host_port}` : ''}

Кликните для просмотра логов`;

      item.innerHTML = `
        <div class="container-name">
          <i class="fas fa-cube"></i>
          ${escapeHtml(svc.name)}
        </div>
        <div class="container-service">
          ${escapeHtml(svc.service || svc.name)}
          • ${escapeHtml(svc.project || 'standalone')}
        </div>
        <div class="container-status">
          <span class="status-indicator ${statusClass}"></span>
          ${escapeHtml(svc.status)}
          ${svc.status === 'running' && svc.host_port ? ` on ${svc.host_port} port` : ''}
          ${svc.url ? `<a href="${svc.url}" target="_blank" class="container-link" title="Открыть сайт"><i class="fas fa-external-link-alt"></i></a>` : ''}
        </div>
        <div class="container-select">
          <input type="checkbox" id="select-${svc.id}" class="container-checkbox" data-container-id="${svc.id}" ${state.selectedContainers.includes(svc.id) ? 'checked' : ''}>
          <label for="select-${svc.id}" class="container-checkbox-label" title="Выбрать для мультипросмотра"></label>
        </div>
      `;
      
      // Добавляем tooltip
      item.setAttribute('title', tooltipText);
      
      // Устанавливаем состояние selected для контейнера
      if (state.selectedContainers.includes(svc.id)) {
        item.classList.add('selected');
      }
      
      item.onclick = async (e) => {
        // Не переключаем контейнер, если кликнули на ссылку или чекбокс
        if (e.target.closest('.container-link') || e.target.closest('.container-select')) {
          e.stopPropagation();
          return;
        }
        await switchToSingle(svc);
      };
      
      // Добавляем контейнер внутрь секции локальных контейнеров
      const localContent = document.getElementById('local-content');
      if (localContent) {
        localContent.appendChild(item);
      } else {
        els.containerList.appendChild(item);
      }
    });
  }
  
  // Создаем секцию для удаленных контейнеров
  if (remoteContainers.length > 0) {
    const remoteSection = document.createElement('div');
    remoteSection.className = 'container-section remote-section';
    remoteSection.innerHTML = `
      <div class="section-header collapsible-section" data-section="remote">
        <div class="section-header-left">
          <i class="fas fa-globe"></i>
          <span>Удаленные контейнеры</span>
          <span class="section-count">${remoteContainers.length}</span>
        </div>
        <button class="section-toggle-btn" data-target="remote" title="Свернуть/развернуть секцию">
          <i class="fas fa-chevron-down"></i>
        </button>
      </div>
      <div class="section-content" id="remote-content">
    `;
    els.containerList.appendChild(remoteSection);
    
    // Группируем удаленные контейнеры по хостам
    const containersByHost = {};
    remoteContainers.forEach(svc => {
      const hostname = svc.hostname || 'unknown';
      if (!containersByHost[hostname]) {
        containersByHost[hostname] = [];
      }
      containersByHost[hostname].push(svc);
    });
    
    Object.entries(containersByHost).forEach(([hostname, containers]) => {
      const hostSection = document.createElement('div');
      hostSection.className = 'host-section';
      hostSection.innerHTML = `
        <div class="host-header collapsible-section" data-section="host-${hostname}">
          <div class="host-header-left">
            <i class="fas fa-server"></i>
            <span class="host-name">${escapeHtml(hostname)}</span>
            <span class="host-count">${containers.length}</span>
          </div>
          <button class="section-toggle-btn" data-target="host-${hostname}" title="Свернуть/развернуть секцию">
            <i class="fas fa-chevron-down"></i>
          </button>
        </div>
        <div class="section-content" id="host-${hostname}-content">
      `;
      
      // Добавляем секцию хоста внутрь секции удаленных контейнеров
      const remoteContent = document.getElementById('remote-content');
      if (remoteContent) {
        remoteContent.appendChild(hostSection);
      } else {
        els.containerList.appendChild(hostSection);
      }
      
      containers.forEach(svc => {
        const item = document.createElement('div');
        item.className = 'container-item remote-container';
        if (state.current && svc.id === state.current.id) {
          item.classList.add('active');
        }
        item.setAttribute('data-cid', svc.id);
        item.setAttribute('data-hostname', svc.hostname);
        
        const statusClass = svc.status === 'running' ? 'running' : 
                           svc.status === 'stopped' ? 'stopped' : 'paused';
        
        // Создаем tooltip для удаленного контейнера
        const tooltipText = `Удаленный контейнер
Имя: ${svc.name}
Сервис: ${svc.service || svc.name}
Проект: ${svc.project || 'remote'}
Статус: ${svc.status}
Хост: ${svc.hostname}
${svc.last_modified ? `Обновлено: ${new Date(svc.last_modified * 1000).toLocaleString()}` : ''}

Кликните для просмотра логов`;
        
        item.innerHTML = `
          <div class="container-name">
            <i class="fas fa-cube"></i>
            ${escapeHtml(svc.name)}
            <span class="remote-badge" title="Удаленный контейнер">
              <i class="fas fa-globe"></i>
            </span>
          </div>
          <div class="container-service">
            ${escapeHtml(svc.service || svc.name)}
            • ${escapeHtml(svc.project || 'remote')}
            <span class="host-info">• ${escapeHtml(svc.hostname)}</span>
          </div>
          <div class="container-status">
            <span class="status-indicator ${statusClass}"></span>
            ${escapeHtml(svc.status)}
            ${svc.last_modified ? `<span class="last-update">• Обновлено: ${new Date(svc.last_modified * 1000).toLocaleString()}</span>` : ''}
          </div>
          <div class="container-select">
            <input type="checkbox" id="select-${svc.id}" class="container-checkbox" data-container-id="${svc.id}" ${state.selectedContainers.includes(svc.id) ? 'checked' : ''}>
            <label for="select-${svc.id}" class="container-checkbox-label" title="Выбрать для мультипросмотра"></label>
          </div>
        `;
        
        // Добавляем tooltip
        item.setAttribute('title', tooltipText);
        
        // Устанавливаем состояние selected для контейнера
        if (state.selectedContainers.includes(svc.id)) {
          item.classList.add('selected');
        }
        
        item.onclick = async (e) => {
          // Не переключаем контейнер, если кликнули на ссылку или чекбокс
          if (e.target.closest('.container-link') || e.target.closest('.container-select')) {
            e.stopPropagation();
            return;
          }
          await switchToSingle(svc);
        };
        
        // Добавляем контейнер внутрь секции хоста
        const hostContent = document.getElementById(`host-${hostname}-content`);
        if (hostContent) {
          hostContent.appendChild(item);
        } else {
          els.containerList.appendChild(item);
        }
      });
      
          // Применяем сохраненное состояние для секции хоста
    const hostContent = document.getElementById(`host-${hostname}-content`);
    if (hostContent) {
      const collapsedState = loadCollapsedSections();
      const isHostCollapsed = collapsedState.hosts[hostname] || false;
      
      if (isHostCollapsed) {
        hostContent.style.display = 'none';
        const hostButton = document.querySelector(`[data-target="host-${hostname}"] .section-toggle-btn`);
        if (hostButton) {
          const icon = hostButton.querySelector('i');
          icon.className = 'fas fa-chevron-right';
          hostButton.title = 'Развернуть секцию';
        }
      } else {
        hostContent.style.display = 'block';
      }
    }
  });
  
  // Применяем сохраненное состояние для секции удаленных контейнеров
  const remoteContent = document.getElementById('remote-content');
  if (remoteContent) {
    const collapsedState = loadCollapsedSections();
    const isRemoteCollapsed = collapsedState.remote || false;
    
    if (isRemoteCollapsed) {
      remoteContent.style.display = 'none';
      const remoteButton = document.querySelector('[data-target="remote"] .section-toggle-btn');
      if (remoteButton) {
        const icon = remoteButton.querySelector('i');
        icon.className = 'fas fa-chevron-right';
        remoteButton.title = 'Развернуть секцию';
      }
    } else {
      remoteContent.style.display = 'block';
    }
  }
  
  // Применяем сохраненное состояние для секции локальных контейнеров
  const localContent = document.getElementById('local-content');
  if (localContent) {
    const collapsedState = loadCollapsedSections();
    const isLocalCollapsed = collapsedState.local || false;
    
    if (isLocalCollapsed) {
      localContent.style.display = 'none';
      const localButton = document.querySelector('[data-target="local"] .section-toggle-btn');
      if (localButton) {
        const icon = localButton.querySelector('i');
        icon.className = 'fas fa-chevron-right';
        localButton.title = 'Развернуть секцию';
      }
    } else {
      localContent.style.display = 'block';
    }
  }
  }
  
  // Создаем миникарточки для всех контейнеров
  if (miniContainerList) {
    state.services.forEach(svc => {
      const miniItem = document.createElement('div');
      miniItem.className = 'mini-container-item';
      if (svc.is_remote) {
        miniItem.classList.add('remote-container');
      }
      if (state.current && svc.id === state.current.id) {
        miniItem.classList.add('active');
      }
      miniItem.setAttribute('data-cid', svc.id);
      
      // Определяем статус для миникарточки
      const statusClass = svc.status === 'running' ? 'running' : 
                         svc.status === 'stopped' ? 'stopped' : 'paused';
      
      // Сокращаем имя для миникарточки
      const shortName = svc.name.length > 8 ? svc.name.substring(0, 6) + '..' : svc.name;
      
      miniItem.innerHTML = `
        <div class="mini-container-icon">
          <i class="fas fa-cube"></i>
          ${svc.is_remote ? '<i class="fas fa-globe remote-icon"></i>' : ''}
        </div>
        <div class="mini-container-name">${escapeHtml(shortName)}</div>
        <div class="mini-container-status ${statusClass}"></div>
      `;
      
      // Добавляем обработчики для всплывающих подсказок
      miniItem.addEventListener('mouseenter', (e) => {
        showMiniContainerTooltip(e, svc);
      });
      
      miniItem.addEventListener('mouseleave', () => {
        // Добавляем задержку перед скрытием подсказки
        const tooltip = document.getElementById('miniContainerTooltip');
        if (tooltip) {
          tooltip.hideTimer = setTimeout(() => {
            if (tooltip && !tooltip.matches(':hover')) {
              hideMiniContainerTooltip();
            }
          }, 150);
        }
      });
      
      // Обработчик клика для миникарточек с поддержкой Shift+клик и Ctrl+клик
      miniItem.addEventListener('click', async (e) => {
        e.preventDefault();
        e.stopPropagation();
        
        if (e.shiftKey && lastSelectedContainerId && lastSelectedContainerId !== svc.id) {
          // Shift+клик с предыдущим выбором - диапазонный выбор
          console.log('Shift+клик для диапазонного выбора:', lastSelectedContainerId, 'to', svc.id);
          selectContainerRange(lastSelectedContainerId, svc.id);
        } else if (e.shiftKey) {
          // Shift+клик - добавляем/убираем из мультивыбора
          console.log('Shift+клик на миникарточку:', svc.name);
          toggleContainerSelection(svc.id);
          lastSelectedContainerId = svc.id;
        } else if (e.ctrlKey || e.metaKey) {
          // Ctrl/Cmd+клик - добавляем/убираем из мультивыбора
          console.log('Ctrl+клик на миникарточку:', svc.name);
          toggleContainerSelection(svc.id);
          lastSelectedContainerId = svc.id;
        } else {
          // Обычный клик - переключаемся в single view
          console.log('Обычный клик на миникарточку:', svc.name);
          lastSelectedContainerId = svc.id;
          await switchToSingle(svc);
        }
      });
      
      miniContainerList.appendChild(miniItem);
    });
  }
  
  // Добавляем обработчики для сворачивания секций после построения интерфейса
  // Вызов перемещен в конец инициализации
}

function setLayout(cls){
  state.layout = cls;
  if (els.layoutBadge) {
    els.layoutBadge.textContent = 'view: ' + (cls==='tabs'?'tabs':cls);
  }
  els.grid.className = cls==='tabs' ? 'grid-1' : (cls==='grid2'?'grid-2':cls==='grid3'?'grid-3':'grid-4');
}

async function fetchProjects(){
  try {
    console.log('Fetching projects...');
    const url = new URL(location.origin + '/api/containers/projects');
    const token = localStorage.getItem('access_token');
    if (!token) {
      console.error('No access token found');
      window.location.href = '/login';
      return;
    }
    
    const res = await fetch(url, {
      headers: {
        'Authorization': `Bearer ${token}`
      }
    });
    if (!res.ok){ 
      if (res.status === 401) {
        console.error('Unauthorized, redirecting to login');
        window.location.href = '/login';
        return;
      }
      console.error('Failed to fetch projects:', res.status, res.statusText); 
      return; 
    }
    const projects = await res.json();
    console.log('Projects loaded:', projects);
    

    
    // Обновляем мультивыбор проектов в заголовке
    const dropdown = document.getElementById('projectSelectDropdown');
    const display = document.getElementById('projectSelectDisplay');
    const displayText = display?.querySelector('.multi-select-text');
    
    console.log('Multi-select elements found:', {dropdown: !!dropdown, display: !!display, displayText: !!displayText});
    
    if (dropdown && displayText) {
      // Очищаем dropdown
      dropdown.innerHTML = '';
      
      // Добавляем опцию "All Projects"
      const allOption = document.createElement('div');
      allOption.className = 'multi-select-option';
      allOption.setAttribute('data-value', 'all');
      allOption.innerHTML = `
        <input type="checkbox" id="project-all" checked>
        <label for="project-all">All Projects</label>
      `;
      dropdown.appendChild(allOption);
      
      // Добавляем проекты
      console.log('Adding projects to multi-select:', projects);
      projects.forEach(project => {
        const option = document.createElement('div');
        option.className = 'multi-select-option';
        option.setAttribute('data-value', project);
        option.innerHTML = `
          <input type="checkbox" id="project-${project}">
          <label for="project-${project}">${escapeHtml(project)}</label>
        `;
        dropdown.appendChild(option);
      });
      
      // Восстанавливаем сохраненные выбранные проекты
      const savedProjects = JSON.parse(localStorage.getItem('lb_selected_projects') || '["all"]');
      updateMultiSelect(savedProjects);
      
      console.log('Multi-select updated, current selection:', savedProjects);
    } else {
      console.error('Multi-select elements not found!');
    }
  } catch (error) {
    console.error('Error fetching projects:', error);
  }
}

// Функция для обновления мультивыбора проектов
function updateMultiSelect(selectedProjects) {
  const dropdown = document.getElementById('projectSelectDropdown');
  const displayText = document.querySelector('.multi-select-text');
  
  if (!dropdown || !displayText) return;
  
  // Фильтруем выбранные проекты, оставляя только те, которые есть в dropdown
  const availableProjects = [];
  dropdown.querySelectorAll('.multi-select-option').forEach(option => {
    const value = option.getAttribute('data-value');
    availableProjects.push(value);
  });
  
  const filteredProjects = selectedProjects.filter(project => 
    project === 'all' || availableProjects.includes(project)
  );
  
  // Если все выбранные проекты исчезли, выбираем "All Projects"
  if (filteredProjects.length === 0 || (filteredProjects.length === 1 && filteredProjects[0] === 'all')) {
    filteredProjects.length = 0;
    filteredProjects.push('all');
  }
  
  // Обновляем чекбоксы
  dropdown.querySelectorAll('.multi-select-option').forEach(option => {
    const value = option.getAttribute('data-value');
    const checkbox = option.querySelector('input[type="checkbox"]');
    if (checkbox) {
      checkbox.checked = filteredProjects.includes(value);
    }
  });
  
  // Обновляем текст отображения
  if (filteredProjects.includes('all') || filteredProjects.length === 0) {
    displayText.textContent = 'All Projects';
  } else if (filteredProjects.length === 1) {
    displayText.textContent = filteredProjects[0];
  } else {
    displayText.textContent = `${filteredProjects.length} Projects`;
  }
  
  // Сохраняем в localStorage
  localStorage.setItem('lb_selected_projects', JSON.stringify(filteredProjects));
}

// Функция для получения выбранных проектов
function getSelectedProjects() {
  const dropdown = document.getElementById('projectSelectDropdown');
  if (!dropdown) return ['all'];
  
  const selectedProjects = [];
  dropdown.querySelectorAll('.multi-select-option input[type="checkbox"]:checked').forEach(checkbox => {
    const option = checkbox.closest('.multi-select-option');
    const value = option.getAttribute('data-value');
    selectedProjects.push(value);
  });
  
  return selectedProjects.length > 0 ? selectedProjects : ['all'];
}

// Функции для работы с исключенными контейнерами
async function loadExcludedContainers() {
  try {
    const token = localStorage.getItem('access_token');
    if (!token) {
      console.error('No access token found');
      return [];
    }
    
    const response = await fetch('/api/containers/excluded', {
      headers: {
        'Authorization': `Bearer ${token}`
      }
    });
    if (!response.ok) {
      if (response.status === 401) {
        console.error('Unauthorized, redirecting to login');
        window.location.href = '/login';
        return [];
      }
      console.error('Ошибка загрузки исключенных контейнеров:', response.status);
      return [];
    }
    const data = await response.json();
    return data.excluded_containers || [];
  } catch (error) {
    console.error('Ошибка загрузки исключенных контейнеров:', error);
    return [];
  }
}

async function saveExcludedContainers(containers) {
  try {
    const token = localStorage.getItem('access_token');
    if (!token) {
      console.error('No access token found');
      return false;
    }
    
    const response = await fetch('/api/containers/excluded', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${token}`
      },
      body: JSON.stringify(containers)
    });
    
    if (!response.ok) {
      if (response.status === 401) {
        console.error('Unauthorized, redirecting to login');
        window.location.href = '/login';
        return false;
      }
      console.error('Ошибка сохранения исключенных контейнеров:', response.status);
      return false;
    }
    
    const data = await response.json();
    console.log('Исключенные контейнеры сохранены:', data);
    return true;
  } catch (error) {
    console.error('Ошибка сохранения исключенных контейнеров:', error);
    return false;
  }
}

function renderExcludedContainers(containers) {
  const list = document.getElementById('excludedContainersList');
  if (!list) return;
  
  list.innerHTML = '';
  
  if (containers.length === 0) {
    list.innerHTML = '<div class="excluded-container-item"><span class="excluded-container-name">Нет исключенных контейнеров</span></div>';
    return;
  }
  
  containers.forEach(container => {
    const item = document.createElement('div');
    item.className = 'excluded-container-item';
    item.innerHTML = `
      <span class="excluded-container-name">${escapeHtml(container)}</span>
      <button class="remove-excluded-btn" onclick="removeExcludedContainer('${escapeHtml(container)}')">×</button>
    `;
    list.appendChild(item);
  });
}

async function addExcludedContainer() {
  const input = document.getElementById('newExcludedContainer');
  const containerName = input.value.trim();
  
  if (!containerName) {
    alert('Введите имя контейнера');
    return;
  }
  
  const currentContainers = await loadExcludedContainers();
  if (currentContainers.includes(containerName)) {
    alert('Контейнер уже в списке исключенных');
    return;
  }
  
  currentContainers.push(containerName);
  const success = await saveExcludedContainers(currentContainers);
  
  if (success) {
    renderExcludedContainers(currentContainers);
    input.value = '';
    // Обновляем список проектов и контейнеров
    await fetchProjects();
    await fetchServices();
  } else {
    alert('Ошибка сохранения');
  }
}

async function removeExcludedContainer(containerName) {
  const currentContainers = await loadExcludedContainers();
  const updatedContainers = currentContainers.filter(name => name !== containerName);
  
  const success = await saveExcludedContainers(updatedContainers);
  
  if (success) {
    renderExcludedContainers(updatedContainers);
    // Обновляем список проектов и контейнеров
    await fetchProjects();
    await fetchServices();
  } else {
    alert('Ошибка удаления');
  }
}

// Функции для мультивыбора контейнеров
function toggleContainerSelection(containerId) {
  console.log('toggleContainerSelection called for:', containerId);
  console.log('Current state.selectedContainers before:', [...state.selectedContainers]);
  
  const index = state.selectedContainers.indexOf(containerId);
  if (index > -1) {
    state.selectedContainers.splice(index, 1);
    console.log('Removed container from selection:', containerId);
  } else {
    state.selectedContainers.push(containerId);
    console.log('Added container to selection:', containerId);
  }
  
  console.log('Current selected containers after:', state.selectedContainers);
  
  updateContainerSelectionUI();
  updateMultiViewMode();
}

function updateContainerSelectionUI() {
  console.log('updateContainerSelectionUI called, selected containers:', state.selectedContainers);
  
  // Обновляем чекбоксы и обычные карточки контейнеров
  const checkboxes = document.querySelectorAll('.container-checkbox');
  console.log('Found checkboxes:', checkboxes.length);
  
  checkboxes.forEach(checkbox => {
    const containerId = checkbox.getAttribute('data-container-id');
    const containerItem = checkbox.closest('.container-item');
    
    console.log('Processing checkbox for container:', containerId, 'checked:', checkbox.checked, 'should be:', state.selectedContainers.includes(containerId));
    
    if (state.selectedContainers.includes(containerId)) {
      checkbox.checked = true;
      containerItem.classList.add('selected');
      console.log('Container selected:', containerId);
    } else {
      checkbox.checked = false;
      containerItem.classList.remove('selected');
      console.log('Container deselected:', containerId);
    }
  });
  
  // Обновляем миникарточки контейнеров
  const miniContainerItems = document.querySelectorAll('.mini-container-item');
  miniContainerItems.forEach(miniItem => {
    const containerId = miniItem.getAttribute('data-cid');
    if (state.selectedContainers.includes(containerId)) {
      miniItem.classList.add('selected');
    } else {
      miniItem.classList.remove('selected');
    }
  });
  
  // Обновляем single-view-title если он существует
  const singleViewTitle = document.getElementById('singleViewTitle');
  if (singleViewTitle && state.selectedContainers.length === 1) {
    const service = state.services.find(s => s.id === state.selectedContainers[0]);
    if (service) {
      singleViewTitle.textContent = `${service.name} (${service.service || service.name})`;
    }
  } else if (singleViewTitle && state.selectedContainers.length === 0) {
    singleViewTitle.textContent = 'No container selected';
  } else if (singleViewTitle && state.selectedContainers.length > 1) {
    singleViewTitle.textContent = `Multi-view: ${state.selectedContainers.length} containers`;
  }
  
  // Если есть сохраненный контейнер, убеждаемся что его чекбокс отмечен
  const savedContainerId = getSelectedContainerFromStorage();
  if (savedContainerId && state.selectedContainers.includes(savedContainerId)) {
    const checkbox = document.querySelector(`.container-checkbox[data-container-id="${savedContainerId}"]`);
    if (checkbox) {
      checkbox.checked = true;
      const containerItem = checkbox.closest('.container-item');
      if (containerItem) {
        containerItem.classList.add('selected');
      }
    }
  }
  
  // Обновляем видимость кнопок LogLevels
  updateLogLevelsVisibility();
}

// Функция для обновления активного состояния контейнеров в UI
function updateActiveContainerUI(activeContainerId) {
  console.log('updateActiveContainerUI called for:', activeContainerId);
  
  // Обновляем обычные карточки контейнеров
  const containerItems = document.querySelectorAll('.container-item');
  containerItems.forEach(item => {
    const containerId = item.getAttribute('data-cid');
    if (activeContainerId && containerId === activeContainerId) {
      item.classList.add('active');
    } else {
      item.classList.remove('active');
    }
  });
  
  // Обновляем миникарточки контейнеров
  const miniContainerItems = document.querySelectorAll('.mini-container-item');
  miniContainerItems.forEach(miniItem => {
    const containerId = miniItem.getAttribute('data-cid');
    if (activeContainerId && containerId === activeContainerId) {
      miniItem.classList.add('active');
    } else {
      miniItem.classList.remove('active');
    }
  });
  
  // Обновляем legacy tabs
  const tabButtons = document.querySelectorAll('.tab');
  tabButtons.forEach(tab => {
    const tabText = tab.textContent;
    const service = state.services.find(s => 
      (s.project ? `[${s.project}] ` : '') + (s.service || s.name) === tabText
    );
    if (service && activeContainerId && service.id === activeContainerId) {
      tab.classList.add('active');
    } else {
      tab.classList.remove('active');
    }
  });
}

// Функции для всплывающих подсказок миникарточек
function showMiniContainerTooltip(event, service) {
  // Удаляем существующую подсказку
  hideMiniContainerTooltip();
  
  // Очищаем все таймеры скрытия
  const existingTooltips = document.querySelectorAll('.mini-container-tooltip');
  existingTooltips.forEach(tooltip => {
    if (tooltip.hideTimer) {
      clearTimeout(tooltip.hideTimer);
    }
  });
  
  // Создаем новую подсказку
  const tooltip = document.createElement('div');
  tooltip.className = 'mini-container-tooltip';
  tooltip.id = 'miniContainerTooltip';
  
  const statusClass = service.status === 'running' ? 'running' : 
                     service.status === 'stopped' ? 'stopped' : 'paused';
  
  // Определяем тип контейнера и создаем соответствующий tooltip
  const isRemote = service.is_remote || service.hostname !== 'localhost';
  const hostname = service.hostname || 'localhost';
  
  let tooltipContent = `
    <div class="mini-container-tooltip-header">
      <i class="fas fa-cube mini-container-tooltip-icon"></i>
      <span>${isRemote ? 'Удаленный контейнер' : 'Локальный контейнер'}</span>
      ${isRemote ? '<i class="fas fa-globe mini-container-tooltip-remote-icon"></i>' : ''}
    </div>
    <div class="mini-container-tooltip-name">${escapeHtml(service.name)}</div>
    <div class="mini-container-tooltip-service">${escapeHtml(service.service || service.name)} • ${escapeHtml(service.project || 'standalone')}</div>
    <div class="mini-container-tooltip-host">Хост: ${escapeHtml(hostname)}</div>
    <div class="mini-container-tooltip-status">
      <span class="mini-container-tooltip-status-indicator ${statusClass}"></span>
      <span>${escapeHtml(service.status)}</span>
    </div>
  `;
  
  // Добавляем порт для работающих контейнеров
  if (service.status === 'running' && service.host_port) {
    tooltipContent += `<div class="mini-container-tooltip-port">Порт: ${escapeHtml(service.host_port)}</div>`;
  }
  
  // Добавляем время последнего обновления для удаленных контейнеров
  if (isRemote && service.last_modified) {
    const updateTime = new Date(service.last_modified * 1000).toLocaleString();
    tooltipContent += `<div class="mini-container-tooltip-update">Обновлено: ${updateTime}</div>`;
  }
  
  // Добавляем ссылки
  if (service.url) {
    tooltipContent += `<a href="${service.url}" target="_blank" class="mini-container-tooltip-url"><i class="fas fa-external-link-alt"></i> Открыть сайт</a>`;
  }
  
  // Добавляем ссылку для удаленных контейнеров
  if (isRemote && service.hostname) {
    tooltipContent += `<a href="ssh://${service.hostname}" class="mini-container-tooltip-ssh"><i class="fas fa-terminal"></i> Подключиться по SSH</a>`;
  }
  
  // Добавляем ссылку для просмотра логов
  tooltipContent += `<div class="mini-container-tooltip-action">Кликните для просмотра логов</div>`;
  
  tooltip.innerHTML = tooltipContent;
  
  // Добавляем подсказку в body
  document.body.appendChild(tooltip);
  
  // Позиционируем подсказку сразу
  positionTooltip(event, tooltip);
  
  // Показываем подсказку сразу
  tooltip.classList.add('show');
  
  // Добавляем обработчики для подсказки
  tooltip.addEventListener('mouseenter', () => {
    // Останавливаем таймер скрытия при наведении на подсказку
    clearTimeout(tooltip.hideTimer);
  });
  
  tooltip.addEventListener('mouseleave', () => {
    // Скрываем подсказку при уходе курсора с неё
    hideMiniContainerTooltip();
  });
  
  // Добавляем обработчик для клика по ссылке
  const link = tooltip.querySelector('.mini-container-tooltip-url');
  if (link) {
    link.addEventListener('click', (e) => {
      e.stopPropagation();
      // Ссылка откроется в новой вкладке благодаря target="_blank"
    });
  }
}

function hideMiniContainerTooltip() {
  const tooltip = document.getElementById('miniContainerTooltip');
  if (tooltip) {
    // Очищаем таймер скрытия
    if (tooltip.hideTimer) {
      clearTimeout(tooltip.hideTimer);
    }
    tooltip.remove();
  }
}

function positionTooltip(event, tooltip) {
  const rect = event.target.getBoundingClientRect();
  const tooltipRect = tooltip.getBoundingClientRect();
  const viewportWidth = window.innerWidth;
  const viewportHeight = window.innerHeight;
  
  // Определяем позицию по умолчанию (справа от миникарточки)
  let position = 'right';
  let left = rect.right + 8;
  
  // Всегда центрируем по высоте относительно миникарточки
  let top = rect.top + (rect.height / 2) - (tooltipRect.height / 2);
  
  // Проверяем, помещается ли подсказка справа
  if (left + tooltipRect.width > viewportWidth - 10) {
    // Не помещается справа, пробуем слева
    position = 'left';
    left = rect.left - tooltipRect.width - 8;
  }
  
  // Проверяем, помещается ли подсказка по вертикали
  if (top < 10) {
    // Не помещается сверху, выравниваем по верху с отступом
    top = 10;
  } else if (top + tooltipRect.height > viewportHeight - 10) {
    // Не помещается снизу, выравниваем по низу с отступом
    top = viewportHeight - tooltipRect.height - 10;
  }
  
  // Применяем позицию сразу, без анимации
  tooltip.className = `mini-container-tooltip ${position}`;
  tooltip.style.left = `${left}px`;
  tooltip.style.top = `${top}px`;
  
  // Принудительно применяем стили без анимации
  tooltip.style.transition = 'none';
  // Сбрасываем transition после применения позиции
  setTimeout(() => {
    tooltip.style.transition = '';
  }, 10);
}



// Функции для help modal
function showHelpTooltip() {
  const helpTooltip = document.getElementById('helpTooltip');
  
  if (!helpTooltip) return;
  
  // Показываем модальное окно
  helpTooltip.classList.add('show');
  
  // Блокируем скролл страницы
  document.body.style.overflow = 'hidden';
}

function hideHelpTooltip() {
  const helpTooltip = document.getElementById('helpTooltip');
  if (helpTooltip) {
    helpTooltip.classList.remove('show');
    
    // Восстанавливаем скролл страницы
    document.body.style.overflow = '';
  }
}

// Глобальные переменные для выбора контейнеров
let lastSelectedContainerId = null;

// Функция для диапазонного выбора контейнеров
function selectContainerRange(startId, endId) {
  console.log('selectContainerRange:', startId, 'to', endId);
  
  const miniContainerItems = document.querySelectorAll('.mini-container-item');
  const containerIds = Array.from(miniContainerItems).map(item => item.getAttribute('data-cid'));
  
  const startIndex = containerIds.indexOf(startId);
  const endIndex = containerIds.indexOf(endId);
  
  if (startIndex === -1 || endIndex === -1) {
    console.error('Container not found in range selection');
    return;
  }
  
  const minIndex = Math.min(startIndex, endIndex);
  const maxIndex = Math.max(startIndex, endIndex);
  
  // Выбираем все контейнеры в диапазоне
  for (let i = minIndex; i <= maxIndex; i++) {
    const containerId = containerIds[i];
    if (!state.selectedContainers.includes(containerId)) {
      state.selectedContainers.push(containerId);
    }
  }
  
  // Обновляем UI
  updateContainerSelectionUI();
  updateMultiViewMode();
}

// Глобальные обработчики для подсказок
document.addEventListener('click', (event) => {
  const tooltip = document.getElementById('miniContainerTooltip');
  if (tooltip && !tooltip.contains(event.target) && !event.target.closest('.mini-container-item')) {
    hideMiniContainerTooltip();
  }
  

});

window.addEventListener('resize', () => {
  hideMiniContainerTooltip();
  hideHelpTooltip();
});

window.addEventListener('scroll', () => {
  hideMiniContainerTooltip();
  hideHelpTooltip();
});

// Обработчик для скрытия подсказки при клике на ссылку
document.addEventListener('click', (event) => {
  if (event.target.closest('.mini-container-tooltip-url')) {
    // Не скрываем подсказку при клике на ссылку
    event.stopPropagation();
  }
});

// Функция для сохранения выбранного контейнера в localStorage
function saveSelectedContainer(containerId) {
  if (containerId) {
    localStorage.setItem('lb_selected_container', containerId);
    console.log('Saved selected container to localStorage:', containerId);
  } else {
    localStorage.removeItem('lb_selected_container');
    console.log('Removed selected container from localStorage');
  }
}

// Функция для восстановления выбранного контейнера из localStorage
function getSelectedContainerFromStorage() {
  const containerId = localStorage.getItem('lb_selected_container');
  console.log('Retrieved selected container from localStorage:', containerId);
  return containerId;
}

// Функция для сохранения режима просмотра в localStorage
function saveViewMode(multiViewMode, selectedContainers) {
  const viewModeData = {
    multiViewMode: multiViewMode,
    selectedContainers: selectedContainers || []
  };
  localStorage.setItem('lb_view_mode', JSON.stringify(viewModeData));
  console.log('Saved view mode to localStorage:', viewModeData);
}

// Функция для восстановления режима просмотра из localStorage
function getViewModeFromStorage() {
  const viewModeData = localStorage.getItem('lb_view_mode');
  if (viewModeData) {
    try {
      const data = JSON.parse(viewModeData);
      console.log('Retrieved view mode from localStorage:', data);
      return data;
    } catch (error) {
      console.error('Error parsing view mode from localStorage:', error);
      return null;
    }
  }
  return null;
}

// Функция для сохранения состояния кнопок loglevels в localStorage
function saveLogLevelsState() {
  const levelsData = {
    globalLevels: state.levels,
    containerLevels: state.containerLevels
  };
  localStorage.setItem('lb_log_levels', JSON.stringify(levelsData));
  console.log('Saved log levels state to localStorage:', levelsData);
}

// Функция для восстановления состояния кнопок loglevels из localStorage
function getLogLevelsStateFromStorage() {
  const levelsData = localStorage.getItem('lb_log_levels');
  if (levelsData) {
    try {
      const data = JSON.parse(levelsData);
      console.log('Retrieved log levels state from localStorage:', data);
      return data;
    } catch (error) {
      console.error('Error parsing log levels state from localStorage:', error);
      return null;
    }
  }
  return null;
}

async function updateMultiViewMode() {
  console.log(`updateMultiViewMode called: selectedContainers.length = ${state.selectedContainers.length}, containers:`, state.selectedContainers);
  
  if (state.selectedContainers.length > 1) {
    state.multiViewMode = true;
    state.current = null; // Сбрасываем текущий контейнер
    console.log('Setting up multi-view mode');
    
    // Сохраняем режим просмотра в localStorage
    saveViewMode(true, state.selectedContainers);
    
    await setupMultiView();
  } else if (state.selectedContainers.length === 1) {
    // Переключаемся в single view для одного контейнера
    console.log('Switching from multi-view to single view');
    state.multiViewMode = false;
    const selectedService = state.services.find(s => s.id === state.selectedContainers[0]);
    if (selectedService) {
      console.log('Switching to single view for:', selectedService.name);
      console.log('updateMultiViewMode: About to call switchToSingle - VERSION 2');
      
      // Сохраняем режим просмотра в localStorage
      saveViewMode(false, [selectedService.id]);
      
      // Сохраняем выбранный контейнер в localStorage
      saveSelectedContainer(selectedService.id);
      
      // Обновляем страницу для полного сброса состояния
      console.log('Refreshing page to switch to single view');
      window.location.reload();
      return; // Прерываем выполнение, так как страница перезагрузится
    }
  } else {
    // Когда снимаем все галочки, переключаемся в single view
    state.multiViewMode = false;
    state.current = null;
    
    // Сохраняем режим просмотра в localStorage
    saveViewMode(false, []);
    
    clearLogArea();
    
    // Очищаем область логов и показываем пустое состояние
    const logArea = document.querySelector('.log-area');
    if (logArea) {
      const logContent = logArea.querySelector('.log-content');
      if (logContent) {
        logContent.innerHTML = '<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: var(--muted); font-size: 14px;">Выберите контейнер для просмотра логов</div>';
      }
    }
    
    // Очищаем активное состояние всех контейнеров
    updateActiveContainerUI(null);
  }
  
  console.log(`Multi-view mode updated: multiViewMode = ${state.multiViewMode}`);
  
  // Сохраняем состояние кнопок loglevels при переключении режимов
  saveLogLevelsState();
  
  // Обновляем состояние кнопок уровней логирования при переключении режимов
  setTimeout(() => {
    initializeLevelButtons();
  }, 100);
  
  // Обновляем видимость кнопок LogLevels
  updateLogLevelsVisibility();
}

/**
 * Настраивает интерфейс для режима мультипросмотра (multi-view)
 * Создает сетку панелей для одновременного просмотра нескольких контейнеров
 * Открывает WebSocket соединения для всех выбранных контейнеров
 */
async function setupMultiView() {
  console.log('setupMultiView called');
  
  // Проверяем, что у нас действительно больше одного контейнера
  if (state.selectedContainers.length <= 1) {
    console.log('setupMultiView: Not enough containers for multi-view, switching to single view');
    if (state.selectedContainers.length === 1) {
      const selectedService = state.services.find(s => s.id === state.selectedContainers[0]);
          if (selectedService) {
      console.log('setupMultiView: Calling switchToSingle for:', selectedService.name);
      await switchToSingle(selectedService);
    }
    } else {
      console.log('setupMultiView: No containers selected, clearing log area');
      clearLogArea();
    }
    return;
  }
  
  // Дополнительная проверка - если уже есть мультипросмотр, удаляем его для пересоздания
  const existingMultiView = document.getElementById('multiViewGrid');
  if (existingMultiView) {
    console.log('setupMultiView: Multi-view already exists, removing for recreation');
    existingMultiView.remove();
  }
  
  const logArea = document.querySelector('.log-area');
  if (!logArea) {
    console.log('Log area not found');
    return;
  }
  
  // Очищаем область логов
  const logContent = logArea.querySelector('.log-content');
  if (logContent) {
    logContent.innerHTML = '';
  }
  
  // Удаляем single-view-panel если он существует
  const singleViewPanel = document.getElementById('singleViewPanel');
  if (singleViewPanel) {
    singleViewPanel.remove();
  }
  
  // Создаем сетку для мультипросмотра
  const gridContainer = document.createElement('div');
  gridContainer.className = 'multi-view-grid';
  gridContainer.id = 'multiViewGrid';
  
  // Определяем количество колонок в зависимости от количества контейнеров
  let columns = 1;
  if (state.selectedContainers.length === 1) columns = 1;
  else if (state.selectedContainers.length === 2) columns = 2;
  else if (state.selectedContainers.length <= 4) columns = 2;
  else if (state.selectedContainers.length <= 6) columns = 3;
  else columns = 4;
  
  console.log(`setupMultiView: Creating grid with ${columns} columns for ${state.selectedContainers.length} containers`);
  gridContainer.style.gridTemplateColumns = `repeat(${columns}, 1fr)`;
  console.log(`setupMultiView: Grid template columns set to: repeat(${columns}, 1fr)`);
  
  // Создаем панели для каждого выбранного контейнера
  console.log(`setupMultiView: Creating panels for ${state.selectedContainers.length} containers:`, state.selectedContainers);
  state.selectedContainers.forEach((containerId, index) => {
    const service = state.services.find(s => s.id === containerId);
    if (!service) {
      console.error(`setupMultiView: Service not found for container ID: ${containerId}`);
      return;
    }
    
    console.log(`setupMultiView: Creating panel ${index + 1} for service: ${service.name} (${containerId})`);
    const panel = createMultiViewPanel(service);
    gridContainer.appendChild(panel);
    console.log(`setupMultiView: Panel ${index + 1} added to grid, total children: ${gridContainer.children.length}`);
  });
  
  if (logContent) {
    logContent.appendChild(gridContainer);
    console.log(`setupMultiView: Grid added to log content, grid children: ${gridContainer.children.length}`);
    
    // Проверяем, что все панели созданы правильно
    const panels = gridContainer.querySelectorAll('.multi-view-panel');
    console.log(`setupMultiView: Total panels found in grid: ${panels.length}`);
    panels.forEach((panel, index) => {
      const containerId = panel.getAttribute('data-container-id');
      const title = panel.querySelector('.multi-view-title');
      console.log(`setupMultiView: Panel ${index + 1}: containerId=${containerId}, title="${title?.textContent}"`);
    });
  } else {
    console.error('setupMultiView: logContent not found');
  }
  
  // Применяем настройки wrap lines
  applyWrapSettings();
  
  // Очищаем активное состояние всех контейнеров в мультипросмотре
  updateActiveContainerUI(null);
  
  // Принудительно обновляем стили логов для multi-view
  setTimeout(() => {
    updateLogStyles();
    
    // Дополнительная проверка для multi-view логов
    console.log('setupMultiView: Force fixing multi-view styles');
    forceFixMultiViewStyles();
  }, 200);
  
  // Подключаем WebSocket для каждого контейнера
  console.log(`setupMultiView: Setting up WebSockets for ${state.selectedContainers.length} containers`);
  state.selectedContainers.forEach((containerId, index) => {
    const service = state.services.find(s => s.id === containerId);
    if (service) {
      console.log(`setupMultiView: Setting up WebSocket ${index + 1} for multi-view container: ${service.name} (${containerId})`);
      openMultiViewWs(service);
    } else {
      console.error(`setupMultiView: Service not found for container ID: ${containerId}`);
    }
  });
  
  console.log(`setupMultiView: Multi-view setup completed for ${state.selectedContainers.length} containers`);
  
  // Обновляем счетчики для multi view
  setTimeout(() => {
    recalculateMultiViewCounters();
  }, 1000); // Небольшая задержка для завершения загрузки логов
  
  // Применяем стили логов после настройки multi view
  setTimeout(() => {
    updateLogStyles();
  }, 1500); // Задержка после настройки счетчиков
  
  // Обновляем видимость кнопок LogLevels
  updateLogLevelsVisibility();
}

/**
 * Создает панель для мультипросмотра контейнера
 * Генерирует HTML структуру с заголовком, кнопками уровней и областью логов
 * @param {Object} service - Объект сервиса/контейнера
 * @returns {HTMLElement} Созданная панель мультипросмотра
 */
function createMultiViewPanel(service) {
  console.log(`Creating multi-view panel for service: ${service.name} (${service.id})`);
  const panel = document.createElement('div');
  panel.className = 'multi-view-panel';
  panel.setAttribute('data-container-id', service.id);
  console.log(`createMultiViewPanel: Panel element created with data-container-id: ${service.id}`);
  
  panel.innerHTML = `
    <div class="multi-view-header">
      <h4 class="multi-view-title">${escapeHtml(service.name)}</h4>
      <div class="multi-view-levels">
        <button class="level-btn debug-btn" data-level="debug" data-container-id="${service.id}" title="DEBUG">
          <span class="level-label">DEBUG</span>
          <span class="level-value" data-container="${service.id}">0</span>
        </button>
        <button class="level-btn info-btn" data-level="info" data-container-id="${service.id}" title="INFO">
          <span class="level-label">INFO</span>
          <span class="level-value" data-container="${service.id}">0</span>
        </button>
        <button class="level-btn warn-btn" data-level="warn" data-container-id="${service.id}" title="WARN">
          <span class="level-label">WARN</span>
          <span class="level-value" data-container="${service.id}">0</span>
        </button>
        <button class="level-btn error-btn" data-level="err" data-container-id="${service.id}" title="ERROR">
          <span class="level-label">ERROR</span>
          <span class="level-value" data-container="${service.id}">0</span>
        </button>
        <button class="level-btn other-btn" data-level="other" data-container-id="${service.id}" title="OTHER">
          <span class="level-label">OTHER</span>
          <span class="level-value" data-container="${service.id}">0</span>
        </button>
      </div>
    </div>
    <div class="multi-view-content">
      <div class="multi-view-log" data-container-id="${service.id}"></div>
    </div>
  `;
  
  // Проверяем, что элемент создался правильно
  const logElement = panel.querySelector(`.multi-view-log[data-container-id="${service.id}"]`);
  if (logElement) {
    console.log(`Multi-view log element created successfully for ${service.name}`);
    // Очищаем пустые строки после создания панели
    cleanMultiViewEmptyLines(logElement);
    // Очищаем дублированные строки после создания панели
    cleanMultiViewDuplicateLines(logElement);
  } else {
    console.error(`Failed to create multi-view log element for ${service.name}`);
  }
  
  // Инициализируем состояние кнопок уровней логирования для этого контейнера
  setTimeout(() => {
    const levelBtns = panel.querySelectorAll('.level-btn');
    levelBtns.forEach(btn => {
      const level = btn.getAttribute('data-level');
      const containerId = btn.getAttribute('data-container-id');
      
      // Инициализируем настройки контейнера, если их нет
      if (containerId && (!state.containerLevels || !state.containerLevels[containerId])) {
        if (!state.containerLevels) {
          state.containerLevels = {};
        }
        state.containerLevels[containerId] = {debug: true, info: true, warn: true, err: true, other: true};
      }
      
      // Используем настройки контейнера
      const isActive = state.containerLevels && state.containerLevels[containerId] ? 
        state.containerLevels[containerId][level] : true;
      
      btn.classList.toggle('active', isActive);
      btn.classList.toggle('disabled', !isActive);
    });
  }, 100);
  
  console.log(`Multi-view panel created for ${service.name}`);
  
  // Применяем стили к новой панели
  setTimeout(() => {
    updateLogStyles();
  }, 200);
  
  return panel;
}

/**
 * Открывает WebSocket соединение для контейнера в режиме мультипросмотра
 * Настраивает обработчики сообщений и управляет отображением логов
 * @param {Object} service - Объект сервиса/контейнера
 */
function openMultiViewWs(service) {
  const containerId = service.id;
  console.log(`openMultiViewWs: Starting WebSocket setup for ${service.name} (${containerId})`);
  console.log(`openMultiViewWs: Current multiViewMode: ${state.multiViewMode}`);
  console.log(`openMultiViewWs: Selected containers: ${state.selectedContainers.join(', ')}`);
  
  // Закрываем существующее соединение
  closeWs(containerId);
  
  // Создаем новое WebSocket соединение
  const ws = new WebSocket(wsUrl(containerId, service.service, service.project));
  
  ws.onopen = () => {
    console.log(`Multi-view WebSocket connected for ${service.name}`);
    const logEl = document.querySelector(`.multi-view-log[data-container-id="${containerId}"]`);
    if (logEl) {
      // Убираем сообщение "Connected..." для MultiView режима
      logEl.textContent = '';
      // Очищаем пустые строки после установки соединения
      setTimeout(() => {
        cleanMultiViewEmptyLines(logEl);
        cleanMultiViewDuplicateLines(logEl);
      }, 100);
    }
  };
  
  ws.onmessage = (event) => {
    console.log(`Multi-view WebSocket received message for ${service.name}: ${event.data.substring(0, 100)}...`);
    
    // Устанавливаем состояние 'on' при получении сообщений
    setWsState('on');
    
    const parts = (event.data||'').split(/\r?\n/);
    
    // Проверяем на дублирование в исходных данных
    if (event.data.includes('FoundINFO:')) {
      console.log('🚨 WebSocket: ОБНАРУЖЕНЫ данные с FoundINFO:');
      console.log('🚨 WebSocket: Полные данные:', event.data);
    }
    
    // Проверяем на дублирование строк и убираем дубликаты
    const lines = event.data.split(/\r?\n/).filter(line => line.trim().length > 0);
    const uniqueLines = [...new Set(lines)];
    if (lines.length !== uniqueLines.length) {
      console.log('🚨 WebSocket: ОБНАРУЖЕНО ДУБЛИРОВАНИЕ строк!');
      console.log('🚨 WebSocket: Всего строк:', lines.length);
      console.log('🚨 WebSocket: Уникальных строк:', uniqueLines.length);
      console.log('🚨 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('🚨 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(); } }
        handleLine(containerId, uniqueParts[i]);
      }
    } else {
      // Если дублирования нет, обрабатываем как обычно
      for (let i=0;i<parts.length;i++){
        if (parts[i].length===0 && i===parts.length-1) continue;
        
        // Проверяем каждую часть на FoundINFO:
        if (parts[i].includes('FoundINFO:')) {
          console.log('🚨 WebSocket: Часть с FoundINFO:', parts[i]);
        }
        
        // harvest instance ids if present
        const pr = parsePrefixAndStrip(parts[i]);
        if (pr){ if (!(pr.id8 in inst.filters)) { inst.filters[pr.id8] = true; updateIdFiltersBar(); } }
        handleLine(containerId, parts[i]);
      }
    }
  };
  
  ws.onclose = () => {
    console.log(`Multi-view WebSocket closed for ${service.name}`);
  };
  
  ws.onerror = (error) => {
    console.error(`Multi-view WebSocket error for ${service.name}:`, error);
  };
  
  // Сохраняем соединение с полным набором полей как в openWs
  const logEl = document.querySelector(`.multi-view-log[data-container-id="${containerId}"]`);
  console.log(`openMultiViewWs: Found log element for ${service.name}:`, !!logEl);
  
  state.open[containerId] = {
    ws: ws,
    serviceName: service.service,
    logEl: logEl,
    wrapEl: logEl,
    counters: {dbg:0, info:0, warn:0, err:0, other:0},
    pausedBuffer: [],
    allLogs: [] // Добавляем буфер для логов
  };
  
  console.log(`openMultiViewWs: WebSocket setup completed for ${service.name} (${containerId})`);
}

function clearLogArea() {
  console.log('clearLogArea called');
  
  // Очищаем мультипросмотр если он был активен
  if (state.multiViewMode) {
    console.log('Clearing multi-view grid');
    const multiViewGrid = document.getElementById('multiViewGrid');
    if (multiViewGrid) {
      multiViewGrid.remove();
    }
  }
  
  const logContent = document.querySelector('.log-content');
  if (logContent) {
    logContent.innerHTML = `
      <div class="single-view-panel" id="singleViewPanel">
        <div class="single-view-header">
          <h4 class="single-view-title" id="singleViewTitle">No container selected</h4>
        </div>
        <div class="single-view-content">
          <pre class="log" id="logContent">No container selected</pre>
        </div>
      </div>
    `;
  }
  
  const logTitle = document.getElementById('logTitle');
  if (logTitle) {
    logTitle.textContent = 'LogBoard+';
  }
  
  // Обновляем видимость кнопок LogLevels
  updateLogLevelsVisibility();
}



function applyWrapSettings() {
  const wrapEnabled = els.wrapToggle && els.wrapToggle.checked;
  const wrapStyle = wrapEnabled ? 'pre-wrap' : 'pre';
  
  // Применяем к обычному просмотру (только к логам в основном контенте)
  document.querySelectorAll('.main-content .log').forEach(el => {
    el.style.whiteSpace = wrapStyle;
  });
  
  // Применяем к мультипросмотру
  document.querySelectorAll('.multi-view-content .multi-view-log').forEach(el => {
    el.style.whiteSpace = wrapStyle;
  });
  
  // Применяем к single-view
  document.querySelectorAll('.single-view-content .log').forEach(el => {
    el.style.whiteSpace = wrapStyle;
  });
}

async function fetchServices(){
  try {
    console.log('Fetching services...');
    const url = new URL(location.origin + '/api/containers/services');
    const selectedProjects = getSelectedProjects();
    
    // Если выбраны конкретные проекты (не "all"), добавляем их в URL как строку через запятую
    if (selectedProjects.length > 0 && !selectedProjects.includes('all')) {
      url.searchParams.set('projects', selectedProjects.join(','));
    }
    
    const token = localStorage.getItem('access_token');
    if (!token) {
      console.error('No access token found');
      window.location.href = '/login';
      return;
    }
    
    const res = await fetch(url, {
      headers: {
        'Authorization': `Bearer ${token}`
      }
    });
    if (!res.ok){ 
      if (res.status === 401) {
        console.error('Unauthorized, redirecting to login');
        window.location.href = '/login';
        return;
      }
      console.error('Auth failed (HTTP):', res.status, res.statusText);
      alert('Auth failed (HTTP)'); 
      return; 
    }
    const data = await res.json();
    console.log('Services loaded:', data);
    
    // Фильтруем остановленные контейнеры
    const filteredData = filterStoppedContainers(data);
    console.log('Filtered services (running only):', filteredData);
    state.services = filteredData;
    
    buildTabs();
    
    // Восстанавливаем режим просмотра из localStorage
    const savedViewMode = getViewModeFromStorage();
    if (savedViewMode) {
      console.log('Restoring view mode from localStorage:', savedViewMode);
      
      if (savedViewMode.multiViewMode && savedViewMode.selectedContainers.length > 1) {
        // Восстанавливаем Multi View режим
        console.log('Restoring Multi View mode with containers:', savedViewMode.selectedContainers);
        state.multiViewMode = true;
        state.selectedContainers = savedViewMode.selectedContainers;
        
        // Отмечаем чекбоксы для выбранных контейнеров
        savedViewMode.selectedContainers.forEach(containerId => {
          const checkbox = document.querySelector(`.container-checkbox[data-container-id="${containerId}"]`);
          if (checkbox) {
            checkbox.checked = true;
            const containerItem = checkbox.closest('.container-item');
            if (containerItem) {
              containerItem.classList.add('selected');
            }
          }
        });
        
        // Настраиваем Multi View
        await setupMultiView();
      } else if (savedViewMode.selectedContainers.length === 1) {
        // Восстанавливаем Single View режим
        console.log('Restoring Single View mode for container:', savedViewMode.selectedContainers[0]);
        state.multiViewMode = false;
        const selectedService = state.services.find(s => s.id === savedViewMode.selectedContainers[0]);
        if (selectedService) {
          await switchToSingle(selectedService);
        }
      } else {
        // Нет сохраненного режима, не открываем автоматически первый контейнер
        console.log('No saved view mode, not auto-opening first container');
        // Пользователь сам выберет нужный контейнер
      }
    } else {
      // Нет сохраненного режима, не открываем автоматически первый контейнер
      console.log('No saved view mode found, not auto-opening first container');
      // Пользователь сам выберет нужный контейнер
    }
    
    // Добавляем обработчики для счетчиков после загрузки сервисов
    addCounterClickHandlers();
  } catch (error) {
    console.error('Error fetching services:', error);
  }
}

function wsUrl(containerId, service, project){
  const proto = location.protocol === 'https:' ? 'wss' : 'ws';
  const tail = els.tail.value || '500';
  const token = encodeURIComponent(localStorage.getItem('access_token') || '');
  const sp = service?`&service=${encodeURIComponent(service)}`:'';
  const pj = project?`&project=${encodeURIComponent(project)}`:'';
  if (els.aggregate && els.aggregate.checked && service){
    // fan-in by service
    return `${proto}://${location.host}/api/websocket/fan/${encodeURIComponent(service)}?tail=${tail}&token=${token}${pj}`;
  }
  return `${proto}://${location.host}/api/websocket/logs/${encodeURIComponent(containerId)}?tail=${tail}&token=${token}${sp}${pj}`;
}

/**
 * Закрывает WebSocket соединение для контейнера
 * @param {string} id - ID контейнера
 */
function closeWs(id){
  const o = state.open[id];
  if (!o) {
    console.log(`closeWs: No connection found for container ${id}`);
    return;
  }
  
  console.log(`closeWs: Closing WebSocket connection for container ${id}`);
  
  try { 
    if (o.ws && o.ws.readyState !== WebSocket.CLOSED) {
      o.ws.close();
      console.log(`closeWs: WebSocket connection closed for container ${id}`);
    }
  } catch(e){
    console.error(`closeWs: Error closing WebSocket for container ${id}:`, e);
  }
  
  delete state.open[id];
  console.log(`closeWs: Connection state removed for container ${id}`);
}

/**
 * Создает и скачивает снимок логов контейнера
 * В режиме мультипросмотра создает отдельные файлы для каждого контейнера
 * @param {string} id - ID контейнера
 */
async function sendSnapshot(id){
  const o = state.open[id];
  if (!o){ alert('not open'); return; }
  
  const token = localStorage.getItem('access_token');
  if (!token) {
    console.error('No access token found');
    window.location.href = '/login';
    return;
  }
  
  if (state.multiViewMode && state.selectedContainers.length > 0) {
    // В Multi View режиме создаем отдельный файл для каждого контейнера
    console.log('Creating snapshots for Multi View mode with containers:', state.selectedContainers);
    
    let hasLogs = false;
    
    // Создаем отдельный файл для каждого выбранного контейнера
    for (const containerId of state.selectedContainers) {
      const multiViewLog = document.querySelector(`.multi-view-log[data-container-id="${containerId}"]`);
      if (multiViewLog && multiViewLog.textContent.trim()) {
        const service = state.services.find(s => s.id === containerId);
        const serviceName = service ? (service.service || service.name) : containerId;
        const text = multiViewLog.textContent;
        
        console.log(`Saving snapshot for ${serviceName} with content length:`, text.length);
        
        const payload = {container_id: containerId, service: serviceName, content: text};
        
        try {
          const res = await fetch('/api/logs/snapshot', {
            method:'POST', 
            headers:{
              'Content-Type':'application/json',
              'Authorization': `Bearer ${token}`
            }, 
            body: JSON.stringify(payload)
          });
          
          if (!res.ok){ 
            if (res.status === 401) {
              console.error('Unauthorized, redirecting to login');
              window.location.href = '/login';
              return;
            }
            console.error(`Snapshot failed for ${serviceName}:`, res.status, res.statusText);
            alert(`snapshot failed for ${serviceName}`); 
            return; 
          }
          
          const js = await res.json();
          const a = document.createElement('a');
          a.href = js.url; a.download = js.file; a.click();
          
          hasLogs = true;
          
          // Небольшая задержка между скачиваниями файлов
          await new Promise(resolve => setTimeout(resolve, 100));
          
        } catch (error) {
          console.error(`Error saving snapshot for ${serviceName}:`, error);
          alert(`Error saving snapshot for ${serviceName}`);
          return;
        }
      }
    }
    
    if (!hasLogs) {
      alert('No logs to save in Multi View mode');
      return;
    }
    
  } else {
    // Обычный режим просмотра
    let text = '';
    if (state.current && state.current.id === id && els.logContent) {
      text = els.logContent.textContent;
    } else if (o.logEl) {
      text = o.logEl.textContent;
    }
    
    if (!text || text.trim() === '') {
      alert('No logs to save');
      return;
    }
    
    console.log('Saving snapshot with content length:', text.length);
    
    const serviceName = o.serviceName || id;
    const payload = {container_id: id, service: serviceName, content: text};
    
    const res = await fetch('/api/logs/snapshot', {
      method:'POST', 
      headers:{
        'Content-Type':'application/json',
        'Authorization': `Bearer ${token}`
      }, 
      body: JSON.stringify(payload)
    });
    if (!res.ok){ 
      if (res.status === 401) {
        console.error('Unauthorized, redirecting to login');
        window.location.href = '/login';
        return;
      }
      console.error('Snapshot failed:', res.status, res.statusText);
      alert('snapshot failed'); 
      return; 
    }
    const js = await res.json();
    const a = document.createElement('a');
    a.href = js.url; a.download = js.file; a.click();
  }
}

/**
 * Открывает WebSocket соединение для контейнера
 * Настраивает обработчики событий и управляет отображением логов
 * @param {Object} svc - Объект сервиса/контейнера
 * @param {HTMLElement} panel - Панель для отображения логов
 */
function openWs(svc, panel){
  const id = svc.id;
  console.log(`openWs: Called for ${svc.name} (${id}) in multiViewMode: ${state.multiViewMode}`);
  console.log(`openWs: Selected containers: ${state.selectedContainers.join(', ')}`);
  
  // Проверяем, есть ли уже открытое соединение для этого контейнера
  if (state.open[id]) {
    console.log(`openWs: Connection already exists for container ${id}, closing it first`);
    closeWs(id);
    // Добавляем небольшую задержку для корректного закрытия
    setTimeout(() => {
      openWs(svc, panel);
    }, 50);
    return;
  }
  
  const logEl = panel.querySelector('.log');
  const wrapEl = panel.querySelector('.logwrap');
  
  // Ищем счетчики в panel или в глобальных элементах
  const cdbg  = panel.querySelector('.cdbg') || document.querySelector('.cdbg');
  const cinfo = panel.querySelector('.cinfo') || document.querySelector('.cinfo');
  const cwarn = panel.querySelector('.cwarn') || document.querySelector('.cwarn');
  const cerr  = panel.querySelector('.cerr') || document.querySelector('.cerr');
  const cother = panel.querySelector('.cother') || document.querySelector('.cother');
  const counters = {dbg:0,info:0,warn:0,err:0,other:0};

  const ws = new WebSocket(wsUrl(id, (svc.service||svc.name), svc.project||''));
  state.open[id] = {ws, logEl, wrapEl, counters, pausedBuffer:[], serviceName: (svc.service||svc.name)};
  console.log(`openWs: Created state.open[${id}] with logEl:`, !!logEl, 'wrapEl:', !!wrapEl);

  ws.onopen = ()=> {
    console.log(`WebSocket ${id}: Соединение открыто`);
    setWsState('on');
    // Очищаем сообщение "Connecting..." когда соединение установлено
    if (state.current && state.current.id === id && els.logContent) {
      els.logContent.innerHTML = '';
    }
    // Также очищаем legacy элемент лога
    const obj = state.open[id];
    if (obj && obj.logEl) {
      obj.logEl.innerHTML = '';
    }
    
    // Принудительно проверяем состояние через AJAX через 500мс и 1 секунду
    setTimeout(() => {
      console.log(`WebSocket ${id}: Принудительная проверка состояния после открытия (500мс)`);
      checkWebSocketStatus();
    }, 500);
    
    setTimeout(() => {
      console.log(`WebSocket ${id}: Принудительная проверка состояния после открытия (1с)`);
      checkWebSocketStatus();
    }, 1000);
  };
  ws.onclose = ()=> {
    console.log(`WebSocket ${id}: Соединение закрыто`);
    setWsState(determineWsState());
    
    // Принудительно проверяем состояние через AJAX через 500мс
    setTimeout(() => {
      console.log(`WebSocket ${id}: Принудительная проверка состояния после закрытия`);
      checkWebSocketStatus();
    }, 500);
  };
  ws.onerror = (error)=> {
    console.log(`WebSocket ${id}: Ошибка соединения:`, error);
    setWsState('err');
  };
  ws.onmessage = (ev)=>{
    console.log(`Received WebSocket message: ${ev.data.substring(0, 100)}...`);
    
    // Устанавливаем состояние 'on' при получении сообщений
    setWsState('on');
    
    const parts = (ev.data||'').split(/\r?\n/);
    console.log(`openWs: Processing ${parts.length} lines for container ${id}`);
    
    // Проверяем на дублирование в исходных данных для Single View
    if (ev.data.includes('FoundINFO:')) {
      console.log('🚨 Single View WebSocket: ОБНАРУЖЕНЫ данные с FoundINFO:');
      console.log('🚨 Single View WebSocket: Полные данные:', ev.data);
    }
    
    // Обрабатываем строки логов
    for (let i=0;i<parts.length;i++){
      if (parts[i].length===0 && i===parts.length-1) continue;
      
      // harvest instance ids if present
      const pr = parsePrefixAndStrip(parts[i]);
      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)}..."`);
      handleLine(id, parts[i]);
    }
    
    // Обновляем счетчики после обработки всех строк
    cdbg.textContent  = counters.dbg;
    cinfo.textContent = counters.info;
    cwarn.textContent = counters.warn;
    cerr.textContent  = counters.err;
    if (cother) cother.textContent = counters.other;
  };

  // Убираем автоматический refresh - теперь только по кнопке
}

/**
 * Функция для обработки переноса строк в multi view
 * Если символов больше 5, то перенос строк работает
 * Если меньше 5, то переноса строк нет
 * @param {string} text - исходный текст
 * @returns {string} - обработанный текст с правильными переносами
 */
function processMultiViewLineBreaks(text) {
  // Если символов меньше или равно 5, возвращаем без переносов
  if (text.length <= 5) {
    return text;
  }
  
  // Если символов больше 5, добавляем перенос строки в конце
  return text + '\n';
}

/**
 * Функция для радикальной очистки пустых строк в multi view
 * Удаляет все пустые строки и лишние переносы строк
 * @param {HTMLElement} multiViewLog - элемент лога multi view
 */
function cleanMultiViewEmptyLines(multiViewLog) {
  if (!multiViewLog) return;
  
  let removedCount = 0;
  
  // Удаляем все пустые строки (элементы .line без текста)
  const lines = Array.from(multiViewLog.querySelectorAll('.line'));
  lines.forEach(line => {
    const textContent = line.textContent || line.innerText || '';
    if (textContent.trim() === '') {
      line.remove();
      removedCount++;
    }
  });
  
  // Удаляем все текстовые узлы, которые содержат только пробелы и переносы строк
  const walker = document.createTreeWalker(
    multiViewLog,
    NodeFilter.SHOW_TEXT,
    null,
    false
  );
  
  const textNodesToRemove = [];
  let node;
  while (node = walker.nextNode()) {
    const content = node.textContent;
    // Удаляем все узлы, которые содержат только пробелы, переносы строк или табуляцию
    if (content.trim() === '') {
      textNodesToRemove.push(node);
    }
  }
  
  textNodesToRemove.forEach(node => node.remove());
  
  // Удаляем все пустые текстовые узлы между элементами .line
  const allNodes = Array.from(multiViewLog.childNodes);
  for (let i = allNodes.length - 1; i >= 0; i--) {
    const node = allNodes[i];
    if (node.nodeType === Node.TEXT_NODE && node.textContent.trim() === '') {
      node.remove();
    }
  }
  
  if (removedCount > 0) {
    console.log(`cleanMultiViewEmptyLines: Удалено ${removedCount} пустых строк`);
  }
}

/**
 * Функция для очистки дублированных строк в multi view
 * Удаляет последовательные дублированные строки
 * @param {HTMLElement} multiViewLog - элемент лога multi view
 */
function cleanMultiViewDuplicateLines(multiViewLog) {
  if (!multiViewLog) return;
  
  const lines = Array.from(multiViewLog.querySelectorAll('.line'));
  let removedCount = 0;
  
  // Проходим по строкам с конца, чтобы не нарушить индексы
  for (let i = lines.length - 1; i > 0; i--) {
    const currentLine = lines[i];
    const previousLine = lines[i - 1];
    
    if (currentLine && previousLine) {
      const currentText = currentLine.textContent || currentLine.innerText || '';
      const previousText = previousLine.textContent || previousLine.innerText || '';
      
      // Удаляем дублированные строки (включая пустые)
      if (currentText.trim() === previousText.trim()) {
        console.log(`cleanMultiViewDuplicateLines: Удаляем дублированную строку: ${currentText.substring(0, 50)}...`);
        currentLine.remove();
        removedCount++;
      }
    }
  }
  
  // После удаления дубликатов очищаем лишние пустые строки
  cleanMultiViewEmptyLines(multiViewLog);
  
  if (removedCount > 0) {
    console.log(`cleanMultiViewDuplicateLines: Удалено ${removedCount} дублированных строк`);
  }
}

/**
 * Универсальная функция для очистки дублированных строк
 * Работает как для Single View, так и для MultiView
 * @param {HTMLElement} logElement - элемент лога (любого типа)
 */
function cleanDuplicateLines(logElement) {
  if (!logElement) return;
  
  const lines = Array.from(logElement.querySelectorAll('.line'));
  let removedCount = 0;
  
  // Проходим по строкам с конца, чтобы не нарушить индексы
  for (let i = lines.length - 1; i > 0; i--) {
    const currentLine = lines[i];
    const previousLine = lines[i - 1];
    
    if (currentLine && previousLine) {
      const currentText = currentLine.textContent || currentLine.innerText || '';
      const previousText = previousLine.textContent || previousLine.innerText || '';
      
      // Удаляем дублированные строки (включая пустые)
      if (currentText.trim() === previousText.trim()) {
        console.log(`cleanDuplicateLines: Удаляем дублированную строку: ${currentText.substring(0, 50)}...`);
        currentLine.remove();
        removedCount++;
      }
    }
  }
  
  // После удаления дубликатов очищаем лишние пустые строки
  if (logElement.classList.contains('multi-view-log')) {
    cleanMultiViewEmptyLines(logElement);
  } else {
    cleanSingleViewEmptyLines(logElement);
  }
  
  if (removedCount > 0) {
    console.log(`cleanDuplicateLines: Удалено ${removedCount} дублированных строк`);
  }
}

/**
 * Функция для радикальной очистки пустых строк в Single View
 * Удаляет все пустые строки и лишние переносы строк
 * @param {HTMLElement} logElement - элемент лога Single View
 */
function cleanSingleViewEmptyLines(logElement) {
  if (!logElement) return;
  
  let removedCount = 0;
  
  // Удаляем все пустые строки (элементы .line без текста)
  const lines = Array.from(logElement.querySelectorAll('.line'));
  lines.forEach(line => {
    const textContent = line.textContent || line.innerText || '';
    if (textContent.trim() === '') {
      line.remove();
      removedCount++;
    }
  });
  
  // Удаляем все текстовые узлы, которые содержат только пробелы и переносы строк
  const walker = document.createTreeWalker(
    logElement,
    NodeFilter.SHOW_TEXT,
    null,
    false
  );
  
  const textNodesToRemove = [];
  let node;
  while (node = walker.nextNode()) {
    const content = node.textContent;
    // Удаляем все узлы, которые содержат только пробелы, переносы строк или табуляцию
    if (content.trim() === '') {
      textNodesToRemove.push(node);
    }
  }
  
  textNodesToRemove.forEach(node => node.remove());
  
  // Удаляем все пустые текстовые узлы между элементами .line
  const allNodes = Array.from(logElement.childNodes);
  for (let i = allNodes.length - 1; i >= 0; i--) {
    const node = allNodes[i];
    if (node.nodeType === Node.TEXT_NODE && node.textContent.trim() === '') {
      node.remove();
    }
  }
  
  if (removedCount > 0) {
    console.log(`cleanSingleViewEmptyLines: Удалено ${removedCount} пустых строк`);
  }
}

/**
 * Функция для нормализации пробелов в логах
 * Заменяет множественные пробелы на один пробел
 * @param {string} text - исходный текст
 * @returns {string} - текст с нормализованными пробелами
 */
function normalizeSpaces(text) {
  if (!text) return text;
  
  // Заменяем множественные пробелы на один пробел
  // Используем регулярное выражение для замены 2+ пробелов на один
  return text.replace(/\s{2,}/g, ' ');
}

/**
 * Функция для периодической очистки пустых строк
 * Вызывается автоматически каждые 2 секунды для поддержания чистоты логов
 */
function periodicCleanup() {
  // Очищаем пустые строки в Single View
  if (!state.multiViewMode && els.logContent) {
    cleanSingleViewEmptyLines(els.logContent);
    cleanDuplicateLines(els.logContent);
  }
  
  // Очищаем пустые строки в мультипросмотре
  if (state.multiViewMode) {
    state.selectedContainers.forEach(containerId => {
      const multiViewLog = document.querySelector(`.multi-view-log[data-container-id="${containerId}"]`);
      if (multiViewLog) {
        cleanMultiViewEmptyLines(multiViewLog);
        cleanMultiViewDuplicateLines(multiViewLog);
      }
    });
  }
  
  // Очищаем пустые строки в legacy панелях
  Object.values(state.open).forEach(obj => {
    if (obj.logEl) {
      cleanSingleViewEmptyLines(obj.logEl);
      cleanDuplicateLines(obj.logEl);
    }
  });
}

// Запускаем периодическую очистку каждые 2 секунды
setInterval(periodicCleanup, 2000);

// Запускаем периодическую проверку стилей multi-view логов каждые 5 секунд
setInterval(() => {
        if (state.multiViewMode) {
        const multiViewLogs = document.querySelectorAll('.multi-view-content .multi-view-log');
        if (multiViewLogs.length > 0) {
      console.log('Periodic check: Force fixing multi-view styles');
      forceFixMultiViewStyles();
      
      // Дополнительно исправляем все контейнеры
      console.log('Periodic check: Fixing all containers');
      if (window.fixAllContainers) {
        window.fixAllContainers();
      }
    }
  }
}, 5000);

/**
 * Функция для обработки специальных замен в MultiView логах
 * Выполняет специфичные замены для улучшения читаемости логов
 * @param {string} text - исходный текст
 * @returns {string} - текст с примененными заменами
 */
function processMultiViewSpecialReplacements(text) {
  if (!text) return text;
  
  let processedText = text;
  
  // Добавляем отладочную информацию для проверки
  if (text.includes('FoundINFO:')) {
    console.log('🔍 processMultiViewSpecialReplacements: Найдена строка с FoundINFO:', text);
  }
  
  // Проверяем на дублирование строк в исходном тексте
  const lines = processedText.split('\n');
  const uniqueLines = [...new Set(lines)];
  if (lines.length !== uniqueLines.length) {
    console.log('🚨 processMultiViewSpecialReplacements: Обнаружено дублирование в исходном тексте!');
    console.log('🚨 Исходные строки:', lines.length, 'Уникальные строки:', uniqueLines.length);
    // Убираем дублированные строки
    processedText = uniqueLines.join('\n');
  }
  
  // Заменяем случаи, где INFO: прилипает к предыдущему тексту
  // Ищем паттерн: любой текст + INFO: (но не в начале строки)
  // Используем более точное регулярное выражение для поиска
  processedText = processedText.replace(/([A-Za-z0-9\s]+)INFO:/g, '$1\nINFO:');
  
  // Убираем лишние переносы строк в начале, если они есть
  processedText = processedText.replace(/^\n+/, '');
  
  // Проверяем результат
  if (text.includes('FoundINFO:') && processedText !== text) {
    console.log('✅ processMultiViewSpecialReplacements: Замена выполнена:', processedText);
  } else if (text.includes('FoundINFO:') && processedText === text) {
    console.log('❌ processMultiViewSpecialReplacements: Замена НЕ выполнена для:', text);
  }
  
  return processedText;
}

/**
 * Функция для обработки специальных замен в Single View логах
 * Не добавляет лишние переносы строк
 * @param {string} text - исходный текст
 * @returns {string} - текст с примененными заменами
 */
function processSingleViewSpecialReplacements(text) {
  if (!text) return text;
  
  let processedText = text;
  
  // Добавляем отладочную информацию для проверки
  if (text.includes('FoundINFO:')) {
    console.log('🔍 processSingleViewSpecialReplacements: Найдена строка с FoundINFO:', text);
  }
  
  // Проверяем на дублирование строк в исходном тексте
  const lines = processedText.split('\n');
  const uniqueLines = [...new Set(lines)];
  if (lines.length !== uniqueLines.length) {
    console.log('🚨 processSingleViewSpecialReplacements: Обнаружено дублирование в исходном тексте!');
    console.log('🚨 Исходные строки:', lines.length, 'Уникальные строки:', uniqueLines.length);
    // Убираем дублированные строки
    processedText = uniqueLines.join('\n');
  }
  
  // Для Single View НЕ добавляем переносы строк, только убираем дубликаты
  // Убираем лишние переносы строк в начале, если они есть
  processedText = processedText.replace(/^\n+/, '');
  
  // Проверяем результат
  if (text.includes('FoundINFO:') && processedText !== text) {
    console.log('✅ processSingleViewSpecialReplacements: Замена выполнена:', processedText);
  } else if (text.includes('FoundINFO:') && processedText === text) {
    console.log('❌ processSingleViewSpecialReplacements: Замена НЕ выполнена для:', text);
  }
  
  return processedText;
}

// Тестовая функция для проверки работы processMultiViewLineBreaks
function testMultiViewLineBreaks() {
  console.log('=== Тест функции processMultiViewLineBreaks ===');
  console.log('Тест 1 (1 символ):', JSON.stringify(processMultiViewLineBreaks('a')));
  console.log('Тест 2 (3 символа):', JSON.stringify(processMultiViewLineBreaks('abc')));
  console.log('Тест 3 (5 символов):', JSON.stringify(processMultiViewLineBreaks('abcde')));
  console.log('Тест 4 (6 символов):', JSON.stringify(processMultiViewLineBreaks('abcdef')));
  console.log('Тест 5 (с переносами):', JSON.stringify(processMultiViewLineBreaks('a\nb\nc')));
  console.log('Тест 6 (длинная строка):', JSON.stringify(processMultiViewLineBreaks('Это длинная строка с текстом')));
  console.log('=== Конец теста ===');
}

// Функция для тестирования исправлений дублирования
function testDuplicateRemoval() {
  console.log('=== Тест исправлений дублирования ===');
  
  // Создаем тестовый элемент
  const testElement = document.createElement('div');
  testElement.innerHTML = `
    <span class="line">Первая строка</span>
    <span class="line">Вторая строка</span>
    <span class="line">Вторая строка</span>
    <span class="line">Третья строка</span>
    <span class="line">Третья строка</span>
    <span class="line">Четвертая строка</span>
  `;
  
  console.log('До очистки дубликатов:', testElement.querySelectorAll('.line').length, 'строк');
  console.log('HTML до очистки:', testElement.innerHTML);
  
  cleanMultiViewDuplicateLines(testElement);
  
  console.log('После очистки дубликатов:', testElement.querySelectorAll('.line').length, 'строк');
  console.log('HTML после очистки:', testElement.innerHTML);
  
  console.log('=== Конец теста дублирования ===');
}

// Функция для тестирования Single View дублирования
function testSingleViewDuplicateRemoval() {
  console.log('=== Тест Single View дублирования ===');
  
  // Создаем тестовый элемент для Single View
  const testElement = document.createElement('div');
  testElement.innerHTML = `
    <span class="line">INFO: 172.22.0.1:59030 - "GET /api/logs/stats/b816b3f326b9 HTTP/1.1" 200 OK</span>
    <span class="line">INFO: 172.22.0.1:61392 - "GET / HTTP/1.1" 401 Unauthorized</span>
    <span class="line">INFO: 172.22.0.1:61392 - "GET / HTTP/1.1" 401 Unauthorized</span>
    <span class="line">INFO: 172.22.0.1:61392 - "GET / HTTP/1.1" 200 OK</span>
    <span class="line">INFO: 172.22.0.1:61392 - "GET / HTTP/1.1" 200 OK</span>
    <span class="line">INFO: 172.22.0.1:61392 - "GET /api/projects HTTP/1.1" 200 OK</span>
  `;
  
  console.log('До очистки Single View дубликатов:', testElement.querySelectorAll('.line').length, 'строк');
  console.log('HTML до очистки:', testElement.innerHTML);
  
  cleanDuplicateLines(testElement);
  
  console.log('После очистки Single View дубликатов:', testElement.querySelectorAll('.line').length, 'строк');
  console.log('HTML после очистки:', testElement.innerHTML);
  
  console.log('=== Конец теста Single View дублирования ===');
}

// Функция для тестирования очистки пустых строк в Single View
function testSingleViewEmptyLinesRemoval() {
  console.log('=== Тест очистки пустых строк в Single View ===');
  
  // Создаем тестовый элемент с пустыми строками
  const testElement = document.createElement('div');
  testElement.innerHTML = `
    <span class="line">INFO: 172.22.0.1:59030 - "GET /api/logs/stats/b816b3f326b9 HTTP/1.1" 200 OK</span>
    <span class="line">   </span>
    <span class="line"></span>
    <span class="line">INFO: 172.22.0.1:61392 - "GET / HTTP/1.1" 401 Unauthorized</span>
    <span class="line">   </span>
    <span class="line">INFO: 172.22.0.1:61392 - "GET / HTTP/1.1" 200 OK</span>
    <span class="line"></span>
    <span class="line">INFO: 172.22.0.1:61392 - "GET /api/projects HTTP/1.1" 200 OK</span>
  `;
  
  console.log('До очистки пустых строк:', testElement.querySelectorAll('.line').length, 'строк');
  console.log('HTML до очистки:', testElement.innerHTML);
  
  cleanSingleViewEmptyLines(testElement);
  
  console.log('После очистки пустых строк:', testElement.querySelectorAll('.line').length, 'строк');
  console.log('HTML после очистки:', testElement.innerHTML);
  
  console.log('=== Конец теста очистки пустых строк ===');
}

// Функция для тестирования правильного отображения переносов строк
function testSingleViewLineBreaks() {
  console.log('=== Тест правильного отображения переносов строк в Single View ===');
  
  // Создаем тестовый элемент с правильными переносами строк
  const testElement = document.createElement('div');
  testElement.innerHTML = `
    <span class="line">INFO: 172.22.0.1:59030 - "GET /api/logs/stats/b816b3f326b9 HTTP/1.1" 200 OK</span>\n
    <span class="line">INFO: 172.22.0.1:61392 - "GET / HTTP/1.1" 401 Unauthorized</span>\n
    <span class="line">INFO: 172.22.0.1:61392 - "GET / HTTP/1.1" 200 OK</span>\n
    <span class="line">INFO: 172.22.0.1:61392 - "GET /api/projects HTTP/1.1" 200 OK</span>\n
  `;
  
  console.log('Тестовый элемент с переносами строк:');
  console.log('Количество строк:', testElement.querySelectorAll('.line').length);
  console.log('HTML:', testElement.innerHTML);
  console.log('Текстовое содержимое:', testElement.textContent);
  
  // Проверяем, что переносы строк присутствуют
  const textContent = testElement.textContent;
  const lineBreaks = (textContent.match(/\n/g) || []).length;
  console.log('Количество переносов строк в тексте:', lineBreaks);
  
  console.log('=== Конец теста переносов строк ===');
}

// Тестовая функция для проверки работы cleanMultiViewEmptyLines
function testCleanMultiViewEmptyLines() {
  console.log('=== Тест функции cleanMultiViewEmptyLines ===');
  
  // Создаем тестовый элемент
  const testElement = document.createElement('div');
  testElement.innerHTML = `
    <span class="line">Первая строка лога</span>
    <span class="line">   </span>
    <span class="line"></span>
    <span class="line">Вторая строка лога</span>
    <span class="line">   </span>
    <span class="line">Третья строка лога</span>
  `;
  
  console.log('До очистки:', testElement.innerHTML);
  cleanMultiViewEmptyLines(testElement);
  console.log('После очистки:', testElement.innerHTML);
  console.log('=== Конец теста ===');
}

// Тестовая функция для проверки работы normalizeSpaces
function testNormalizeSpaces() {
  console.log('=== Тест функции normalizeSpaces ===');
  console.log('Тест 1 (обычная строка):', JSON.stringify(normalizeSpaces('Hello World')));
  console.log('Тест 2 (двойные пробелы):', JSON.stringify(normalizeSpaces('Hello  World')));
  console.log('Тест 3 (множественные пробелы):', JSON.stringify(normalizeSpaces('Hello     World')));
  console.log('Тест 4 (пробелы в начале и конце):', JSON.stringify(normalizeSpaces('  Hello  World  ')));
  console.log('Тест 5 (табуляция и пробелы):', JSON.stringify(normalizeSpaces('Hello\t\tWorld')));
  console.log('Тест 6 (смешанные пробелы):', JSON.stringify(normalizeSpaces('Hello   \t   World')));
  console.log('Тест 7 (пустая строка):', JSON.stringify(normalizeSpaces('')));
  console.log('Тест 8 (null):', JSON.stringify(normalizeSpaces(null)));
  console.log('=== Конец теста ===');
}

// Тестовая функция для проверки работы processMultiViewSpecialReplacements
function testMultiViewSpecialReplacements() {
  console.log('=== Тест функции processMultiViewSpecialReplacements ===');
  console.log('Тест 1 (обычная строка):', JSON.stringify(processMultiViewSpecialReplacements('Hello World')));
  console.log('Тест 2 (200 OKINFO:):', JSON.stringify(processMultiViewSpecialReplacements('200 OKINFO: Some message')));
  console.log('Тест 3 (404 Not FoundINFO:):', JSON.stringify(processMultiViewSpecialReplacements('404 Not FoundINFO: Some message')));
  console.log('Тест 4 (FoundINFO:):', JSON.stringify(processMultiViewSpecialReplacements('FoundINFO: Some message')));
  console.log('Тест 5 (Found INFO:):', JSON.stringify(processMultiViewSpecialReplacements('Found INFO: Some message')));
  console.log('Тест 6 (500 Internal Server ErrorINFO:):', JSON.stringify(processMultiViewSpecialReplacements('500 Internal Server ErrorINFO: Some message')));
  console.log('Тест 7 (GET /api/usersINFO:):', JSON.stringify(processMultiViewSpecialReplacements('GET /api/usersINFO: Some message')));
  console.log('Тест 8 (POST /api/loginINFO:):', JSON.stringify(processMultiViewSpecialReplacements('POST /api/loginINFO: Some message')));
  console.log('Тест 9 (INFO: в начале):', JSON.stringify(processMultiViewSpecialReplacements('INFO: Some message')));
  console.log('Тест 10 (несколько INFO:):', JSON.stringify(processMultiViewSpecialReplacements('200 OKINFO: First INFO: Second')));
  console.log('Тест 11 (пустая строка):', JSON.stringify(processMultiViewSpecialReplacements('')));
  console.log('Тест 12 (null):', JSON.stringify(processMultiViewSpecialReplacements(null)));
  console.log('=== Конец теста ===');
}

// Комплексная тестовая функция для проверки полного процесса обработки MultiView
function testFullMultiViewProcessing() {
  console.log('=== Тест полного процесса обработки MultiView ===');
  
  const testCases = [
    '200 OKINFO: Some message',
    '404 Not FoundINFO: Another message',
    '500 Internal Server ErrorINFO: Third message',
    'FoundINFO: First FoundINFO: Second',
    'GET /api/usersINFO: API call',
    'POST /api/loginINFO: Login attempt',
    'Short',
    'Long message with 200 OKINFO: inside'
  ];
  
  testCases.forEach((testCase, index) => {
    console.log(`\nТест ${index + 1}: "${testCase}"`);
    
    // 1. Нормализация пробелов
    const normalized = normalizeSpaces(testCase);
    console.log('  1. Нормализация пробелов:', JSON.stringify(normalized));
    
    // 2. Специальные замены
    const specialProcessed = processMultiViewSpecialReplacements(normalized);
    console.log('  2. Специальные замены:', JSON.stringify(specialProcessed));
    
    // 3. Обработка переноса строк
    const finalProcessed = processMultiViewLineBreaks(specialProcessed);
    console.log('  3. Перенос строк:', JSON.stringify(finalProcessed));
    
    console.log('  Результат:', finalProcessed);
  });
  
  console.log('\n=== Конец комплексного теста ===');
}

// Быстрая функция для тестирования замены INFO:
function quickTestINFO() {
  console.log('=== Быстрый тест замены INFO: ===');
  const testStrings = [
    'INFO: 127.0.0.1:53352 - "GET /health HTTP/1.1" 200 OKINFO: 127.0.0.1:53352 - "GET /health HTTP/1.1" 200 OK',
    'INFO: 127.0.0.1:48988 - "GET /health HTTP/1.1" 200 OKINFO: 127.0.0.1:48988 - "GET /health HTTP/1.1" 200 OK',
    'INFO: 127.0.0.1:44606 - "GET /health HTTP/1.1" 200 OKINFO: 127.0.0.1:44606 - "GET /health HTTP/1.1" 200 OK',
    '200 OKINFO:',
    '404 Not FoundINFO:',
    '500 Internal Server ErrorINFO:',
    'FoundINFO:',
    'INFO:'
  ];
  
  testStrings.forEach((str, index) => {
    const result = processMultiViewSpecialReplacements(str);
    console.log(`Тест ${index + 1}: "${str}" -> "${result}"`);
  });
  console.log('=== Конец быстрого теста ===');
}

// Функция для тестирования регулярного выражения
function testRegex() {
  console.log('=== Тест регулярного выражения ===');
  
  const testString = 'INFO: 172.22.0.10:33860 - "POST /api/v1/auth/verify HTTP/1.1" 404 Not FoundINFO: 172.22.0.10:33860 - "POST /api/v1/auth/verify HTTP/1.1" 404 Not Found';
  
  console.log('Исходная строка:', testString);
  
  // Тестируем наше регулярное выражение
  const result = testString.replace(/([A-Za-z0-9\s]+)INFO:/g, '$1\nINFO:');
  console.log('Результат замены:', result);
  
  // Проверяем, есть ли совпадения
  const matches = testString.match(/([A-Za-z0-9\s]+)INFO:/g);
  console.log('Найденные совпадения:', matches);
  
  console.log('=== Конец теста регулярного выражения ===');
}

// Функция для проверки HTML в MultiView на наличие FoundINFO:
function checkMultiViewHTML() {
  console.log('=== Проверка HTML в MultiView ===');
  
  const multiViewLogs = document.querySelectorAll('.multi-view-log');
  console.log('Найдено MultiView логов:', multiViewLogs.length);
  
  multiViewLogs.forEach((log, index) => {
    const containerId = log.getAttribute('data-container-id');
    console.log(`MultiView ${index + 1} (${containerId}):`);
    
    // Проверяем весь HTML
    const html = log.innerHTML;
    if (html.includes('FoundINFO:')) {
      console.log('🚨 НАЙДЕНО FoundINFO: в HTML!');
      console.log('HTML:', html);
    } else {
      console.log('✅ FoundINFO: не найдено в HTML');
    }
    
    // Проверяем текстовое содержимое
    const textContent = log.textContent;
    if (textContent.includes('FoundINFO:')) {
      console.log('🚨 НАЙДЕНО FoundINFO: в тексте!');
      console.log('Текст:', textContent);
    } else {
      console.log('✅ FoundINFO: не найдено в тексте');
    }
  });
  
  console.log('=== Конец проверки HTML ===');
}

/**
 * Основная функция обработки строк логов
 * Классифицирует, фильтрует и отображает строки логов в зависимости от режима
 * @param {string} id - ID контейнера
 * @param {string} line - Строка лога для обработки
 */
function handleLine(id, line){
  
  const obj = state.open[id];
  if (!obj) {
    console.error(`handleLine: Object not found for container ${id}, available containers:`, Object.keys(state.open));
    return;
  }
  
  // Отладочная информация для первых нескольких строк
  if (!obj.counters) {
    console.error(`handleLine: Counters not initialized for container ${id}`);
    obj.counters = {dbg:0, info:0, warn:0, err:0, other:0};
  }
  
  // Фильтруем сообщение "Connected to container" для всех режимов
  // Это сообщение отправляется сервером при установке WebSocket соединения
  if (line.includes('Connected to container:')) {
    console.log(`handleLine: Фильтруем сообщение "Connected to container" для контейнера ${id}`);
    return; // Пропускаем это сообщение во всех режимах
  }
  
  // Нормализуем пробелы в строке лога
  const normalizedLine = normalizeSpaces(line);
  
  const cls = classify(normalizedLine);
  
  // Обновляем счетчики только для отображаемых логов
  // Проверяем фильтры для отображения в зависимости от режима
  let shouldShow;
  if (state.multiViewMode && state.selectedContainers.includes(id)) {
    // Для multi-view используем настройки конкретного контейнера
    shouldShow = allowedByContainerLevel(cls, id) && applyFilter(normalizedLine);
  } else {
    // Для single-view используем глобальные настройки
    shouldShow = allowedByLevel(cls) && applyFilter(normalizedLine);
  }
  
  // Обновляем счетчики только если строка будет отображаться
  if (obj.counters && shouldShow) {
    if (cls==='dbg') obj.counters.dbg++;
    if (cls==='ok') obj.counters.info++;
    if (cls==='warn') obj.counters.warn++;
    if (cls==='err') obj.counters.err++;
    if (cls==='other') obj.counters.other++;
  }
  
  // Обновляем счетчики в кнопках заголовков
  updateHeaderCounters(id, obj.counters);
  
  // Для Single View НЕ добавляем перенос строки после каждой строки лога
  const html = `<span class="line ${cls}">${ansiToHtml(normalizedLine)}</span>`;
  
  // Сохраняем все логи в буфере (всегда)
  if (!obj.allLogs) obj.allLogs = [];
  // Для Single View сохраняем обработанную строку, для MultiView - оригинальную
  const processedLine = !state.multiViewMode ? processSingleViewSpecialReplacements(normalizedLine) : normalizedLine;
  const processedHtml = `<span class="line ${cls}">${ansiToHtml(processedLine)}</span>`;
  obj.allLogs.push({html: processedHtml, line: processedLine, cls: cls});
  
  // Ограничиваем размер буфера
  if (obj.allLogs.length > 10000) {
    obj.allLogs = obj.allLogs.slice(-5000);
  }
  
  // Добавляем логи в отображение (обычный просмотр) - только если НЕ в multi-view режиме
  if (shouldShow && obj.logEl && !state.multiViewMode) {
    // Обрабатываем строку для Single View (без лишних переносов строк)
    const singleViewProcessedLine = processSingleViewSpecialReplacements(normalizedLine);
    
    // Проверяем на дублирование в Single View логах
    const existingLines = Array.from(obj.logEl.querySelectorAll('.line'));
    const lastLine = existingLines[existingLines.length - 1];
    if (lastLine && lastLine.textContent === singleViewProcessedLine) {
      console.log(`handleLine: Пропускаем дублированную строку для Single View контейнера ${id}:`, singleViewProcessedLine.substring(0, 50));
      return; // Пропускаем дублированную строку
    }
    
    // Создаем HTML с обработанной строкой для Single View (без переноса строки)
    const singleViewHtml = `<span class="line ${cls}">${ansiToHtml(singleViewProcessedLine)}</span>`;
    
    obj.logEl.insertAdjacentHTML('beforeend', singleViewHtml);
    
    // Очищаем лишние пустые строки в Single View
    cleanSingleViewEmptyLines(obj.logEl);
    
    if (els.autoscroll && els.autoscroll.checked && obj.wrapEl) {
      obj.wrapEl.scrollTop = obj.wrapEl.scrollHeight;
    }
    
    // Update modern interface
    console.log(`handleLine: Checking modern interface update - state.current:`, state.current, `id:`, id, `els.logContent:`, !!els.logContent);
    if (state.current && state.current.id === id && els.logContent) {
      console.log(`handleLine: Updating modern interface for container ${id} with html:`, singleViewHtml.substring(0, 100));
      // Добавляем новую строку напрямую в современный интерфейс
      els.logContent.insertAdjacentHTML('beforeend', singleViewHtml);
      
      // Очищаем лишние пустые строки в современном интерфейсе
      cleanSingleViewEmptyLines(els.logContent);
      
      console.log(`handleLine: Modern interface updated, logContent children count:`, els.logContent.children.length);
      if (els.autoscroll && els.autoscroll.checked) {
        els.logContent.scrollTop = els.logContent.scrollHeight;
      }
    } else {
      console.log(`handleLine: Modern interface update skipped - state.current:`, state.current?.id, `id:`, id, `logContent exists:`, !!els.logContent);
    }
  }
  
  // Update multi-view interface
  if (state.multiViewMode && state.selectedContainers.includes(id)) {
    const multiViewLog = document.querySelector(`.multi-view-log[data-container-id="${id}"]`);
    if (multiViewLog) {
      // Проверяем фильтры для конкретного контейнера
      const shouldShowInMultiView = allowedByContainerLevel(cls, id) && applyFilter(normalizedLine);
      
      if (shouldShowInMultiView) {
        // Применяем ограничение tail lines в multi view
        const tailLines = parseInt(els.tail.value) || 50;
        
        // Порядок обработки строк для MultiView:
        // 1. Нормализация пробелов (уже выполнена выше)
        // 2. Специальные замены (например, "FoundINFO:" -> "Found\nINFO:")
        // 3. Обработка переноса строк
        const specialProcessedLine = processMultiViewSpecialReplacements(normalizedLine);
        
        // Обрабатываем перенос строк для multi view
        // Если символов больше 5, то перенос строк работает
        // Если меньше 5, то переноса строк нет
        const processedLine = processMultiViewLineBreaks(specialProcessedLine);
        
        // Проверяем на дублирование в multi-view логах
        const existingLines = Array.from(multiViewLog.querySelectorAll('.line'));
        const lastLine = existingLines[existingLines.length - 1];
        if (lastLine && lastLine.textContent === processedLine) {
          console.log(`handleLine: Пропускаем дублированную строку для контейнера ${id}:`, processedLine.substring(0, 50));
          return; // Пропускаем дублированную строку
        }
        
        const multiViewHtml = `<span class="line ${cls}">${ansiToHtml(processedLine)}</span>`;
        
        // Добавляем новую строку
        multiViewLog.insertAdjacentHTML('beforeend', multiViewHtml);
        
        // Очищаем пустые строки в multi view
        cleanMultiViewEmptyLines(multiViewLog);
        
        // Очищаем дублированные строки в multi view
        cleanMultiViewDuplicateLines(multiViewLog);
        
        // Ограничиваем количество отображаемых строк
        const logLines = Array.from(multiViewLog.querySelectorAll('.line'));
        if (logLines.length > tailLines) {
          // Удаляем лишние строки с начала
          const linesToRemove = logLines.length - tailLines;
          console.log(`handleLine: Trimming ${linesToRemove} lines from container ${id} (tail: ${tailLines})`);
          
          // Удаляем первые N строк
          logLines.slice(0, linesToRemove).forEach(line => {
            line.remove();
          });
        }
        
        if (els.autoscroll && els.autoscroll.checked) {
          multiViewLog.scrollTop = multiViewLog.scrollHeight;
        }
        console.log(`handleLine: Updated multi-view for container ${id}, log element found: true, tail lines: ${tailLines}`);
      }
    } else {
      console.error(`handleLine: Multi-view log element not found for container ${id}`);
    }
    
    // Обновляем счетчики в multi view периодически (каждые 10 строк)
    if (!state.multiViewCounterUpdateTimer) {
      state.multiViewCounterUpdateTimer = setTimeout(() => {
        updateMultiViewCounters();
        
        // Периодически очищаем дублированные строки во всех multi-view логах
        state.selectedContainers.forEach(containerId => {
          const multiViewLog = document.querySelector(`.multi-view-log[data-container-id="${containerId}"]`);
          if (multiViewLog) {
            cleanMultiViewDuplicateLines(multiViewLog);
          }
        });
        
        state.multiViewCounterUpdateTimer = null;
      }, 1000); // Обновляем каждую секунду
    }
  }
}

function ensurePanel(svc){
  let panel = els.grid.querySelector(`.panel[data-cid="${svc.id}"]`);
  if (!panel){
    panel = panelTemplate(svc);
    els.grid.appendChild(panel);
    panel.querySelector('.t-reconnect').onclick = ()=>{
      const id = svc.id;
      const o = state.open[id];
      if (o){ o.logEl.textContent=''; closeWs(id); }
      openWs(svc, panel);
    };
    panel.querySelector('.t-close').onclick = ()=>{
      closeWs(svc.id);
      panel.remove();
      if (!Object.keys(state.open).length) setWsState('off');
    };
    panel.querySelector('.t-snapshot').onclick = ()=> sendSnapshot(svc.id);
  }
  return panel;
}

/**
 * Переключает интерфейс в режим одиночного просмотра (single view)
 * Закрывает мультипросмотр, открывает WebSocket для выбранного контейнера
 * @param {Object} svc - Объект сервиса/контейнера для просмотра
 */
async function switchToSingle(svc){
  console.log('switchToSingle: ENTRY POINT - function called - VERSION 2');
  console.log('switchToSingle: svc parameter:', svc);
  
  try {
    console.log('switchToSingle called for:', svc.name);
    console.log('switchToSingle: Starting function execution');
    console.log('switchToSingle: svc name:', svc.name, 'id:', svc.id);
    console.log('switchToSingle: state.current:', state.current?.name, 'multiViewMode:', state.multiViewMode);
    
    // Всегда очищаем мультипросмотр при переключении в single view
    console.log('Clearing multi-view mode');
    state.multiViewMode = false;
  
  // Закрываем WebSocket соединения для мультипросмотра
  console.log('switchToSingle: Closing WebSocket connections for multi-view');
  state.selectedContainers.forEach(containerId => {
    closeWs(containerId);
  });
  
  // Очищаем область логов
  console.log('switchToSingle: Clearing log content');
  if (els.logContent) {
    els.logContent.innerHTML = '';
  }
  
  // Воссоздаем single-view-panel если его нет
  const logContent = document.querySelector('.log-content');
  const singleViewPanel = document.getElementById('singleViewPanel');
  if (logContent && !singleViewPanel) {
    console.log('switchToSingle: Recreating single-view-panel');
    logContent.innerHTML = `
      <div class="single-view-panel" id="singleViewPanel">
        <div class="single-view-header">
          <h4 class="single-view-title" id="singleViewTitle">${svc.name} (${svc.service || svc.name})</h4>
          <div class="single-view-levels">
            <button class="level-btn debug-btn" data-level="debug" title="DEBUG">
              <span class="level-label">DEBUG</span>
              <span class="level-value" data-container="single">0</span>
            </button>
            <button class="level-btn info-btn" data-level="info" title="INFO">
              <span class="level-label">INFO</span>
              <span class="level-value" data-container="single">0</span>
            </button>
            <button class="level-btn warn-btn" data-level="warn" title="WARN">
              <span class="level-label">WARN</span>
              <span class="level-value" data-container="single">0</span>
            </button>
            <button class="level-btn error-btn" data-level="err" title="ERROR">
              <span class="level-label">ERROR</span>
              <span class="level-value" data-container="single">0</span>
            </button>
            <button class="level-btn other-btn" data-level="other" title="OTHER">
              <span class="level-label">OTHER</span>
              <span class="level-value" data-container="single">0</span>
            </button>
          </div>
        </div>
        <div class="single-view-content">
          <pre class="log" id="logContent">Connecting...</pre>
        </div>
      </div>
    `;
    // Обновляем ссылки на элементы
    els.singleViewPanel = document.getElementById('singleViewPanel');
    els.singleViewTitle = document.getElementById('singleViewTitle');
    els.logContent = document.getElementById('logContent');
  }
  
  // Удаляем мультипросмотр из DOM
  const multiViewGrid = document.getElementById('multiViewGrid');
  if (multiViewGrid) {
    console.log('Removing multi-view grid from DOM');
    multiViewGrid.remove();
  } else {
    console.log('Multi-view grid not found in DOM');
  }
  
  // Устанавливаем текущий контейнер
  state.current = svc;
  console.log('switchToSingle: Set state.current to:', svc.name, 'id:', svc.id);
  
  // Обновляем состояние выбранных контейнеров для корректного отображения заголовка
  state.selectedContainers = [svc.id];
  
  // Сохраняем режим просмотра в localStorage
  saveViewMode(false, [svc.id]);
  
  // Открываем WebSocket соединение для логов
  console.log('switchToSingle: Calling openWs for:', svc.name, 'id:', svc.id);
  openWs(svc, els.logContent);
  
  // Обновляем интерфейс
  buildTabs();
  
  // Обновляем активное состояние в UI
  updateActiveContainerUI(svc.id);
  
  // Сохраняем состояние кнопок loglevels в localStorage
  saveLogLevelsState();
  

  if (els.multiViewPanelTitle) {
    els.multiViewPanelTitle.textContent = `${svc.name} (${svc.service || svc.name})`;
  }
  if (els.singleViewTitle) {
    els.singleViewTitle.textContent = `${svc.name} (${svc.service || svc.name})`;
  }
  if (els.logContent) {
    els.logContent.innerHTML = '<span class="line info">Connecting...</span>';
  }
  
  // Обновляем logEl для современного интерфейса
  const obj = state.open[svc.id];
  if (obj && els.logContent) {
    obj.logEl = els.logContent;
    obj.wrapEl = els.logContent.parentElement;
    console.log('switchToSingle: Updated obj.logEl and obj.wrapEl for modern interface');
    
    // Если у нас уже есть логи в буфере, отображаем их
    if (obj.allLogs && obj.allLogs.length > 0) {
      console.log(`switchToSingle: Restoring ${obj.allLogs.length} buffered log lines`);
      els.logContent.innerHTML = '';
      obj.allLogs.forEach(logEntry => {
        if (allowedByLevel(logEntry.cls) && applyFilter(logEntry.line)) {
          els.logContent.insertAdjacentHTML('beforeend', logEntry.html);
        }
      });
      
      // Очищаем лишние пустые строки после восстановления логов
      cleanSingleViewEmptyLines(els.logContent);
      cleanDuplicateLines(els.logContent);
      
      if (els.autoscroll && els.autoscroll.checked) {
        els.logContent.scrollTop = els.logContent.scrollHeight;
      }
    }
  }
  
  // Update active state in container list
  document.querySelectorAll('.container-item').forEach(item => {
    item.classList.remove('active');
  });
  const activeItem = document.querySelector(`.container-item[data-cid="${svc.id}"]`);
  if (activeItem) {
    activeItem.classList.add('active');
  }
  
  // Обновляем состояние чекбоксов после переключения контейнера
  updateContainerSelectionUI();
  
  // Обновляем счетчики для нового контейнера
  setTimeout(() => {
    recalculateCounters();
    // Применяем настройки wrap text после переключения контейнера
    applyWrapSettings();
  }, 500); // Небольшая задержка для завершения загрузки логов
  await updateCounters(svc.id);
  
  // Добавляем обработчики для счетчиков после переключения контейнера
  addCounterClickHandlers();
  
  // Обновляем состояние кнопок уровней логирования
  setTimeout(() => {
    initializeLevelButtons();
  }, 100);
  
  } catch (error) {
    console.error('switchToSingle: Error occurred:', error);
    console.error('switchToSingle: Error stack:', error.stack);
  }
}

async function openMulti(ids){
  els.grid.innerHTML='';
  const chosen = state.services.filter(s=> ids.includes(s.id));
  const n = chosen.length;
  if (n<=1){ if (n===1) await switchToSingle(chosen[0]); return; }
  setLayout(n>=4 ? 'grid4' : n===3 ? 'grid3' : 'grid2');
  for (const svc of chosen){
    const panel = ensurePanel(svc);
    panel.querySelector('.log').textContent='';
    closeWs(svc.id);
    openWs(svc, panel);
  }
  
  // Добавляем обработчики для счетчиков после открытия мульти-контейнеров
  addCounterClickHandlers();
  
  // Применяем настройки wrap text после открытия мульти-контейнеров
  applyWrapSettings();
}

// ----- Copy on selection -----
function getSelectionText(){
  const sel = window.getSelection();
  return sel && sel.rangeCount ? sel.toString() : "";
}
function showCopyFabNearSelection(){
  const sel = window.getSelection();
  if (!sel || sel.rangeCount===0) return hideCopyFab();
  const text = sel.toString();
  if (!text.trim()) return hideCopyFab();
  // Only show if selection inside a .log or .logwrap
  const range = sel.getRangeAt(0);
  const common = range.commonAncestorContainer;
  const el = common.nodeType===1 ? common : common.parentElement;
  if (!el || !el.closest('.logwrap')) return hideCopyFab();
  const rect = range.getBoundingClientRect();
  const top = rect.bottom + 8 + window.scrollY;
  const left = rect.right + 8 + window.scrollX;
  els.copyFab.style.top = top + 'px';
  els.copyFab.style.left = left + 'px';
  els.copyFab.classList.add('show');
}
function hideCopyFab(){
  els.copyFab.classList.remove('show');
}
document.addEventListener('selectionchange', ()=>{
  // throttle-ish using requestAnimationFrame
  window.requestAnimationFrame(showCopyFabNearSelection);
});
document.addEventListener('scroll', hideCopyFab, true);
els.copyFab.addEventListener('click', async ()=>{
  const text = getSelectionText();
  if (!text) return;
  try {
    await navigator.clipboard.writeText(text);
    const old = els.copyFab.textContent;
    els.copyFab.textContent = 'скопировано';
    setTimeout(()=> els.copyFab.textContent = old, 1000);
    hideCopyFab();
    window.getSelection()?.removeAllRanges();
  } catch(e){
    alert('не удалось скопировать: ' + e);
  }
});


function fanGroupUrl(servicesCsv, project){
  const proto = location.protocol === 'https:' ? 'wss' : 'ws';
  const tail = els.tail.value || '500';
  const token = encodeURIComponent(localStorage.getItem('access_token') || '');
  const pj = project?`&project=${encodeURIComponent(project)}`:'';
  return `${proto}://${location.host}/api/websocket/fan_group?services=${encodeURIComponent(servicesCsv)}&tail=${tail}&token=${token}${pj}`;
}

function openFanGroup(services){
  // Build a special panel named after the group
  els.grid.innerHTML='';
  const fake = { id: 'group-'+services.join(','), name:'group', service: 'group', project: (localStorage.lb_project||'') };
  const panel = ensurePanel(fake);
  panel.querySelector('.log').textContent='';
  closeWs(fake.id);

  // Override ws creation to fan_group
  const id = fake.id;
  const logEl = panel.querySelector('.log');
  const wrapEl = panel.querySelector('.logwrap');
  const cdbg  = panel.querySelector('.cdbg') || document.querySelector('.cdbg');
  const cinfo = panel.querySelector('.cinfo') || document.querySelector('.cinfo');
  const cwarn = panel.querySelector('.cwarn') || document.querySelector('.cwarn');
  const cerr  = panel.querySelector('.cerr') || document.querySelector('.cerr');
  const cother = panel.querySelector('.cother') || document.querySelector('.cother');
  const counters = {dbg:0,info:0,warn:0,err:0,other:0};

  const ws = new WebSocket(fanGroupUrl(services.join(','), fake.project||''));
  state.open[id] = {ws, logEl, wrapEl, counters, pausedBuffer:[], serviceName: ('group:'+services.join(','))};

  ws.onopen = ()=> {
    console.log(`WebSocket ${id}: Соединение открыто`);
    setWsState('on');
  };
  ws.onclose = ()=> {
    console.log(`WebSocket ${id}: Соединение закрыто`);
    setWsState(determineWsState());
    
    // Принудительно проверяем состояние через AJAX через 500мс
    setTimeout(() => {
      console.log(`WebSocket ${id}: Принудительная проверка состояния после закрытия`);
      checkWebSocketStatus();
    }, 500);
  };
  ws.onerror = (error)=> {
    console.log(`WebSocket ${id}: Ошибка соединения:`, error);
    setWsState('err');
  };
  ws.onmessage = (ev)=>{
    // Устанавливаем состояние 'on' при получении сообщений
    setWsState('on');
    
    const parts = (ev.data||'').split(/\r?\n/);
    for (let i=0;i<parts.length;i++){
      if (parts[i].length===0 && i===parts.length-1) continue;
      const pr = parsePrefixAndStrip(parts[i]);
      if (pr){ if (!(pr.id8 in inst.filters)) { inst.filters[pr.id8] = true; updateIdFiltersBar(); } }
      handleLine(id, parts[i]);
    }
    cdbg.textContent  = counters.dbg;
    cinfo.textContent = counters.info;
    cwarn.textContent = counters.warn;
    cerr.textContent  = counters.err;
    if (cother) cother.textContent = counters.other;
    
    // Обновляем видимость счетчиков
    updateCounterVisibility();
    
    // Добавляем обработчики для счетчиков
    addCounterClickHandlers();
  };

  // Show filter bar and clear previous filters
  inst.filters = {};
  updateIdFiltersBar();
}

if (els.groupBtn && els.groupBtn.onclick !== null) {
  els.groupBtn.onclick = ()=>{
    const list = state.services.map(s=> `${s.service}`).filter((v,i,a)=> a.indexOf(v)===i).join(', ');
    const ans = prompt('Введите имена сервисов через запятую:\n'+list);
    if (ans){
      const services = ans.split(',').map(x=>x.trim()).filter(Boolean);
      if (services.length) openFanGroup(services);
    }
  };
}

// Функция для обновления счетчиков через Ajax
async function updateCounters(containerId) {
  try {
    const token = localStorage.getItem('access_token');
    if (!token) {
      console.error('No access token found');
      return;
    }
    
    const response = await fetch(`/api/logs/stats/${containerId}`, {
      headers: {
        'Authorization': `Bearer ${token}`
      }
    });
    if (response.ok) {
      const stats = await response.json();
      const cdbg = document.querySelector('.cdbg');
      const cinfo = document.querySelector('.cinfo');
      const cwarn = document.querySelector('.cwarn');
      const cerr = document.querySelector('.cerr');
      const cother = document.querySelector('.cother');
      
      if (cdbg) cdbg.textContent = stats.debug || 0;
      if (cinfo) cinfo.textContent = stats.info || 0;
      if (cwarn) cwarn.textContent = stats.warn || 0;
      if (cerr) cerr.textContent = stats.error || 0;
      if (cother) cother.textContent = stats.other || 0;
      
      // Обновляем видимость счетчиков
      updateCounterVisibility();
      
      // Добавляем обработчики для счетчиков
      addCounterClickHandlers();
      
      // Очищаем дублированные строки в Single View режиме
      if (!state.multiViewMode && state.current && state.current.id === containerId) {
        const logContent = document.querySelector('.log-content');
        if (logContent) {
          cleanDuplicateLines(logContent);
          cleanSingleViewEmptyLines(logContent);
        }
        
        // Также очищаем в legacy панели
        const obj = state.open[containerId];
        if (obj && obj.logEl) {
          cleanDuplicateLines(obj.logEl);
          cleanSingleViewEmptyLines(obj.logEl);
        }
      }
    }
  } catch (error) {
    console.error('Error updating counters:', error);
  }
}

// Функция для обновления счетчиков в multi view (суммирует статистику всех контейнеров)
// Эта функция теперь использует пересчет на основе отображаемых логов
async function updateMultiViewCounters() {
  if (!state.multiViewMode || state.selectedContainers.length === 0) {
    return;
  }
  
  try {
    console.log('Updating multi-view counters for containers:', state.selectedContainers);
    
    // Используем новую функцию пересчета счетчиков
    recalculateMultiViewCounters();
    
    // Добавляем обработчики для счетчиков
    addCounterClickHandlers();
    
  } catch (error) {
    console.error('Error updating multi-view counters:', error);
  }
}

// Функция для пересчета счетчиков на основе отображаемых логов (Single View)
function recalculateCounters() {
  if (!state.current) return;
  
  const containerId = state.current.id;
  const obj = state.open[containerId];
  if (!obj || !obj.allLogs) return;
  
  // Получаем значение Tail Lines
  const tailLines = parseInt(els.tail.value) || 50;
  
  // Берем только последние N логов в соответствии с Tail Lines
  const visibleLogs = obj.allLogs.slice(-tailLines);
  
  // Сбрасываем счетчики
  obj.counters = {dbg: 0, info: 0, warn: 0, err: 0, other: 0};
  
  // Пересчитываем счетчики только для отображаемых логов
  visibleLogs.forEach(logEntry => {
    const shouldShow = allowedByLevel(logEntry.cls) && applyFilter(logEntry.line);
    if (shouldShow) {
      if (logEntry.cls === 'dbg') obj.counters.dbg++;
      if (logEntry.cls === 'ok') obj.counters.info++;
      if (logEntry.cls === 'warn') obj.counters.warn++;
      if (logEntry.cls === 'err') obj.counters.err++;
      if (logEntry.cls === 'other') obj.counters.other++;
    }
  });
  
  // Обновляем отображение счетчиков
  const cdbg = document.querySelector('.cdbg');
  const cinfo = document.querySelector('.cinfo');
  const cwarn = document.querySelector('.cwarn');
  const cerr = document.querySelector('.cerr');
  const cother = document.querySelector('.cother');
  
  if (cdbg) cdbg.textContent = obj.counters.dbg;
  if (cinfo) cinfo.textContent = obj.counters.info;
  if (cwarn) cwarn.textContent = obj.counters.warn;
  if (cerr) cerr.textContent = obj.counters.err;
  if (cother) cother.textContent = obj.counters.other;
  
  // Обновляем счетчики в кнопках заголовка single-view
  updateHeaderCounters(containerId, obj.counters);
  
  console.log(`Counters recalculated for container ${containerId} (tail: ${tailLines}):`, obj.counters);
}

// Функция для пересчета счетчиков в MultiView на основе отображаемых логов
function recalculateMultiViewCounters() {
  if (!state.multiViewMode || state.selectedContainers.length === 0) {
    return;
  }
  
  console.log('Recalculating multi-view counters for containers:', state.selectedContainers);
  
  // Получаем значение Tail Lines
  const tailLines = parseInt(els.tail.value) || 50;
  
  // Суммируем статистику всех выбранных контейнеров
  let totalDebug = 0;
  let totalInfo = 0;
  let totalWarn = 0;
  let totalError = 0;
  let totalOther = 0;
  
  // Пересчитываем счетчики для каждого контейнера
  for (const containerId of state.selectedContainers) {
    const obj = state.open[containerId];
    if (!obj || !obj.allLogs) continue;
    
    // Берем только последние N логов в соответствии с Tail Lines
    const visibleLogs = obj.allLogs.slice(-tailLines);
    
    // Сбрасываем счетчики для этого контейнера
    obj.counters = {dbg: 0, info: 0, warn: 0, err: 0, other: 0};
    
    // Пересчитываем счетчики только для отображаемых логов
    visibleLogs.forEach(logEntry => {
      const shouldShow = allowedByContainerLevel(logEntry.cls, containerId) && applyFilter(logEntry.line);
      if (shouldShow) {
        if (logEntry.cls === 'dbg') obj.counters.dbg++;
        if (logEntry.cls === 'ok') obj.counters.info++;
        if (logEntry.cls === 'warn') obj.counters.warn++;
        if (logEntry.cls === 'err') obj.counters.err++;
        if (logEntry.cls === 'other') obj.counters.other++;
      }
    });
    
    // Обновляем счетчики в кнопках заголовка для этого контейнера
    updateHeaderCounters(containerId, obj.counters);
    
    // Добавляем к общим счетчикам
    totalDebug += obj.counters.dbg;
    totalInfo += obj.counters.info;
    totalWarn += obj.counters.warn;
    totalError += obj.counters.err;
    totalOther += obj.counters.other;
  }
  
  // Обновляем отображение счетчиков
  const cdbg = document.querySelector('.cdbg');
  const cinfo = document.querySelector('.cinfo');
  const cwarn = document.querySelector('.cwarn');
  const cerr = document.querySelector('.cerr');
  const cother = document.querySelector('.cother');
  
  if (cdbg) cdbg.textContent = totalDebug;
  if (cinfo) cinfo.textContent = totalInfo;
  if (cwarn) cwarn.textContent = totalWarn;
  if (cerr) cerr.textContent = totalError;
  if (cother) cother.textContent = totalOther;
  
  console.log(`Multi-view counters recalculated (tail: ${tailLines}):`, { totalDebug, totalInfo, totalWarn, totalError, totalOther });
}

// Функция для обновления видимости счетчиков
function updateCounterVisibility() {
  // Обновляем старые кнопки счетчиков (только для legacy интерфейса)
  const debugBtn = document.querySelector('.debug-btn');
  const infoBtn = document.querySelector('.info-btn');
  const warnBtn = document.querySelector('.warn-btn');
  const errorBtn = document.querySelector('.error-btn');
  const otherBtn = document.querySelector('.other-btn');
  
  if (debugBtn) {
    debugBtn.classList.toggle('disabled', !state.levels.debug);
  }
  if (infoBtn) {
    infoBtn.classList.toggle('disabled', !state.levels.info);
  }
  if (warnBtn) {
    warnBtn.classList.toggle('disabled', !state.levels.warn);
  }
  if (errorBtn) {
    errorBtn.classList.toggle('disabled', !state.levels.err);
  }
  if (otherBtn) {
    otherBtn.classList.toggle('disabled', !state.levels.other);
  }
}

// Функция для управления видимостью кнопок LogLevels
function updateLogLevelsVisibility() {
  const singleViewLevels = document.querySelector('.single-view-levels');
  const multiViewLevels = document.querySelectorAll('.multi-view-levels');
  
  // Проверяем, есть ли выбранные контейнеры
  const hasSelectedContainers = state.selectedContainers.length > 0 || state.current;
  
  // Управляем видимостью кнопок в single view
  if (singleViewLevels) {
    if (hasSelectedContainers) {
      singleViewLevels.style.display = 'flex';
    } else {
      singleViewLevels.style.display = 'none';
    }
  }
  
  // Управляем видимостью кнопок в multi view
  multiViewLevels.forEach(levelsContainer => {
    if (hasSelectedContainers) {
      levelsContainer.style.display = 'flex';
    } else {
      levelsContainer.style.display = 'none';
    }
  });
}

// Функция для обновления логов и счетчиков
async function refreshLogsAndCounters() {
  if (state.multiViewMode && state.selectedContainers.length > 0) {
    // Обновляем мультипросмотр
    console.log('Refreshing multi-view for containers:', state.selectedContainers);
    
    // Очищаем логи в мультипросмотре перед обновлением
    state.selectedContainers.forEach(containerId => {
      const multiViewLog = document.querySelector(`.multi-view-log[data-container-id="${containerId}"]`);
      if (multiViewLog) {
        multiViewLog.textContent = 'Refreshing...';
      }
      // Очищаем буфер логов для мультипросмотра
      const obj = state.open[containerId];
      if (obj && obj.allLogs) {
        obj.allLogs = [];
      }
    });
    
    // Перезапускаем WebSocket соединения для всех выбранных контейнеров
    state.selectedContainers.forEach(containerId => {
      closeWs(containerId);
      const service = state.services.find(s => s.id === containerId);
      if (service) {
        openMultiViewWs(service);
      }
    });
    
    // Пересчитываем счетчики на основе отображаемых логов
    setTimeout(() => {
      recalculateMultiViewCounters();
      // Применяем настройки wrap text после обновления
      applyWrapSettings();
    }, 1000); // Небольшая задержка для завершения переподключения
    
  } else if (state.current) {
    // Обычный режим просмотра
    console.log('Refreshing logs and counters for:', state.current.id);
    
    // Очищаем логи перед обновлением
    if (els.logContent) {
      els.logContent.textContent = 'Refreshing...';
    }
    
    // Перезапускаем WebSocket соединение для получения свежих логов
    const currentId = state.current.id;
    closeWs(currentId);
    
    // Добавляем небольшую задержку для корректного закрытия соединения
    await new Promise(resolve => setTimeout(resolve, 100));
    
    // Находим обновленный контейнер в списке
    const updatedContainer = state.services.find(s => s.id === currentId);
    if (updatedContainer) {
      // Переключаемся на обновленный контейнер
      await switchToSingle(updatedContainer);
      
      // Очищаем лишние пустые строки после переключения
      if (els.logContent) {
        cleanSingleViewEmptyLines(els.logContent);
        cleanDuplicateLines(els.logContent);
      }
    }
    
    // Пересчитываем счетчики на основе отображаемых логов
    setTimeout(() => {
      recalculateCounters();
      // Применяем настройки wrap text после обновления
      applyWrapSettings();
    }, 1000); // Небольшая задержка для завершения переподключения
    
  } else {
    console.log('No container selected');
  }
}

// Controls
els.clearBtn.onclick = ()=> { 
  // Очищаем обычный просмотр
  Object.values(state.open).forEach(o => {
    if (o.logEl) o.logEl.textContent = '';
    if (o.allLogs) o.allLogs = []; // Очищаем буфер логов
  });
  
  // Очищаем современный интерфейс
  if (els.logContent) {
    els.logContent.textContent = '';
    // Очищаем лишние пустые строки после очистки
    cleanSingleViewEmptyLines(els.logContent);
    cleanDuplicateLines(els.logContent);
  }
  
  // Очищаем мультипросмотр
  if (state.multiViewMode) {
    state.selectedContainers.forEach(containerId => {
      const multiViewLog = document.querySelector(`.multi-view-log[data-container-id="${containerId}"]`);
      if (multiViewLog) {
        multiViewLog.textContent = '';
      }
      // Очищаем буфер логов для мультипросмотра
      const obj = state.open[containerId];
      if (obj && obj.allLogs) {
        obj.allLogs = [];
      }
    });
  }
  
  // Сбрасываем счетчики
  document.querySelectorAll('.cdbg, .cinfo, .cwarn, .cerr').forEach(el => {
    el.textContent = '0';
  });
  
  // Сбрасываем счетчики в объектах состояния
  Object.values(state.open).forEach(obj => {
    if (obj.counters) {
      obj.counters = {dbg: 0, info: 0, warn: 0, err: 0};
    }
  });
};

els.refreshBtn.onclick = async () => {
  console.log('Refreshing services...');
  await fetchServices();
  
  if (state.multiViewMode && state.selectedContainers.length > 0) {
    // В Multi View режиме обновляем все выбранные контейнеры
    console.log('Refreshing Multi View mode with containers:', state.selectedContainers);
    
    // Закрываем все текущие соединения
    state.selectedContainers.forEach(containerId => {
      closeWs(containerId);
    });
    
    // Перезапускаем соединения для всех выбранных контейнеров
    state.selectedContainers.forEach(containerId => {
      const service = state.services.find(s => s.id === containerId);
      if (service) {
        openMultiViewWs(service);
      }
    });
    
    // Очищаем логи в мультипросмотре
    state.selectedContainers.forEach(containerId => {
      const multiViewLog = document.querySelector(`.multi-view-log[data-container-id="${containerId}"]`);
      if (multiViewLog) {
        multiViewLog.textContent = 'Refreshing...';
      }
    });
    
  } else if (state.current) {
    // Обычный режим просмотра
    console.log('Reconnecting to current container:', state.current.id);
    const currentId = state.current.id;
    
    // Закрываем текущее соединение
    closeWs(currentId);
    
    // Находим обновленный контейнер в списке
    const updatedContainer = state.services.find(s => s.id === currentId);
    if (updatedContainer) {
      // Переключаемся на обновленный контейнер
      await switchToSingle(updatedContainer);
    } else {
      // Если контейнер больше не существует, не открываем автоматически первый доступный
      // Пользователь сам выберет нужный контейнер
      console.log('Container no longer exists, not auto-opening first available container');
    }
  }
};

// Обработчик для кнопок refresh логов (в log-header и в header)
    document.querySelectorAll('.log-refresh-btn').forEach(btn=>{
  btn.addEventListener('click', refreshLogsAndCounters);
});

// Обработчик для кнопки update (AJAX autoupdate toggle)
if (els.ajaxUpdateBtn) {
  console.log('Инициализация обработчика клика для кнопки update');
  els.ajaxUpdateBtn.addEventListener('click', () => {
    console.log('Клик по кнопке update - вызываем toggleAjaxLogUpdate()');
    toggleAjaxLogUpdate();
  });
} else {
  console.error('Кнопка ajaxUpdateBtn не найдена при инициализации обработчика!');
}

// Обработчики для счетчиков
function addCounterClickHandlers() {
  // Назначаем обработчики на все дубликаты кнопок (и в log-header, и в header-compact-controls)
  const debugButtons = document.querySelectorAll('.debug-btn');
  const infoButtons = document.querySelectorAll('.info-btn');
  const warnButtons = document.querySelectorAll('.warn-btn');
  const errorButtons = document.querySelectorAll('.error-btn');
  const otherButtons = document.querySelectorAll('.other-btn');
  
  debugButtons.forEach(debugBtn => debugBtn.onclick = () => {
      state.levels.debug = !state.levels.debug;
      updateCounterVisibility();
      refreshAllLogs();
      // Добавляем refresh для обновления логов
      if (state.current) {
        refreshLogsAndCounters();
      }
      // Обновляем multi-view если он активен
      if (state.multiViewMode) {
        refreshAllLogs();
      }
  });
  
  infoButtons.forEach(infoBtn => infoBtn.onclick = () => {
      state.levels.info = !state.levels.info;
      updateCounterVisibility();
      refreshAllLogs();
      // Добавляем refresh для обновления логов
      if (state.current) {
        refreshLogsAndCounters();
      }
      // Обновляем multi-view если он активен
      if (state.multiViewMode) {
        refreshAllLogs();
      }
  });
  
  warnButtons.forEach(warnBtn => warnBtn.onclick = () => {
      state.levels.warn = !state.levels.warn;
      updateCounterVisibility();
      refreshAllLogs();
      // Добавляем refresh для обновления логов
      if (state.current) {
        refreshLogsAndCounters();
      }
      // Обновляем multi-view если он активен
      if (state.multiViewMode) {
        refreshAllLogs();
      }
  });
  
  errorButtons.forEach(errorBtn => errorBtn.onclick = () => {
      state.levels.err = !state.levels.err;
      updateCounterVisibility();
      refreshAllLogs();
      // Добавляем refresh для обновления логов
      if (state.current) {
        refreshLogsAndCounters();
      }
      // Обновляем multi-view если он активен
      if (state.multiViewMode) {
        refreshAllLogs();
      }
  });
  
  otherButtons.forEach(otherBtn => otherBtn.onclick = () => {
      state.levels.other = !state.levels.other;
      updateCounterVisibility();
      refreshAllLogs();
      // Добавляем refresh для обновления логов
      if (state.current) {
        refreshLogsAndCounters();
      }
      // Обновляем multi-view если он активен
      if (state.multiViewMode) {
        refreshAllLogs();
      }
  });
}


// Функция для добавления обработчиков мультивыбора проектов
function addMultiSelectHandlers() {
  const display = document.getElementById('projectSelectDisplay');
  const dropdown = document.getElementById('projectSelectDropdown');
  
  console.log('Adding multi-select handlers, elements found:', {display: !!display, dropdown: !!dropdown});
  
  if (display && dropdown) {
    // Обработчик клика по дисплею
    display.addEventListener('click', (e) => {
      e.stopPropagation();
      const isOpen = dropdown.style.display !== 'none';
      
      if (isOpen) {
        dropdown.style.display = 'none';
        display.classList.remove('active');
      } else {
        dropdown.style.display = 'block';
        display.classList.add('active');
      }
    });
    
    // Обработчики кликов по опциям
    dropdown.addEventListener('click', async (e) => {
      e.stopPropagation();
      
      const option = e.target.closest('.multi-select-option');
      if (!option) return;
      
      const checkbox = option.querySelector('input[type="checkbox"]');
      if (!checkbox) return;
      
      const value = option.getAttribute('data-value');
      
      // Специальная логика для "All Projects"
      if (value === 'all') {
        if (checkbox.checked) {
          // Если "All Projects" выбран, снимаем все остальные
          dropdown.querySelectorAll('.multi-select-option input[type="checkbox"]').forEach(cb => {
            if (cb !== checkbox) cb.checked = false;
          });
        }
      } else {
        // Если выбран конкретный проект, снимаем "All Projects"
        const allCheckbox = dropdown.querySelector('#project-all');
        if (allCheckbox) allCheckbox.checked = false;
      }
      
      // Обновляем отображение и загружаем сервисы
      const selectedProjects = getSelectedProjects();
      updateMultiSelect(selectedProjects);
      await fetchServices();
    });
    
    // Закрытие dropdown при клике вне его
    document.addEventListener('click', (e) => {
      if (!display.contains(e.target) && !dropdown.contains(e.target)) {
        dropdown.style.display = 'none';
        display.classList.remove('active');
      }
    });
  }
}

// Функция для показа уведомления о горячих клавишах
function showHotkeysNotification() {
  // Создаем уведомление
  const notification = document.createElement('div');
  notification.className = 'hotkeys-notification';
  notification.innerHTML = `
    <div class="notification-content">
      <h4><i class="fas fa-keyboard"></i> Горячие клавиши</h4>
      <ul>
        <li><kbd>[</kbd> <kbd>]</kbd> - Навигация между контейнерами</li>
        <li><kbd>Ctrl</kbd> + <kbd>R</kbd> или <kbd>Ctrl</kbd> + <kbd>K</kbd> - Обновить логи</li>
        <li><kbd>Ctrl</kbd> + <kbd>B</kbd> - Свернуть/развернуть панель</li>
        <li>Кнопка <i class="fas fa-chevron-left"></i> - управление панелью</li>
      </ul>
      <button class="notification-close" onclick="this.parentElement.parentElement.remove()">
        <i class="fas fa-times"></i>
      </button>
    </div>
  `;
  
  document.body.appendChild(notification);
  
  // Автоматически скрываем через 8 секунд
  setTimeout(() => {
    if (notification.parentElement) {
      notification.remove();
    }
  }, 8000);
}

// Функция для сворачивания/разворачивания sidebar и header
function toggleSidebar() {
  if (els.sidebar) {
    const isCollapsed = els.sidebar.classList.contains('collapsed');
    
    if (isCollapsed) {
      // Разворачиваем sidebar
      els.sidebar.classList.remove('collapsed');
      els.sidebarToggle.innerHTML = '<i class="fas fa-chevron-left"></i>';
      els.sidebarToggle.title = 'Свернуть панель (Ctrl+B / Ctrl+И)';
      localStorage.setItem('lb_sidebar_collapsed', 'false');
    } else {
      // Сворачиваем sidebar
      els.sidebar.classList.add('collapsed');
      els.sidebarToggle.innerHTML = '<i class="fas fa-chevron-right"></i>';
      els.sidebarToggle.title = 'Развернуть панель (Ctrl+B / Ctrl+И)';
      localStorage.setItem('lb_sidebar_collapsed', 'true');
    }
    
    // Принудительно обновляем стили логов после переключения sidebar
    setTimeout(() => {
      updateLogStyles();
      
      // Дополнительная проверка для multi-view логов
      if (state.multiViewMode) {
        console.log('Sidebar toggle: Force fixing multi-view styles');
        forceFixMultiViewStyles();
      }
    }, 100);
  }
}

// Функция для принудительного исправления стилей multi-view логов
function forceFixMultiViewStyles() {
  const multiViewLogs = document.querySelectorAll('.multi-view-content .multi-view-log');
  console.log(`Force fixing styles for ${multiViewLogs.length} multi-view logs`);
  
  multiViewLogs.forEach((log, index) => {
    const containerId = log.getAttribute('data-container-id');
    console.log(`Force fixing multi-view log ${index + 1} for container: ${containerId}`);
    
    // Универсальное исправление для всех контейнеров
    log.style.setProperty('height', '100%', 'important');
    log.style.setProperty('overflow', 'auto', 'important');
    log.style.setProperty('max-height', 'none', 'important');
    log.style.setProperty('display', 'block', 'important');
    log.style.setProperty('min-height', '200px', 'important');
    log.style.setProperty('position', 'relative', 'important');
    log.style.setProperty('flex', '1', 'important');
    log.style.setProperty('min-height', '0', 'important');
    log.style.setProperty('width', '100%', 'important');
    log.style.setProperty('box-sizing', 'border-box', 'important');
    
    // Принудительно вызываем пересчет layout
    log.style.setProperty('transform', 'translateZ(0)', 'important');
    
    // Устанавливаем универсальные inline стили для всех контейнеров
    const currentStyle = log.getAttribute('style') || '';
    const newStyle = currentStyle + '; height: 100% !important; overflow: auto !important; flex: 1 !important; min-height: 0 !important; width: 100% !important; box-sizing: border-box !important; display: block !important; position: relative !important;';
    log.setAttribute('style', newStyle);
    
    // Проверяем и исправляем родительские элементы для всех контейнеров
    const parentContent = log.closest('.multi-view-content');
    if (parentContent) {
      parentContent.style.setProperty('display', 'flex', 'important');
      parentContent.style.setProperty('flex-direction', 'column', 'important');
      parentContent.style.setProperty('overflow', 'hidden', 'important');
      parentContent.style.setProperty('height', '100%', 'important');
    }
    
    const parentPanel = log.closest('.multi-view-panel');
    if (parentPanel) {
      parentPanel.style.setProperty('display', 'flex', 'important');
      parentPanel.style.setProperty('flex-direction', 'column', 'important');
      parentPanel.style.setProperty('overflow', 'hidden', 'important');
      parentPanel.style.setProperty('height', '100%', 'important');
    }
  });
  
  // Также исправляем стили для multi-view-content контейнеров
  const multiViewContents = document.querySelectorAll('.multi-view-content');
  multiViewContents.forEach(content => {
    content.style.setProperty('display', 'flex', 'important');
    content.style.setProperty('flex-direction', 'column', 'important');
    content.style.setProperty('overflow', 'hidden', 'important');
    content.style.setProperty('height', '100%', 'important');
  });
  
  // Универсальное исправление для всех контейнеров
  multiViewLogs.forEach(log => {
    console.log(`Universal fix for container:`, log.getAttribute('data-container-id'));
    
    // Принудительно устанавливаем все стили заново для всех контейнеров
    log.style.cssText = 'height: 100% !important; overflow: auto !important; max-height: none !important; display: block !important; min-height: 200px !important; position: relative !important; flex: 1 !important; min-height: 0 !important; width: 100% !important; box-sizing: border-box !important;';
    
    // Принудительно вызываем пересчет layout
    log.style.setProperty('transform', 'translateZ(0)', 'important');
  });
  
  // Применяем настройки wrap text после исправления стилей
  applyWrapSettings();
}

// Функция для обновления стилей логов
function updateLogStyles() {
  const isCollapsed = els.sidebar && els.sidebar.classList.contains('collapsed');
  
  // Обновляем стили для single-view логов
  const singleViewLogs = document.querySelectorAll('.single-view-content .log');
  singleViewLogs.forEach(log => {
    if (isCollapsed) {
      log.style.height = 'calc(100vh - var(--header-height))';
      log.style.overflow = 'auto';
    } else {
      log.style.height = '100%';
      log.style.overflow = 'auto';
    }
  });
  
  // Обновляем стили для multi-view логов (более агрессивно)
  const multiViewLogs = document.querySelectorAll('.multi-view-content .multi-view-log');
  console.log(`Found ${multiViewLogs.length} multi-view logs to update`);
  
  multiViewLogs.forEach((log, index) => {
    const containerId = log.getAttribute('data-container-id');
    console.log(`Updating multi-view log ${index + 1}/${multiViewLogs.length} for container: ${containerId}`);
    
    // Принудительно устанавливаем правильные стили независимо от состояния sidebar
    log.style.setProperty('height', '100%', 'important');
    log.style.setProperty('overflow', 'auto', 'important');
    log.style.setProperty('max-height', 'none', 'important');
    log.style.setProperty('display', 'block', 'important');
    log.style.setProperty('min-height', '200px', 'important');
    log.style.setProperty('position', 'relative', 'important');
    log.style.setProperty('flex', '1', 'important');
    log.style.setProperty('min-height', '0', 'important');
    
    // Принудительно вызываем пересчет layout
    log.style.setProperty('transform', 'translateZ(0)', 'important');
  });
  
  // Также обновляем стили для multi-view-content контейнеров
  const multiViewContents = document.querySelectorAll('.multi-view-content');
  multiViewContents.forEach(content => {
    if (isCollapsed) {
      // В свернутом состоянии multi-view-content должен иметь правильную высоту
      content.style.setProperty('height', 'calc(100vh - var(--header-height) - 60px)', 'important');
    } else {
      content.style.setProperty('height', '100%', 'important');
    }
    content.style.setProperty('overflow', 'hidden', 'important');
    content.style.setProperty('display', 'flex', 'important');
    content.style.setProperty('flex-direction', 'column', 'important');
  });
  
  // Применяем настройки wrap text
  applyWrapSettings();
  
  console.log('Log styles updated, sidebar collapsed:', isCollapsed, 'multi-view logs found:', multiViewLogs.length);
  
  // Принудительно исправляем стили multi-view логов
  forceFixMultiViewStyles();
  
  // Дополнительная проверка через 500ms для multi view логов
  if (multiViewLogs.length > 0) {
    setTimeout(() => {
      console.log('Performing delayed update for multi-view logs...');
      forceFixMultiViewStyles();
    }, 500);
  }
}

// Mobile menu toggle
if (els.mobileToggle) {
  els.mobileToggle.onclick = () => {
    const sidebar = document.querySelector('.sidebar');
    if (sidebar) {
      sidebar.classList.toggle('open');
    }
  };
}

// Функция для показа/скрытия модального окна с горячими клавишами
function toggleHotkeysModal() {
  if (els.hotkeysModal) {
    const isVisible = els.hotkeysModal.classList.contains('show');
    if (isVisible) {
      els.hotkeysModal.classList.remove('show');
    } else {
      els.hotkeysModal.classList.add('show');
    }
  }
}

// Sidebar toggle button
if (els.sidebarToggle) {
  els.sidebarToggle.onclick = toggleSidebar;
}



// Modal close button
if (els.hotkeysModalClose) {
  els.hotkeysModalClose.onclick = toggleHotkeysModal;
}

// Close modal on background click
if (els.hotkeysModal) {
  els.hotkeysModal.onclick = (e) => {
    if (e.target === els.hotkeysModal) {
      toggleHotkeysModal();
    }
  };
}

// Collapsible sections
document.addEventListener('DOMContentLoaded', () => {
  // Обработчики для сворачивания секций
  document.querySelectorAll('.control-header').forEach(header => {
    header.addEventListener('click', (e) => {
      if (e.target.closest('.collapse-btn')) return; // Не сворачиваем при клике на кнопку
      
      const group = header.closest('.control-group');
      
      // Если секция минимизирована, сначала разворачиваем
      if (group.classList.contains('minimized')) {
        group.classList.remove('minimized');
        group.classList.add('collapsed');
        const section = group.dataset.section;
        localStorage.setItem(`lb_minimized_${section}`, 'false');
        localStorage.setItem(`lb_collapsed_${section}`, 'true');
      } else {
        // Обычное сворачивание/разворачивание
        group.classList.toggle('collapsed');
        const section = group.dataset.section;
        localStorage.setItem(`lb_collapsed_${section}`, group.classList.contains('collapsed'));
        localStorage.setItem(`lb_minimized_${section}`, 'false');
      }
    });
  });
  
  // Обработчики для кнопок сворачивания
  document.querySelectorAll('.collapse-btn').forEach(btn => {
    btn.addEventListener('click', (e) => {
      e.stopPropagation();
      const group = btn.closest('.control-group');
      
      // Если секция минимизирована, сначала разворачиваем
      if (group.classList.contains('minimized')) {
        group.classList.remove('minimized');
        group.classList.add('collapsed');
        const section = group.dataset.section;
        localStorage.setItem(`lb_minimized_${section}`, 'false');
        localStorage.setItem(`lb_collapsed_${section}`, 'true');
      } else {
        // Обычное сворачивание/разворачивание
        group.classList.toggle('collapsed');
        const section = group.dataset.section;
        localStorage.setItem(`lb_collapsed_${section}`, group.classList.contains('collapsed'));
        localStorage.setItem(`lb_minimized_${section}`, 'false');
      }
    });
  });
  
  // Восстанавливаем состояние секций из localStorage или сворачиваем по умолчанию
  document.querySelectorAll('.control-group.collapsible').forEach(group => {
    const section = group.dataset.section;
    const savedCollapsed = localStorage.getItem(`lb_collapsed_${section}`);
    const savedMinimized = localStorage.getItem(`lb_minimized_${section}`);
    
    // Если состояние не сохранено, сворачиваем по умолчанию
    if (savedCollapsed === null && savedMinimized === null) {
      group.classList.add('collapsed');
      localStorage.setItem(`lb_collapsed_${section}`, 'true');
      localStorage.setItem(`lb_minimized_${section}`, 'false');
    } else if (savedMinimized === 'true') {
      group.classList.add('minimized');
      group.classList.remove('collapsed');
    } else if (savedCollapsed === 'true') {
      group.classList.add('collapsed');
      group.classList.remove('minimized');
    }
  });
  
  // Обработчик для кнопки Options
  if (els.optionsBtn) {
    els.optionsBtn.addEventListener('click', () => {
      const sidebarControls = document.querySelector('.sidebar-controls');
      const isHidden = sidebarControls.classList.contains('hidden');
      
      if (isHidden) {
        // Если сайдбар свернут, сначала разворачиваем его
        if (els.sidebar.classList.contains('collapsed')) {
          toggleSidebar();
        }
        // Показываем настройки
        sidebarControls.classList.remove('hidden');
        els.optionsBtn.classList.remove('active');
        els.optionsBtn.title = 'Скрыть настройки';
        localStorage.setItem('lb_options_hidden', 'false');
      } else {
        // Скрываем настройки
        sidebarControls.classList.add('hidden');
        els.optionsBtn.classList.add('active');
        els.optionsBtn.title = 'Показать настройки';
        localStorage.setItem('lb_options_hidden', 'true');
      }
    });
    
    // Восстанавливаем состояние кнопки Options (по умолчанию скрыто)
    const optionsHidden = localStorage.getItem('lb_options_hidden');
    if (optionsHidden === null || optionsHidden === 'true') {
      document.querySelector('.sidebar-controls').classList.add('hidden');
      els.optionsBtn.classList.add('active');
      els.optionsBtn.title = 'Показать настройки';
      localStorage.setItem('lb_options_hidden', 'true');
    }
    
    // Инициализируем состояние кнопок уровней логирования
    initializeLevelButtons();
  }

  // Обработчик для кнопки выхода
  if (els.logoutBtn) {
    els.logoutBtn.addEventListener('click', async () => {
      if (confirm('Вы уверены, что хотите выйти?')) {
        try {
          // Вызываем API для выхода
          await fetch('/api/auth/logout', {
            method: 'POST',
            headers: {
              'Authorization': `Bearer ${localStorage.getItem('access_token')}`
            }
          });
        } catch (error) {
          console.error('Logout error:', error);
        } finally {
          // Останавливаем автоматическую проверку WebSocket
          stopWebSocketStatusCheck();
          // Очищаем localStorage
          localStorage.removeItem('access_token');
          // Перенаправляем на страницу входа
          window.location.href = '/login';
        }
      }
    });
  }



  // Инициализируем стили логов при загрузке страницы
  updateLogStyles();
  
  // Применяем настройки wrap text при загрузке
  applyWrapSettings();
  
  // Дополнительная проверка для multi-view логов при загрузке
  setTimeout(() => {
    if (state.multiViewMode) {
      console.log('Initialization: Force fixing multi-view styles');
      forceFixMultiViewStyles();
    }
  }, 1000);

  // Обработчик для кнопки помощи
  if (els.helpBtn) {
    const helpTooltip = document.getElementById('helpTooltip');
    let tooltipTimeout;

    // Показ модального окна при клике
    els.helpBtn.addEventListener('click', () => {
      showHelpTooltip();
    });

    // Кнопка закрытия модального окна
    const helpTooltipClose = document.getElementById('helpTooltipClose');
    if (helpTooltipClose) {
      helpTooltipClose.addEventListener('click', () => {
        hideHelpTooltip();
      });
    }

    // Закрытие по Escape
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape') {
        hideHelpTooltip();
      }
    });

    // Закрытие по клику вне модального окна
    helpTooltip.addEventListener('click', (e) => {
      if (e.target === helpTooltip) {
        hideHelpTooltip();
      }
    });
  }

});
if (els.snapshotBtn) {
  els.snapshotBtn.onclick = ()=>{ 
    if (state.multiViewMode && state.selectedContainers.length > 0) {
      // В Multi View режиме используем первый выбранный контейнер как ID для sendSnapshot
      // Функция sendSnapshot сама определит, что нужно скачать логи всех контейнеров
      sendSnapshot(state.selectedContainers[0]);
    } else if (state.current) {
      sendSnapshot(state.current.id);
    } else {
      alert('No container selected');
    }
  };
}
if (els.tail) {
  els.tail.onchange = ()=> {
    Object.keys(state.open).forEach(id=>{
      const svc = state.services.find(s=> s.id===id);
      if (!svc) return;
      
      // В multi view режиме используем openMultiViewWs
      if (state.multiViewMode && state.selectedContainers.includes(id)) {
        console.log(`Refresh: Using openMultiViewWs for ${svc.name} in multi view mode`);
        closeWs(id);
        openMultiViewWs(svc);
      } else {
        // В обычном режиме используем openWs
        const panel = els.grid.querySelector(`.panel[data-cid="${id}"]`);
        if (!panel) return;
        state.open[id].logEl.textContent='';
        closeWs(id); 
        openWs(svc, panel);
      }
    });
    
    // Обновляем современный интерфейс
    if (state.current && els.logContent) {
      els.logContent.textContent = 'Reconnecting...';
    }
    
    // Пересчитываем счетчики после изменения Tail Lines
    setTimeout(() => {
      if (state.multiViewMode) {
        recalculateMultiViewCounters();
      } else {
        recalculateCounters();
      }
    }, 1000); // Небольшая задержка для завершения переподключения
  };
}
if (els.wrapToggle) {
  els.wrapToggle.onchange = ()=> {
    applyWrapSettings();
  };
}

// Добавляем обработчики для autoscroll и pause
if (els.autoscroll) {
  els.autoscroll.onchange = ()=> {
    // Обновляем настройку автопрокрутки для всех открытых логов
    Object.keys(state.open).forEach(id => {
      const obj = state.open[id];
      if (obj && obj.wrapEl) {
        if (els.autoscroll.checked) {
          obj.wrapEl.scrollTop = obj.wrapEl.scrollHeight;
        }
      }
    });
    
    // Обновляем современный интерфейс
    if (state.current && els.logContent) {
      const logContent = document.querySelector('.log-content');
      if (logContent && els.autoscroll.checked) {
        logContent.scrollTop = logContent.scrollHeight;
      }
    }
    
    // Обновляем мультипросмотр
    if (state.multiViewMode) {
      state.selectedContainers.forEach(containerId => {
        const multiViewLog = document.querySelector(`.multi-view-log[data-container-id="${containerId}"]`);
        if (multiViewLog && els.autoscroll.checked) {
          multiViewLog.scrollTop = multiViewLog.scrollHeight;
        }
      });
    }
  };
}


// Обработчик для фильтра (если элемент существует)
if (els.filter) {
  els.filter.oninput = ()=> { 
    state.filter = els.filter.value.trim(); 
    refreshAllLogs();
    // Пересчитываем счетчики в зависимости от режима
    setTimeout(() => {
      if (state.multiViewMode) {
        recalculateMultiViewCounters();
      } else {
        recalculateCounters();
      }
    }, 100);
  };
}
// Обработчики для LogLevels (если элементы существуют)
if (els.lvlDebug) {
  els.lvlDebug.onchange = ()=> { 
    state.levels.debug = els.lvlDebug.checked; 
    updateCounterVisibility();
    refreshAllLogs();
    // Обновляем multi-view если он активен
    if (state.multiViewMode) {
      refreshAllLogs();
      setTimeout(() => {
        recalculateMultiViewCounters();
      }, 100);
    } else {
      // Пересчитываем счетчики для Single View
      setTimeout(() => {
        recalculateCounters();
      }, 100);
    }
  };
}
if (els.lvlInfo) {
  els.lvlInfo.onchange  = ()=> { 
    state.levels.info  = els.lvlInfo.checked; 
    updateCounterVisibility();
    refreshAllLogs();
    // Обновляем multi-view если он активен
    if (state.multiViewMode) {
      refreshAllLogs();
      setTimeout(() => {
        recalculateMultiViewCounters();
      }, 100);
    } else {
      // Пересчитываем счетчики для Single View
      setTimeout(() => {
        recalculateCounters();
      }, 100);
    }
  };
}
if (els.lvlWarn) {
  els.lvlWarn.onchange  = ()=> { 
    state.levels.warn  = els.lvlWarn.checked; 
    updateCounterVisibility();
    refreshAllLogs();
    // Обновляем multi-view если он активен
    if (state.multiViewMode) {
      refreshAllLogs();
      setTimeout(() => {
        recalculateMultiViewCounters();
      }, 100);
    } else {
      // Пересчитываем счетчики для Single View
      setTimeout(() => {
        recalculateCounters();
      }, 100);
    }
  };
}
if (els.lvlErr) {
  els.lvlErr.onchange   = ()=> { 
    state.levels.err   = els.lvlErr.checked; 
    updateCounterVisibility();
    refreshAllLogs();
    // Обновляем multi-view если он активен
    if (state.multiViewMode) {
      refreshAllLogs();
      setTimeout(() => {
        recalculateMultiViewCounters();
      }, 100);
    } else {
      // Пересчитываем счетчики для Single View
      setTimeout(() => {
        recalculateCounters();
      }, 100);
    }
  };
}
if (els.lvlOther) {
  els.lvlOther.onchange = ()=> { 
    state.levels.other = els.lvlOther.checked; 
    updateCounterVisibility();
    refreshAllLogs();
    // Обновляем multi-view если он активен
    if (state.multiViewMode) {
      refreshAllLogs();
      setTimeout(() => {
        recalculateMultiViewCounters();
      }, 100);
    } else {
      // Пересчитываем счетчики для Single View
      setTimeout(() => {
        recalculateCounters();
      }, 100);
    }
  };
}

// Обработчик изменения размера окна для обновления стилей multi-view логов
window.addEventListener('resize', () => {
  if (state.multiViewMode) {
    console.log('Window resize: Force fixing multi-view styles');
    setTimeout(() => {
      forceFixMultiViewStyles();
      
      // Дополнительно исправляем все контейнеры
      console.log('Window resize: Fixing all containers');
      if (window.fixAllContainers) {
        window.fixAllContainers();
      }
    }, 100);
  }
});

// Hotkeys: [ ] х ъ — navigation between containers, Ctrl/Cmd+R/K — refresh logs, Ctrl/Cmd+B/И — toggle sidebar
window.addEventListener('keydown', async (e)=>{
  // Проверяем, не находится ли фокус в поле ввода
  const activeElement = document.activeElement;
  const isInputActive = activeElement && (
    activeElement.tagName === 'INPUT' || 
    activeElement.tagName === 'TEXTAREA' || 
    activeElement.contentEditable === 'true'
  );
  
  // Если фокус в поле ввода, не обрабатываем горячие клавиши
  if (isInputActive) {
    return;
  }
  
  // Навигация между контейнерами по [ ] х ъ
  if (e.key==='[' || e.key==='х'){
    e.preventDefault();
    const idx = state.services.findIndex(s=> s.id===state.current?.id);
    if (idx>0) await switchToSingle(state.services[idx-1]);
  }
  if (e.key===']' || e.key==='ъ'){
    e.preventDefault();
    const idx = state.services.findIndex(s=> s.id===state.current?.id);
    if (idx>=0 && idx<state.services.length-1) await switchToSingle(state.services[idx+1]);
  }
  
  // Обновление логов по Ctrl/Cmd+R или Ctrl/Cmd+K
  if ((e.ctrlKey || e.metaKey) && (e.key==='r' || e.key==='к')){
    e.preventDefault();
    console.log('Hotkey refresh triggered:', e.key);
    await refreshLogsAndCounters();
  }
  
  // Сворачивание/разворачивание sidebar по Ctrl/Cmd+B/И
  if ((e.ctrlKey || e.metaKey) && (e.key==='b' || e.key==='и' || e.code==='KeyB')){
    e.preventDefault();
    toggleSidebar();
  }
  
  // Фокус на фильтр по Ctrl/Cmd+F
  if ((e.ctrlKey || e.metaKey) && (e.key==='f' || e.key==='а')){
    e.preventDefault();
    console.log('Ctrl+F pressed, els.filter:', els.filter);
    
    // Функция для фокусировки на фильтре
    const focusFilter = () => {
      // Сначала попробуем использовать els.filter
      let filterElement = els.filter;
      
      // Если els.filter не найден, попробуем найти элемент напрямую
      if (!filterElement) {
        console.log('els.filter not found, searching directly...');
        filterElement = document.getElementById('filter');
      }
      
      // Если элемент найден, фокусируемся на нем
      if (filterElement) {
        console.log('Focusing on filter element:', filterElement);
        try {
          filterElement.focus();
          filterElement.select();
          console.log('Filter focused successfully');
        } catch (error) {
          console.error('Error focusing filter:', error);
        }
      } else {
        console.error('Filter element not found anywhere!');
        // Попробуем еще раз через небольшую задержку
        setTimeout(() => {
          const retryElement = document.getElementById('filter');
          if (retryElement) {
            console.log('Filter found on retry, focusing...');
            retryElement.focus();
            retryElement.select();
          }
        }, 100);
      }
    };
    
    // Вызываем функцию фокусировки
    focusFilter();
  }
  


});

// Функция для переинициализации элементов
function reinitializeElements() {
  // Переинициализируем элементы, которые могут быть не найдены при первой загрузке
  els.filter = document.getElementById('filter');
  els.containerList = document.getElementById('containerList');
  els.logContent = document.getElementById('logContent');
  els.mobileToggle = document.getElementById('mobileToggle');
  els.optionsBtn = document.getElementById('optionsBtn');
  els.helpBtn = document.getElementById('helpBtn');
  els.logoutBtn = document.getElementById('logoutBtn');
  els.sidebar = document.getElementById('sidebar');
  els.sidebarToggle = document.getElementById('sidebarToggle');
  els.header = document.getElementById('header');
  
  console.log('Elements reinitialized:', {
    filter: !!els.filter,
    containerList: !!els.containerList,
    logContent: !!els.logContent,
    sidebar: !!els.sidebar,
    sidebarToggle: !!els.sidebarToggle
  });
}

// Инициализация
(async function init() {
  console.log('Initializing LogBoard+...');
  
  // Переинициализируем элементы
  reinitializeElements();
  
  // Инициализируем состояние WebSocket
  setWsState('off');
  
  // Дополнительно инициализируем элементы после полной загрузки DOM
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', reinitializeElements);
  }
  
  // Инициализируем элементы после полной загрузки страницы
  window.addEventListener('load', reinitializeElements);
  
  // Обработчик для правильной очистки при перезагрузке страницы
  window.addEventListener('beforeunload', () => {
    // Останавливаем автоматическую проверку WebSocket
    stopWebSocketStatusCheck();
    
    // Останавливаем периодическое обновление контейнеров
    stopContainerUpdate();
    
    // Закрываем все WebSocket соединения
    Object.keys(state.open).forEach(id => {
      const obj = state.open[id];
      if (obj && obj.ws) {
        try {
          obj.ws.close();
        } catch (e) {
          // Игнорируем ошибки при закрытии
        }
      }
    });
    
    // Очищаем состояние
    state.open = {};
  });
  
  // Проверяем авторизацию
  const token = localStorage.getItem('access_token');
  if (!token) {
    console.log('No access token found, redirecting to login');
    window.location.href = '/login';
    return;
  }
  
  // Проверяем валидность токена
  try {
    const response = await fetch('/api/auth/me', {
      headers: {
        'Authorization': `Bearer ${token}`
      }
    });
    
    if (!response.ok) {
      console.log('Invalid token, redirecting to login');
      localStorage.removeItem('access_token');
      window.location.href = '/login';
      return;
    }
  } catch (error) {
    console.error('Error checking auth:', error);
    localStorage.removeItem('access_token');
    window.location.href = '/login';
    return;
  }
  
  console.log('Elements found:', {
    containerList: !!els.containerList,

    logContent: !!els.logContent,
    mobileToggle: !!els.mobileToggle,
    themeSwitch: !!els.themeSwitch
  });
  
  // Проверяем header project select
  const headerSelect = document.getElementById('projectSelectHeader');
  console.log('Header project select found during init:', !!headerSelect);
  
  await fetchProjects();
  await fetchServices();
  
  // Проверяем состояние WebSocket после загрузки сервисов
  setTimeout(() => {
    console.log('Проверка состояния WebSocket после загрузки сервисов');
    setWsState(determineWsState());
  }, 1000);
  
  // Запускаем автоматическую проверку состояния WebSocket
  startWebSocketStatusCheck();
  
  // Добавляем обработчик клика для кнопки WebSocket статуса
  if (els.wsstate) {
    els.wsstate.addEventListener('click', () => {
      console.log('Ручная проверка состояния WebSocket');
      checkWebSocketStatus();
    });
  }
  
  // Добавляем обработчики для сворачивания секций
  addSectionToggleHandlers();
  
  // Запускаем периодическое обновление контейнеров
  startContainerUpdate();
  
  // Проверяем, есть ли сохраненный контейнер в localStorage
  const savedContainerId = getSelectedContainerFromStorage();
  if (savedContainerId) {
    console.log('Found saved container, switching to it:', savedContainerId);
    const savedService = state.services.find(s => s.id === savedContainerId);
    if (savedService) {
      // Добавляем контейнер в выбранные
      state.selectedContainers = [savedContainerId];
      // Переключаемся на сохраненный контейнер
      await switchToSingle(savedService);
      // Очищаем сохраненный контейнер из localStorage
      saveSelectedContainer(null);
    } else {
      console.log('Saved container not found in services, clearing localStorage');
      saveSelectedContainer(null);
    }
  }
  
  // Инициализируем видимость счетчиков
  updateCounterVisibility();
  
  // Обновляем состояние чекбоксов после загрузки сервисов
  updateContainerSelectionUI();
  
  // Восстанавливаем состояние sidebar
  const sidebarCollapsed = localStorage.getItem('lb_sidebar_collapsed');
  if (sidebarCollapsed === 'true' && els.sidebar && els.sidebarToggle) {
    els.sidebar.classList.add('collapsed');
    els.sidebarToggle.innerHTML = '<i class="fas fa-chevron-right"></i>';
    els.sidebarToggle.title = 'Развернуть панель (Ctrl+B / Ctrl+И)';
  }
  

  
  // Показываем подсказку о горячих клавишах при первом запуске
  const hotkeysShown = localStorage.getItem('lb_hotkeys_shown');
  if (!hotkeysShown) {
    setTimeout(() => {
      showHotkeysNotification();
      localStorage.setItem('lb_hotkeys_shown', 'true');
    }, 2000);
  }
  
  // Добавляем обработчики для счетчиков
  addCounterClickHandlers();
  
      // Добавляем обработчик для выпадающего списка проектов в заголовке
    addMultiSelectHandlers();
    
    // Загружаем и отображаем исключенные контейнеры
    loadExcludedContainers().then(containers => {
      renderExcludedContainers(containers);
    });
    
    // Добавляем обработчики для исключенных контейнеров
    const addExcludedBtn = document.getElementById('addExcludedContainer');
    const newExcludedInput = document.getElementById('newExcludedContainer');
    
    if (addExcludedBtn) {
      addExcludedBtn.onclick = addExcludedContainer;
    }
    
    if (newExcludedInput) {
      newExcludedInput.onkeypress = (e) => {
        if (e.key === 'Enter') {
          addExcludedContainer();
        }
      };
    }
    
    // Добавляем обработчики для чекбоксов контейнеров
    document.addEventListener('change', (e) => {
      if (e.target.classList.contains('container-checkbox')) {
        const containerId = e.target.getAttribute('data-container-id');
        toggleContainerSelection(containerId);
      }
      
      // Обработчик изменения tail lines
      if (e.target.id === 'tail') {
        console.log('Tail lines changed to:', e.target.value);
        if (state.multiViewMode) {
          // В multi view применяем новое ограничение к уже отображаемым логам
          const tailLines = parseInt(e.target.value) || 50;
          console.log(`Applying tail lines limit ${tailLines} to ${state.selectedContainers.length} containers:`, state.selectedContainers);
          
          // Проверяем все элементы multi-view-log на странице
          const allMultiViewLogs = document.querySelectorAll('.multi-view-log');
          console.log(`Found ${allMultiViewLogs.length} multi-view-log elements on page:`, Array.from(allMultiViewLogs).map(el => el.getAttribute('data-container-id')));
          
          state.selectedContainers.forEach(containerId => {
            console.log(`Processing container ${containerId}...`);
            
            // Ищем элемент несколькими способами
            let multiViewLog = document.querySelector(`.multi-view-log[data-container-id="${containerId}"]`);
            
            if (!multiViewLog) {
              console.warn(`Container ${containerId} not found with data-container-id, trying alternative search...`);
              // Попробуем найти по другому селектору
              multiViewLog = document.querySelector(`[data-container-id="${containerId}"]`);
            }
            
            if (multiViewLog) {
              console.log(`Found multi-view-log for container ${containerId}, current lines:`, multiViewLog.querySelectorAll('.line').length);
              
              // Получаем все строки логов
              const logLines = Array.from(multiViewLog.querySelectorAll('.line'));
              console.log(`Container ${containerId}: ${logLines.length} log lines found`);
              
              if (logLines.length > tailLines) {
                // Удаляем лишние строки с начала
                const linesToRemove = logLines.length - tailLines;
                console.log(`Removing ${linesToRemove} lines from container ${containerId}`);
                
                // Удаляем первые N строк
                logLines.slice(0, linesToRemove).forEach(line => {
                  line.remove();
                });
                
                const remainingLines = multiViewLog.querySelectorAll('.line').length;
                console.log(`Container ${containerId} now has ${remainingLines} lines after trimming`);
              } else {
                console.log(`Container ${containerId} has ${logLines.length} lines, no trimming needed (limit: ${tailLines})`);
              }
            } else {
              console.error(`Multi-view log element not found for container ${containerId}`);
              console.error(`Available multi-view-log elements:`, Array.from(document.querySelectorAll('.multi-view-log')).map(el => ({
                containerId: el.getAttribute('data-container-id'),
                className: el.className,
                parent: el.parentElement?.className
              })));
            }
          });
        }
      }
    });
    
    // Добавляем обработчики кликов на label чекбоксов
    document.addEventListener('click', (e) => {
      if (e.target.classList.contains('container-checkbox-label')) {
        e.preventDefault();
        e.stopPropagation();
        const label = e.target;
        const checkbox = label.previousElementSibling;
        if (checkbox && checkbox.classList.contains('container-checkbox')) {
          checkbox.checked = !checkbox.checked;
          const containerId = checkbox.getAttribute('data-container-id');
          toggleContainerSelection(containerId);
        }
      }
    });

    // Обработчики для кнопок уровней логирования в заголовках
    document.addEventListener('click', (e) => {
      if (e.target.closest('.level-btn')) {
        const levelBtn = e.target.closest('.level-btn');
        const level = levelBtn.getAttribute('data-level');
        const containerId = levelBtn.getAttribute('data-container-id');
        
        console.log(`Клик по кнопке уровня логирования: level=${level}, containerId=${containerId}, multiViewMode=${state.multiViewMode}`);
        
        // Переключаем состояние кнопки
        const isActive = levelBtn.classList.contains('active');
        levelBtn.classList.toggle('active');
        
        // Обновляем состояние уровней логирования
        if (containerId) {
          // Для multi-view: конкретный контейнер
          if (!state.containerLevels) {
            state.containerLevels = {};
          }
          if (!state.containerLevels[containerId]) {
            state.containerLevels[containerId] = {debug: true, info: true, warn: true, err: true, other: true};
          }
          state.containerLevels[containerId][level] = !isActive;
          
          // Сохраняем состояние кнопок loglevels в localStorage
          saveLogLevelsState();
          
          // Обновляем видимость логов только для этого контейнера
          updateContainerLogVisibility(containerId);
          
          // Пересчитываем счетчики только для этого контейнера
          setTimeout(() => {
            updateContainerCounters(containerId);
          }, 100);
          
          // Обновляем видимость логов для всех контейнеров в multi-view
          // чтобы убедиться, что изменения применились только к нужному контейнеру
          state.selectedContainers.forEach(id => {
            if (id !== containerId) {
              updateContainerLogVisibility(id);
            }
          });
        } else {
          // Для single-view: глобальные настройки
          state.levels[level] = !isActive;
          
          // Сохраняем состояние кнопок loglevels в localStorage
          saveLogLevelsState();
          
          // Обновляем видимость логов только для текущего контейнера
          if (state.current) {
            updateLogVisibility(els.logContent);
          }
          
          // Пересчитываем счетчики только для текущего контейнера
          setTimeout(() => {
            recalculateCounters();
          }, 100);
        }
      }
    });
    
    // Добавляем тестовые функции в глобальную область для отладки
    window.testDuplicateRemoval = testDuplicateRemoval;
    window.testSingleViewDuplicateRemoval = testSingleViewDuplicateRemoval;
    window.testSingleViewEmptyLinesRemoval = testSingleViewEmptyLinesRemoval;
    window.testSingleViewLineBreaks = testSingleViewLineBreaks;
    window.testMultiViewLineBreaks = testMultiViewLineBreaks;
    window.testMultiViewSpecialReplacements = testMultiViewSpecialReplacements;
    window.testFullMultiViewProcessing = testFullMultiViewProcessing;
    window.quickTestINFO = quickTestINFO;
    window.testRegex = testRegex;
    window.checkMultiViewHTML = checkMultiViewHTML;
    window.cleanMultiViewDuplicateLines = cleanMultiViewDuplicateLines;
    window.cleanDuplicateLines = cleanDuplicateLines;
    window.cleanSingleViewEmptyLines = cleanSingleViewEmptyLines;
    
    // Добавляем функции для исправления стилей в глобальную область
    window.forceFixMultiViewStyles = forceFixMultiViewStyles;
    window.updateLogStyles = updateLogStyles;
    
    // Универсальная функция для исправления всех контейнеров
    window.fixAllContainers = function() {
      console.log('Fixing all multi-view containers');
      
      const allLogs = document.querySelectorAll('.multi-view-content .multi-view-log');
      allLogs.forEach(log => {
        const containerId = log.getAttribute('data-container-id');
        console.log(`Fixing container:`, containerId);
        
        // Принудительно устанавливаем все стили заново
        log.style.cssText = 'height: 100% !important; overflow: auto !important; max-height: none !important; display: block !important; min-height: 200px !important; position: relative !important; flex: 1 !important; min-height: 0 !important; width: 100% !important; box-sizing: border-box !important;';
        
        // Принудительно вызываем пересчет layout
        log.style.setProperty('transform', 'translateZ(0)', 'important');
        
        // Проверяем родительские элементы
        const parentContent = log.closest('.multi-view-content');
        if (parentContent) {
          parentContent.style.cssText = 'display: flex !important; flex-direction: column !important; overflow: hidden !important; height: 100% !important;';
        }
        
        const parentPanel = log.closest('.multi-view-panel');
        if (parentPanel) {
          parentPanel.style.cssText = 'display: flex !important; flex-direction: column !important; overflow: hidden !important; height: 100% !important;';
        }
      });
      
      // Применяем настройки wrap text после исправления всех контейнеров
      applyWrapSettings();
    };
    
    // Оставляем старую функцию для обратной совместимости
    window.fixProblematicContainers = function() {
      console.log('fixProblematicContainers is deprecated, use fixAllContainers instead');
      window.fixAllContainers();
    };
    
    console.log('LogBoard+ инициализирован с исправлениями дублирования строк и правильными переносами строк в Single View и MultiView режимах');
    console.log('Для тестирования используйте: testDuplicateRemoval(), testSingleViewDuplicateRemoval(), testSingleViewEmptyLinesRemoval() или testSingleViewLineBreaks()');
    
    // Запускаем первоначальную очистку пустых строк
    setTimeout(() => {
      if (!state.multiViewMode && els.logContent) {
        cleanSingleViewEmptyLines(els.logContent);
        cleanDuplicateLines(els.logContent);
      }
    }, 1000);
    
    // Инициализируем видимость кнопок LogLevels
    updateLogLevelsVisibility();
    
    // ========================================
    // AJAX ОБНОВЛЕНИЕ ЛОГОВ
    // ========================================
    
    // Глобальные переменные для AJAX обновления
    let ajaxUpdateInterval = null;
    let ajaxUpdateEnabled = true; // По умолчанию включен
    let ajaxUpdateIntervalMs = 2000; // 2 секунды по умолчанию (будет переопределено из env)
    
    // Состояние для каждого контейнера (для multi-view)
    let containerStates = new Map(); // containerId -> {lastTimestamp, lastSecondCount}
    
    /**
     * Включить периодическое обновление логов через AJAX
     * @param {number} intervalMs - Интервал обновления в миллисекундах
     */
    function enableAjaxLogUpdate(intervalMs = null) {
      if (ajaxUpdateInterval) {
        clearInterval(ajaxUpdateInterval);
      }
      
      // Используем переданный интервал или значение по умолчанию
      if (intervalMs === null) {
        intervalMs = ajaxUpdateIntervalMs;
      }
      
      ajaxUpdateEnabled = true;
      ajaxUpdateIntervalMs = intervalMs;
      
      console.log(`AJAX обновление логов включено с интервалом ${intervalMs}ms`);
      
      // Запускаем первое обновление сразу
      performAjaxLogUpdate();
      
      // Устанавливаем интервал
      ajaxUpdateInterval = setInterval(performAjaxLogUpdate, intervalMs);
      
      // Обновляем UI
      updateAjaxUpdateCheckbox();
      
      // Обновляем видимость кнопки refresh и состояние кнопки update
      updateRefreshButtonVisibility();
    }
    
    /**
     * Отключить периодическое обновление логов через AJAX
     */
    function disableAjaxLogUpdate() {
      if (ajaxUpdateInterval) {
        clearInterval(ajaxUpdateInterval);
        ajaxUpdateInterval = null;
      }
      
      ajaxUpdateEnabled = false;
      console.log('AJAX обновление логов отключено');
      
      // Обновляем UI
      updateAjaxUpdateCheckbox();
      
      // Обновляем видимость кнопки refresh и состояние кнопки update
      updateRefreshButtonVisibility();
    }
    
    /**
     * Переключить состояние AJAX обновления
     */
    function toggleAjaxLogUpdate() {
      console.log('toggleAjaxLogUpdate: Текущее состояние ajaxUpdateEnabled =', ajaxUpdateEnabled);
      
      if (ajaxUpdateEnabled) {
        console.log('toggleAjaxLogUpdate: Отключаем AJAX update');
        disableAjaxLogUpdate();
      } else {
        console.log('toggleAjaxLogUpdate: Включаем AJAX update');
        enableAjaxLogUpdate(ajaxUpdateIntervalMs);
      }
      
      console.log('toggleAjaxLogUpdate: Новое состояние ajaxUpdateEnabled =', ajaxUpdateEnabled);
      
      // Обновляем видимость кнопки refresh и состояние кнопки update при переключении
      updateRefreshButtonVisibility();
    }
    
    /**
     * Выполнить обновление логов через AJAX
     */
    async function performAjaxLogUpdate() {
      if (!ajaxUpdateEnabled) {
        return;
      }
      
      // Получаем значение tail, учитывая опцию "all"
      let tailLines = els.tail.value;
      if (tailLines === 'all') {
        tailLines = 'all'; // Оставляем как строку для API
      } else {
        tailLines = parseInt(tailLines) || 50;
      }
      
      try {
        const token = localStorage.getItem('access_token');
        if (!token) {
          console.error('AJAX Update: No access token found');
          return;
        }
        
        // Определяем контейнеры для обновления
        let containersToUpdate = [];
        
        if (state.multiViewMode && state.selectedContainers.length > 0) {
          // Multi-view режим: обновляем все выбранные контейнеры
          containersToUpdate = state.selectedContainers;
        } else if (state.current) {
          // Single-view режим: обновляем текущий контейнер
          containersToUpdate = [state.current.id];
        } else {
          console.log('AJAX Update: Нет контейнеров для обновления');
          return;
        }
        
        console.log(`AJAX Update: Обновляем ${containersToUpdate.length} контейнеров:`, containersToUpdate);
        
        // Обновляем каждый контейнер
        for (const containerId of containersToUpdate) {
          await updateContainerLogs(containerId, tailLines, token);
        }
        
      } catch (error) {
        console.error('AJAX Update Error:', error);
        // Не отключаем обновление при ошибке, просто логируем
      }
    }
    
    /**
     * Обновить логи для конкретного контейнера
     */
    async function updateContainerLogs(containerId, tailLines, token) {
      try {
        // Формируем URL с параметрами
        const url = new URL(`/api/logs/${containerId}`, window.location.origin);
        
        // Передаем tail параметр как строку (для поддержки "all")
        url.searchParams.set('tail', String(tailLines));
        
        // Получаем состояние контейнера
        const containerState = containerStates.get(containerId) || { lastTimestamp: null, lastSecondCount: 0 };
        
        // Если у нас есть временная метка последнего обновления, используем её
        if (containerState.lastTimestamp) {
          url.searchParams.set('since', containerState.lastTimestamp);
        }
        
        console.log(`AJAX Update: Запрашиваем логи для ${containerId} с tail=${tailLines}`);
        
        // Формируем заголовки запроса
        const headers = {
          'Authorization': `Bearer ${token}`,
          'Cache-Control': 'no-cache'
        };
        const response = await fetch(url.toString(), { headers });
        
        if (!response.ok) {
          throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }
        
        const data = await response.json();
        
        if (data.error) {
          throw new Error(data.error);
        }
        
        // Клиентская дедупликация: пропускаем уже учтённые строки в ту же секунду
        let newPortion = data.logs || [];

        // Извлекаем секундную часть из timestamp ответа сервера
        const serverTs = (data.timestamp || '').split('.')[0]; // отбрасываем миллисекунды
        if (!containerState.lastTimestamp || serverTs !== containerState.lastTimestamp) {
          // Новая секунда — сбрасываем счётчик
          containerState.lastTimestamp = serverTs;
          containerState.lastSecondCount = 0;
        }

        if (newPortion.length > 0) {
          // Обрезаем уже учтённые строки в той же секунде
          if (containerState.lastSecondCount > 0 && newPortion.length > containerState.lastSecondCount) {
            newPortion = newPortion.slice(containerState.lastSecondCount);
          } else if (containerState.lastSecondCount >= newPortion.length) {
            newPortion = [];
          }

          if (newPortion.length > 0) {
            console.log(`AJAX Update: К обработке ${newPortion.length} строк для ${containerId} (из ${data.logs.length}), lastSecondCount=${containerState.lastSecondCount}`);
            appendNewLogsForContainer(containerId, newPortion);
            containerState.lastSecondCount += newPortion.length;
          } else {
            console.log(`AJAX Update: Новых логов нет для ${containerId} после дедупликации по секундам`);
          }
        } else {
          console.log(`AJAX Update: Логи не пришли для ${containerId}`);
        }
        
        // Обновляем состояние контейнера
        containerStates.set(containerId, containerState);
        
      } catch (error) {
        console.error(`AJAX Update Error for ${containerId}:`, error);
      }
    }
    
    /**
     * Добавить новые логи в конец существующих (универсальная функция для single и multi view)
     * @param {string} containerId - ID контейнера
     * @param {Array} newLogs - Массив новых логов
     */
    function appendNewLogsForContainer(containerId, newLogs) {
      const obj = state.open[containerId];
      
      if (!obj) {
        console.warn(`AJAX Update: Object not found for container ${containerId}`);
        return;
      }
      
      // Обрабатываем каждую новую строку лога через handleLine
      let addedCount = 0;
      newLogs.forEach(log => {
        const message = log.message || log.raw || '';
        if (message.trim()) {
          // Используем существующую функцию handleLine для правильной обработки
          handleLine(containerId, message);
          addedCount++;
        }
      });
      
      // Прокручиваем к концу, если включена автопрокрутка
      if (state.autoScroll) {
        if (state.multiViewMode) {
          // Для multi-view прокручиваем все контейнеры
          const multiViewLog = document.querySelector(`.multi-view-log[data-container-id="${containerId}"]`);
          if (multiViewLog) {
            multiViewLog.scrollTop = multiViewLog.scrollHeight;
          }
        } else if (els.logContent) {
          // Для single-view прокручиваем основной контент
          els.logContent.scrollTop = els.logContent.scrollHeight;
        }
      }
      
      // Обновляем счетчики
      if (state.multiViewMode) {
        // Для multi-view обновляем счетчики конкретного контейнера
        updateContainerCounters(containerId);
      } else {
        // Для single-view обновляем общие счетчики
        recalculateCounters();
      }
      
      // Очищаем дублированные строки
      if (state.multiViewMode) {
        const multiViewLog = document.querySelector(`.multi-view-log[data-container-id="${containerId}"]`);
        if (multiViewLog) {
          cleanMultiViewDuplicateLines(multiViewLog);
          cleanMultiViewEmptyLines(multiViewLog);
        }
      } else {
        cleanDuplicateLines(els.logContent);
        cleanSingleViewEmptyLines(els.logContent);
      }
      
      console.log(`AJAX Update: Обработано ${addedCount} новых строк логов для ${containerId} через handleLine`);
    }
    
    /**
     * Добавить новые логи в конец существующих (для обратной совместимости)
     * @param {Array} newLogs - Массив новых логов
     */
    function appendNewLogs(newLogs) {
      if (!state.current || !els.logContent) {
        return;
      }
      
      const containerId = state.current.id;
      appendNewLogsForContainer(containerId, newLogs);
    }
    
    /**
     * Обновить чекбокс AJAX обновления в UI
     */
    function updateAjaxUpdateCheckbox() {
      const checkbox = document.getElementById('autoupdate');
      if (checkbox) {
        checkbox.checked = ajaxUpdateEnabled;
      }
      
      // Обновляем видимость кнопки refresh в зависимости от состояния ajax autoupdate
      updateRefreshButtonVisibility();
    }
    
    /**
     * Обновить видимость кнопки refresh в header и состояние кнопки update
     */
    function updateRefreshButtonVisibility() {
      console.log('updateRefreshButtonVisibility: ajaxUpdateEnabled =', ajaxUpdateEnabled);
      
      const refreshButtons = document.querySelectorAll('.log-refresh-btn');
      console.log('updateRefreshButtonVisibility: Найдено кнопок refresh =', refreshButtons.length);
      
      refreshButtons.forEach(btn => {
        if (ajaxUpdateEnabled) {
          // Если ajax autoupdate включен, скрываем кнопку refresh
          btn.style.display = 'none';
          console.log('updateRefreshButtonVisibility: Скрываем кнопку refresh');
        } else {
          // Если ajax autoupdate выключен, показываем кнопку refresh
          btn.style.display = 'inline-flex';
          console.log('updateRefreshButtonVisibility: Показываем кнопку refresh');
        }
      });
      
      // Обновляем состояние кнопки update
      console.log('updateRefreshButtonVisibility: Обновляем состояние кнопки update');
      setAjaxUpdateState(ajaxUpdateEnabled);
    }
    
    /**
     * Инициализировать чекбокс AJAX обновления
     */
    function initAjaxUpdateCheckbox() {
      const checkbox = document.getElementById('autoupdate');
      if (!checkbox) {
        console.error('AJAX Update Checkbox not found in HTML');
        return;
      }
      
      // Настраиваем чекбокс
      checkbox.title = 'Автоматическое обновление логов через AJAX';
      
      // Добавляем обработчик изменения
      checkbox.addEventListener('change', function() {
        if (this.checked) {
          enableAjaxLogUpdate();
        } else {
          disableAjaxLogUpdate();
        }
        
        // Обновляем видимость кнопки refresh и состояние кнопки update при изменении состояния чекбокса
        updateRefreshButtonVisibility();
      });
      
      // Устанавливаем начальное состояние (включен по умолчанию)
      checkbox.checked = true;
      ajaxUpdateEnabled = true;
      
      // Обновляем видимость кнопки refresh и состояние кнопки update при инициализации
      updateRefreshButtonVisibility();
      
      console.log('AJAX Update Checkbox initialized');
    }
    
    /**
     * Инициализация AJAX обновления
     */
    async function initAjaxUpdate() {
      initAjaxUpdateCheckbox();
      
      // Получаем настройки с сервера
      try {
        const token = localStorage.getItem('access_token');
        if (token) {
          const response = await fetch('/api/settings', {
            headers: {
              'Authorization': `Bearer ${token}`
            }
          });
          
          if (response.ok) {
            const settings = await response.json();
            ajaxUpdateIntervalMs = settings.ajax_update_interval || 2000;
            console.log(`AJAX Update: Интервал обновления получен с сервера: ${ajaxUpdateIntervalMs}ms`);
          } else {
            console.warn('AJAX Update: Не удалось получить настройки с сервера, используем значение по умолчанию');
            ajaxUpdateIntervalMs = 2000; // 2 секунды по умолчанию
          }
        } else {
          console.warn('AJAX Update: Токен не найден, используем значение по умолчанию');
          ajaxUpdateIntervalMs = 2000; // 2 секунды по умолчанию
        }
      } catch (error) {
        console.error('AJAX Update: Ошибка получения настроек:', error);
        ajaxUpdateIntervalMs = 2000; // 2 секунды по умолчанию
      }
      
      console.log(`AJAX Update: Интервал обновления установлен на ${ajaxUpdateIntervalMs}ms`);
      
      // НЕ останавливаем AJAX обновление при смене контейнера
      const originalSwitchToSingle = window.switchToSingle;
      window.switchToSingle = function(containerId) {
        // Очищаем состояние для всех контейнеров
        containerStates.clear();
        return originalSwitchToSingle.call(this, containerId);
      };
      
      // НЕ останавливаем AJAX обновление при переключении в multi-view
      const originalSwitchToMultiView = window.switchToMultiView;
      window.switchToMultiView = function() {
        // Очищаем состояние для всех контейнеров
        containerStates.clear();
        return originalSwitchToMultiView.call(this);
      };
      
      console.log('AJAX обновление логов инициализировано');
      
      // Обновляем видимость кнопки refresh и состояние кнопки update после инициализации
      updateRefreshButtonVisibility();
    }
    
    // Запускаем инициализацию AJAX обновления
    initAjaxUpdate().then(() => {
      // Автоматически запускаем AJAX обновление (так как чекбокс включен по умолчанию)
      setTimeout(() => {
        if (ajaxUpdateEnabled) {
          console.log('AJAX Update: Автоматический запуск обновления логов');
          enableAjaxLogUpdate();
        }
      }, 1000); // Запускаем через 1 секунду после инициализации
    });
    
    // Экспортируем функции в глобальную область для отладки
    window.enableAjaxLogUpdate = enableAjaxLogUpdate;
    window.disableAjaxLogUpdate = disableAjaxLogUpdate;
    window.toggleAjaxLogUpdate = toggleAjaxLogUpdate;
    window.performAjaxLogUpdate = performAjaxLogUpdate;
    window.updateContainerLogs = updateContainerLogs;
    
    // Добавляем обработчик изменения выбранных контейнеров в multi-view
    const originalToggleContainerSelection = window.toggleContainerSelection;
    window.toggleContainerSelection = function(containerId) {
      const result = originalToggleContainerSelection.call(this, containerId);
      
      // Если AJAX обновление активно, очищаем состояние для измененных контейнеров
      if (ajaxUpdateEnabled) {
        // Очищаем состояние для всех контейнеров, чтобы избежать проблем с синхронизацией
        containerStates.clear();
        console.log('AJAX Update: Очищено состояние контейнеров после изменения выбора');
      }
      
      return result;
    };
    
    /**
     * Добавляет обработчики для сворачивания секций контейнеров
     */
    function addSectionToggleHandlers() {
      // Удаляем старые обработчики, если они есть
      document.removeEventListener('click', handleSectionToggle);
      document.removeEventListener('click', handleHostHeaderToggle);
      
      // Добавляем новые обработчики
      document.addEventListener('click', handleSectionToggle);
      document.addEventListener('click', handleHostHeaderToggle);
    }
    
    /**
     * Обработчик для сворачивания секций
     */
    function handleSectionToggle(e) {
      if (e.target.closest('.section-toggle-btn')) {
        e.preventDefault();
        e.stopPropagation();
        
        const button = e.target.closest('.section-toggle-btn');
        const target = button.getAttribute('data-target');
        const icon = button.querySelector('i');
        const content = document.getElementById(`${target}-content`);
        
        if (content) {
          const isCollapsed = content.style.display === 'none';
          
          if (isCollapsed) {
            // Разворачиваем секцию
            content.style.display = 'block';
            icon.className = 'fas fa-chevron-down';
            button.title = 'Свернуть секцию';
            updateCollapsedSection(target, false);
          } else {
            // Сворачиваем секцию
            content.style.display = 'none';
            icon.className = 'fas fa-chevron-right';
            button.title = 'Развернуть секцию';
            updateCollapsedSection(target, true);
          }
        }
      }
    }
    
    /**
     * Обработчик для сворачивания секций хостов
     */
    function handleHostHeaderToggle(e) {
      if (e.target.closest('.host-header')) {
        const header = e.target.closest('.host-header');
        const button = header.querySelector('.section-toggle-btn');
        
        if (button && !e.target.closest('.section-toggle-btn')) {
          e.preventDefault();
          e.stopPropagation();
          
          const target = button.getAttribute('data-target');
          const icon = button.querySelector('i');
          const content = document.getElementById(`${target}-content`);
          
          if (content) {
            const isCollapsed = content.style.display === 'none';
            
            if (isCollapsed) {
              // Разворачиваем секцию
              content.style.display = 'block';
              icon.className = 'fas fa-chevron-down';
              button.title = 'Свернуть секцию';
              updateCollapsedSection(target, false);
            } else {
              // Сворачиваем секцию
              content.style.display = 'none';
              icon.className = 'fas fa-chevron-right';
              button.title = 'Развернуть секцию';
              updateCollapsedSection(target, true);
            }
          }
        }
      }
    }
    

    
    /**
     * Периодическое обновление списка контейнеров
     */
    let containerUpdateInterval = null;
    
    function startContainerUpdate() {
      // Останавливаем предыдущий интервал, если он существует
      if (containerUpdateInterval) {
        clearInterval(containerUpdateInterval);
      }
      
      // Запускаем обновление каждые 30 секунд
      containerUpdateInterval = setInterval(async () => {
        try {
          console.log('Периодическое обновление списка контейнеров...');
          
          // Получаем новые данные о контейнерах
          const url = new URL(location.origin + '/api/containers/services');
          const selectedProjects = getSelectedProjects();
          
          if (selectedProjects.length > 0 && !selectedProjects.includes('all')) {
            url.searchParams.set('projects', selectedProjects.join(','));
          }
          
          const token = localStorage.getItem('access_token');
          if (!token) {
            console.error('No access token found during container update');
            return;
          }
          
          const res = await fetch(url, {
            headers: {
              'Authorization': `Bearer ${token}`
            }
          });
          
          if (!res.ok) {
            console.error('Failed to fetch containers during update:', res.status);
            return;
          }
          
          const newContainers = await res.json();
          
          // Фильтруем остановленные контейнеры
          const filteredContainers = filterStoppedContainers(newContainers);
          
          // Проверяем, изменился ли список контейнеров
          const currentIds = state.services.map(c => c.id).sort();
          const newIds = filteredContainers.map(c => c.id).sort();
          
          const hasChanged = JSON.stringify(currentIds) !== JSON.stringify(newIds);
          
          if (hasChanged) {
            console.log('Список контейнеров изменился, обновляем интерфейс...');
            console.log('Было контейнеров:', state.services.length);
            console.log('Стало контейнеров:', filteredContainers.length);
            
            // Обновляем состояние
            state.services = filteredContainers;
            
            // Перестраиваем интерфейс
            buildTabs();
            
            // Обновляем счетчики в заголовке
            updateHeaderCounters();
            
            console.log('Интерфейс обновлен');
          }
          
        } catch (error) {
          console.error('Ошибка при обновлении списка контейнеров:', error);
        }
      }, 30000); // 30 секунд
      
      console.log('Периодическое обновление контейнеров запущено (интервал: 30 сек)');
    }
    
    function stopContainerUpdate() {
      if (containerUpdateInterval) {
        clearInterval(containerUpdateInterval);
        containerUpdateInterval = null;
        console.log('Периодическое обновление контейнеров остановлено');
      }
    }
    
    /**
     * Обновляет счетчики контейнеров в заголовке
     */
    function updateHeaderCounters() {
      const localContainers = state.services.filter(svc => !svc.is_remote);
      const remoteContainers = state.services.filter(svc => svc.is_remote);
      
      // Обновляем счетчики в секциях
      const localCount = document.querySelector('.local-section .section-count');
      if (localCount) {
        localCount.textContent = localContainers.length;
      }
      
      const remoteCount = document.querySelector('.remote-section .section-count');
      if (remoteCount) {
        remoteCount.textContent = remoteContainers.length;
      }
      
      // Обновляем счетчики хостов
      const containersByHost = {};
      remoteContainers.forEach(svc => {
        const hostname = svc.hostname || 'unknown';
        if (!containersByHost[hostname]) {
          containersByHost[hostname] = [];
        }
        containersByHost[hostname].push(svc);
      });
      
      Object.entries(containersByHost).forEach(([hostname, containers]) => {
        const hostCount = document.querySelector(`[data-section="host-${hostname}"] .host-count`);
        if (hostCount) {
          hostCount.textContent = containers.length;
        }
      });
    }
    
})();
