Исправлена проблема с лишними пустыми строками в Single View и Multi View

- Радикально переработаны функции очистки пустых строк
- Удалены переносы строк из HTML-шаблонов логов
- Добавлены CSS стили для правильного отображения элементов .line
- Увеличена частота периодической очистки до 2 секунд
- Добавлена агрессивная очистка во всех ключевых точках приложения
- Улучшена логика определения типа элемента для правильной очистки

Автор: Сергей Антропов
Сайт: https://devops.org.ru
This commit is contained in:
Сергей Антропов 2025-08-17 18:02:43 +03:00
parent 012c31522c
commit 59e0810750

View File

@ -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 = `<span class="line ${cls}">${ansiToHtml(normalizedLine)}</span>\n`;
// Для 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>\n`;
const processedHtml = `<span class="line ${cls}">${ansiToHtml(processedLine)}</span>`;
obj.allLogs.push({html: processedHtml, line: processedLine, cls: cls});
// Ограничиваем размер буфера
@ -3168,8 +3231,8 @@ function handleLine(id, line){
return; // Пропускаем дублированную строку
}
// Создаем HTML с обработанной строкой для Single View (с переносом строки)
const singleViewHtml = `<span class="line ${cls}">${ansiToHtml(singleViewProcessedLine)}</span>\n`;
// Создаем HTML с обработанной строкой для Single View (без переноса строки)
const singleViewHtml = `<span class="line ${cls}">${ansiToHtml(singleViewProcessedLine)}</span>`;
obj.logEl.insertAdjacentHTML('beforeend', singleViewHtml);
@ -3226,7 +3289,7 @@ function handleLine(id, line){
return; // Пропускаем дублированную строку
}
const multiViewHtml = `<span class="line ${cls}">${ansiToHtml(processedLine)}</span>\n`;
const multiViewHtml = `<span class="line ${cls}">${ansiToHtml(processedLine)}</span>`;
// Добавляем новую строку
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);
})();
</script>
</body>