diff --git a/templates/index.html b/templates/index.html index 4e5f600..41486cf 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1098,9 +1098,9 @@ main{display:none} .counter{font-size:11px;color:var(--muted)} .logwrap{flex:1;overflow:auto;padding:10px} .log{white-space:pre-wrap;word-break:break-word;font-size:12px;line-height:1.5;margin:0;tab-size:2} -.line{color:var(--fg)} .ok{color:var(--ok)} .warn{color:var(--warn)} .err{color:var(--err)} .dbg{color:#7dcfff} .ts{color:var(--muted)} +.line{color:var(--fg); display:block; margin:0; padding:0; line-height:1.4} .ok{color:var(--ok)} .warn{color:var(--warn)} .err{color:var(--err)} .dbg{color:#7dcfff} .ts{color:var(--muted)} /* Цвета для multi-view логов */ -.multi-view-log .line{color:var(--fg) !important} +.multi-view-log .line{color:var(--fg) !important; display:block; margin:0; padding:0; line-height:1.4} .multi-view-log .ok{color:var(--ok) !important} .multi-view-log .warn{color:var(--warn) !important} .multi-view-log .err{color:var(--err) !important} @@ -1108,7 +1108,7 @@ main{display:none} .multi-view-log .ts{color:var(--muted) !important} /* Дополнительные стили для multi-view логов */ -.multi-view-log span.line{color:var(--fg) !important} +.multi-view-log span.line{color:var(--fg) !important; display:block; margin:0; padding:0; line-height:1.4} .multi-view-log span.ok{color:var(--ok) !important} .multi-view-log span.warn{color:var(--warn) !important} .multi-view-log span.err{color:var(--err) !important} @@ -1434,18 +1434,18 @@ function refreshAllLogs() { // Обновляем отображение 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 после обновления - cleanDuplicateLines(els.logContent); cleanSingleViewEmptyLines(els.logContent); + cleanDuplicateLines(els.logContent); } - - // Очищаем дублированные строки в legacy панели - cleanDuplicateLines(obj.logEl); - cleanSingleViewEmptyLines(obj.logEl); }); // Обновляем мультипросмотр @@ -1473,7 +1473,8 @@ function refreshAllLogs() { if (multiViewLog) { multiViewLog.innerHTML = filteredHtml.join(''); - // Очищаем дублированные строки после обновления + // Сразу очищаем пустые строки в мультипросмотре + cleanMultiViewEmptyLines(multiViewLog); cleanMultiViewDuplicateLines(multiViewLog); } }); @@ -2566,25 +2567,26 @@ function processMultiViewLineBreaks(text) { } /** - * Функция для очистки пустых строк в multi view - * Удаляет пустые строки между "Connected" и началом логов + * Функция для радикальной очистки пустых строк в multi view + * Удаляет все пустые строки и лишние переносы строк * @param {HTMLElement} multiViewLog - элемент лога multi view */ function cleanMultiViewEmptyLines(multiViewLog) { if (!multiViewLog) return; - // Находим все строки в логе - const lines = Array.from(multiViewLog.querySelectorAll('.line')); + 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, @@ -2595,12 +2597,27 @@ function cleanMultiViewEmptyLines(multiViewLog) { const textNodesToRemove = []; let node; while (node = walker.nextNode()) { - if (node.textContent.trim() === '') { + 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} пустых строк`); + } } /** @@ -2623,7 +2640,8 @@ function cleanMultiViewDuplicateLines(multiViewLog) { const currentText = currentLine.textContent || currentLine.innerText || ''; const previousText = previousLine.textContent || previousLine.innerText || ''; - if (currentText.trim() === previousText.trim() && currentText.trim() !== '') { + // Удаляем дублированные строки (включая пустые) + if (currentText.trim() === previousText.trim()) { console.log(`cleanMultiViewDuplicateLines: Удаляем дублированную строку: ${currentText.substring(0, 50)}...`); currentLine.remove(); removedCount++; @@ -2631,6 +2649,9 @@ function cleanMultiViewDuplicateLines(multiViewLog) { } } + // После удаления дубликатов очищаем лишние пустые строки + cleanMultiViewEmptyLines(multiViewLog); + if (removedCount > 0) { console.log(`cleanMultiViewDuplicateLines: Удалено ${removedCount} дублированных строк`); } @@ -2656,7 +2677,8 @@ function cleanDuplicateLines(logElement) { const currentText = currentLine.textContent || currentLine.innerText || ''; const previousText = previousLine.textContent || previousLine.innerText || ''; - if (currentText.trim() === previousText.trim() && currentText.trim() !== '') { + // Удаляем дублированные строки (включая пустые) + if (currentText.trim() === previousText.trim()) { console.log(`cleanDuplicateLines: Удаляем дублированную строку: ${currentText.substring(0, 50)}...`); currentLine.remove(); removedCount++; @@ -2664,33 +2686,39 @@ function cleanDuplicateLines(logElement) { } } + // После удаления дубликатов очищаем лишние пустые строки + if (logElement.classList.contains('multi-view-log')) { + cleanMultiViewEmptyLines(logElement); + } else { + cleanSingleViewEmptyLines(logElement); + } + if (removedCount > 0) { console.log(`cleanDuplicateLines: Удалено ${removedCount} дублированных строк`); } } /** - * Функция для очистки лишних пустых строк в Single View - * Удаляет только лишние пустые строки, сохраняя переносы строк между логами + * Функция для радикальной очистки пустых строк в Single View + * Удаляет все пустые строки и лишние переносы строк * @param {HTMLElement} logElement - элемент лога Single View */ function cleanSingleViewEmptyLines(logElement) { if (!logElement) return; - // Находим все строки в логе - const lines = Array.from(logElement.querySelectorAll('.line')); let removedCount = 0; - // Удаляем только полностью пустые строки (строки без текста) + // Удаляем все пустые строки (элементы .line без текста) + const lines = Array.from(logElement.querySelectorAll('.line')); lines.forEach(line => { const textContent = line.textContent || line.innerText || ''; - if (textContent.trim() === '' && textContent.length === 0) { + if (textContent.trim() === '') { line.remove(); removedCount++; } }); - // Удаляем только полностью пустые текстовые узлы (не переносы строк) + // Удаляем все текстовые узлы, которые содержат только пробелы и переносы строк const walker = document.createTreeWalker( logElement, NodeFilter.SHOW_TEXT, @@ -2701,23 +2729,24 @@ function cleanSingleViewEmptyLines(logElement) { const textNodesToRemove = []; let node; while (node = walker.nextNode()) { - // Удаляем только узлы, которые содержат только пробелы и переносы строк - if (node.textContent.trim() === '' && node.textContent.length > 0) { - // Проверяем, что это не просто перенос строки - const content = node.textContent; - if (content === '\n' || content === '\r\n') { - // Оставляем переносы строк - continue; - } - // Удаляем только если это множественные пробелы или табуляция - if (content.match(/^[\s\t]+$/)) { - textNodesToRemove.push(node); - } + 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} пустых строк`); } @@ -2737,6 +2766,40 @@ function normalizeSpaces(text) { 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); + /** * Функция для обработки специальных замен в MultiView логах * Выполняет специфичные замены для улучшения читаемости логов @@ -3140,14 +3203,14 @@ function handleLine(id, line){ if (cls==='err') obj.counters.err++; } - // Для Single View добавляем перенос строки после каждой строки лога - const html = `${ansiToHtml(normalizedLine)}\n`; + // Для Single View НЕ добавляем перенос строки после каждой строки лога + const html = `${ansiToHtml(normalizedLine)}`; // Сохраняем все логи в буфере (всегда) if (!obj.allLogs) obj.allLogs = []; // Для Single View сохраняем обработанную строку, для MultiView - оригинальную const processedLine = !state.multiViewMode ? processSingleViewSpecialReplacements(normalizedLine) : normalizedLine; - const processedHtml = `${ansiToHtml(processedLine)}\n`; + const processedHtml = `${ansiToHtml(processedLine)}`; obj.allLogs.push({html: processedHtml, line: processedLine, cls: cls}); // Ограничиваем размер буфера @@ -3168,8 +3231,8 @@ function handleLine(id, line){ return; // Пропускаем дублированную строку } - // Создаем HTML с обработанной строкой для Single View (с переносом строки) - const singleViewHtml = `${ansiToHtml(singleViewProcessedLine)}\n`; + // Создаем HTML с обработанной строкой для Single View (без переноса строки) + const singleViewHtml = `${ansiToHtml(singleViewProcessedLine)}`; obj.logEl.insertAdjacentHTML('beforeend', singleViewHtml); @@ -3226,7 +3289,7 @@ function handleLine(id, line){ return; // Пропускаем дублированную строку } - const multiViewHtml = `${ansiToHtml(processedLine)}\n`; + const multiViewHtml = `${ansiToHtml(processedLine)}`; // Добавляем новую строку multiViewLog.insertAdjacentHTML('beforeend', multiViewHtml); @@ -3376,6 +3439,11 @@ async function switchToSingle(svc){ els.logContent.insertAdjacentHTML('beforeend', logEntry.html); } }); + + // Очищаем лишние пустые строки после восстановления логов + cleanSingleViewEmptyLines(els.logContent); + cleanDuplicateLines(els.logContent); + if (els.autoscroll && els.autoscroll.checked) { els.logContent.scrollTop = els.logContent.scrollHeight; } @@ -3774,6 +3842,12 @@ async function refreshLogsAndCounters() { if (updatedContainer) { // Переключаемся на обновленный контейнер await switchToSingle(updatedContainer); + + // Очищаем лишние пустые строки после переключения + if (els.logContent) { + cleanSingleViewEmptyLines(els.logContent); + cleanDuplicateLines(els.logContent); + } } } else { console.log('No container selected'); @@ -3791,6 +3865,9 @@ els.clearBtn.onclick = ()=> { // Очищаем современный интерфейс if (els.logContent) { els.logContent.textContent = ''; + // Очищаем лишние пустые строки после очистки + cleanSingleViewEmptyLines(els.logContent); + cleanDuplicateLines(els.logContent); } // Очищаем мультипросмотр @@ -4480,6 +4557,14 @@ window.addEventListener('keydown', async (e)=>{ 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); })();