diff --git a/templates/index.html b/templates/index.html index 77b143f..2694bea 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1244,6 +1244,120 @@ a{color:var(--link)} flex: 1; } +/* Кнопки уровней логирования для заголовков */ +.single-view-levels, +.multi-view-levels { + display: flex; + gap: 4px; + align-items: center; +} + +.level-btn { + display: flex; + flex-direction: column; + align-items: center; + gap: 2px; + padding: 4px 6px; + border: 1px solid var(--border); + border-radius: 4px; + background: var(--panel); + color: var(--fg); + cursor: pointer; + transition: all 0.2s ease; + font-size: 10px; + min-width: 40px; + position: relative; +} + +.level-btn:hover { + background: var(--chip); + border-color: var(--accent); +} + +.level-btn.active { + background: var(--accent); + color: #0b0d12; + border-color: var(--accent); +} + +.level-btn.disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.level-btn.disabled:hover { + background: var(--panel); + border-color: var(--border); +} + +.level-label { + font-weight: 500; + font-size: 9px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.level-value { + font-weight: 600; + font-size: 11px; +} + +/* Цвета для разных уровней */ +.level-btn.debug-btn { + border-color: var(--ok); + color: var(--ok); +} + +.level-btn.debug-btn:hover, +.level-btn.debug-btn.active { + background: var(--ok); + color: #0b0d12; +} + +.level-btn.info-btn { + border-color: var(--accent); + color: var(--accent); +} + +.level-btn.info-btn:hover, +.level-btn.info-btn.active { + background: var(--accent); + color: #0b0d12; +} + +.level-btn.warn-btn { + border-color: var(--warn); + color: var(--warn); +} + +.level-btn.warn-btn:hover, +.level-btn.warn-btn.active { + background: var(--warn); + color: #0b0d12; +} + +.level-btn.error-btn { + border-color: var(--err); + color: var(--err); +} + +.level-btn.error-btn:hover, +.level-btn.error-btn.active { + background: var(--err); + color: #0b0d12; +} + +.level-btn.other-btn { + border-color: var(--muted); + color: var(--muted); +} + +.level-btn.other-btn:hover, +.level-btn.other-btn.active { + background: var(--muted); + color: #0b0d12; +} + .single-view-content { flex: 1; overflow: hidden; @@ -1856,6 +1970,28 @@ footer{position:fixed;right:10px;bottom:10px;opacity:.6;font-size:11px}
No container selected@@ -2083,6 +2219,218 @@ function allowedByLevel(cls){ if (cls==='other') return state.levels.other; // Показываем неклассифицированные строки в зависимости от настройки return true; } + +// Функция для проверки уровня логирования для конкретного контейнера +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 +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() { + // Инициализируем кнопки для 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); + }); +} function applyFilter(line){ if(!state.filter) return true; try{ @@ -2672,6 +3020,11 @@ async function updateMultiViewMode() { } console.log(`Multi-view mode updated: multiViewMode = ${state.multiViewMode}`); + + // Обновляем состояние кнопок уровней логирования при переключении режимов + setTimeout(() => { + initializeLevelButtons(); + }, 100); } async function setupMultiView() { @@ -2799,6 +3152,28 @@ function createMultiViewPanel(service) { panel.innerHTML = `
Connecting...@@ -4258,6 +4692,12 @@ async function switchToSingle(svc){ // Добавляем обработчики для счетчиков после переключения контейнера addCounterClickHandlers(); + + // Обновляем состояние кнопок уровней логирования + setTimeout(() => { + initializeLevelButtons(); + }, 100); + } catch (error) { console.error('switchToSingle: Error occurred:', error); console.error('switchToSingle: Error stack:', error.stack); @@ -4465,9 +4905,6 @@ async function updateMultiViewCounters() { // Используем новую функцию пересчета счетчиков recalculateMultiViewCounters(); - // Обновляем видимость счетчиков - updateCounterVisibility(); - // Добавляем обработчики для счетчиков addCounterClickHandlers(); @@ -4518,6 +4955,9 @@ function recalculateCounters() { 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); } @@ -4552,7 +4992,7 @@ function recalculateMultiViewCounters() { // Пересчитываем счетчики только для отображаемых логов visibleLogs.forEach(logEntry => { - const shouldShow = allowedByLevel(logEntry.cls) && applyFilter(logEntry.line); + 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++; @@ -4562,6 +5002,9 @@ function recalculateMultiViewCounters() { } }); + // Обновляем счетчики в кнопках заголовка для этого контейнера + updateHeaderCounters(containerId, obj.counters); + // Добавляем к общим счетчикам totalDebug += obj.counters.dbg; totalInfo += obj.counters.info; @@ -4588,6 +5031,7 @@ function recalculateMultiViewCounters() { // Функция для обновления видимости счетчиков function updateCounterVisibility() { + // Обновляем старые кнопки счетчиков (только для legacy интерфейса) const debugBtn = document.querySelector('.debug-btn'); const infoBtn = document.querySelector('.info-btn'); const warnBtn = document.querySelector('.warn-btn'); @@ -5120,6 +5564,9 @@ document.addEventListener('DOMContentLoaded', () => { els.optionsBtn.title = 'Показать настройки'; localStorage.setItem('lb_options_hidden', 'true'); } + + // Инициализируем состояние кнопок уровней логирования + initializeLevelButtons(); } // Обработчик для кнопки выхода @@ -5589,6 +6036,62 @@ window.addEventListener('keydown', async (e)=>{ } } }); + + // Обработчики для кнопок уровней логирования в заголовках + 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; + + // Обновляем видимость логов только для этого контейнера + updateContainerLogVisibility(containerId); + + // Пересчитываем счетчики только для этого контейнера + setTimeout(() => { + updateContainerCounters(containerId); + }, 100); + + // Обновляем видимость логов для всех контейнеров в multi-view + // чтобы убедиться, что изменения применились только к нужному контейнеру + state.selectedContainers.forEach(id => { + if (id !== containerId) { + updateContainerLogVisibility(id); + } + }); + } else { + // Для single-view: глобальные настройки + state.levels[level] = !isActive; + + // Обновляем видимость логов только для текущего контейнера + if (state.current) { + updateLogVisibility(els.logContent); + } + + // Пересчитываем счетчики только для текущего контейнера + setTimeout(() => { + recalculateCounters(); + }, 100); + } + } + }); // Добавляем тестовые функции в глобальную область для отладки window.testDuplicateRemoval = testDuplicateRemoval;