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);
})();