@@ -15,6 +15,7 @@ const state = {
services : [ ] , // Список всех доступных сервисов
current : null , // Текущий выбранный контейнер для single view
open : { } , // Открытые WebSocket соединения: id -> {ws, logEl, wrapEl, counters, pausedBuffer:[], serviceName}
wsConnections : { } , // WebSocket соединения для multiview: id -> WebSocket
layout : 'tabs' , // Режим отображения: 'tabs' | 'grid2' | 'grid3' | 'grid4'
filter : null , // Текущий фильтр для логов
levels : { debug : true , info : true , warn : true , err : true , other : true } , // Уровни логирования для отображения
@@ -33,6 +34,7 @@ const els = {
tail : document . getElementById ( 'tail' ) , // Поле ввода количества строк логов
autoscroll : document . getElementById ( 'autoscroll' ) , // Чекбокс автопрокрутки
wrapToggle : document . getElementById ( 'wrap' ) , // Переключатель переноса строк
autoRefreshOnRestore : document . getElementById ( 'autoRefreshOnRestore' ) , // Чекбокс автообновления при восстановлении
filter : document . getElementById ( 'filter' ) , // Поле фильтра логов
wsstate : document . getElementById ( 'wsstate' ) , // Индикатор состояния WebSocket
@@ -369,6 +371,8 @@ function refreshAllLogs() {
} else {
recalculateCounters ( ) ;
}
// Прокручиваем к последним логам после обновления
scrollToBottom ( ) ;
} , 100 ) ;
}
/**
@@ -662,20 +666,28 @@ function updateHeaderCounters(containerId, counters) {
// Функция для инициализации состояния кнопок уровней логирования
function initializeLevelButtons ( ) {
console . log ( 'initializeLevelButtons: Starting initialization...' ) ;
console . log ( 'initializeLevelButtons: Current multiViewMode:' , state . multiViewMode ) ;
console . log ( 'initializeLevelButtons: Current selectedContainers:' , state . selectedContainers ) ;
// Восстанавливаем состояние кнопок loglevels из localStorage
const savedLevelsState = getLogLevelsStateFromStorage ( ) ;
if ( savedLevelsState ) {
console . log ( 'Restoring log levels state from localStorage' ) ;
console . log ( 'initializeLevelButtons: Restoring log levels state from localStorage:' , savedLevelsState ) ;
// Восстанавливаем глобальные настройки для single-view
if ( savedLevelsState . globalLevels ) {
state . levels = { ... state . levels , ... savedLevelsState . globalLevels } ;
console . log ( 'initializeLevelButtons: Restored global levels:' , state . levels ) ;
}
// Восстанавливаем настройки контейнеров для multi-view
if ( savedLevelsState . containerLevels ) {
state . containerLevels = { ... state . containerLevels , ... savedLevelsState . containerLevels } ;
console . log ( 'initializeLevelButtons: Restored container levels:' , state . containerLevels ) ;
}
} else {
console . log ( 'initializeLevelButtons: No saved levels state found in localStorage' ) ;
}
// Инициализируем кнопки для single-view
@@ -689,24 +701,33 @@ function initializeLevelButtons() {
// Инициализируем кнопки для multi-view (если есть)
const multiLevelBtns = document . querySelectorAll ( '.multi-view-levels .level-btn' ) ;
multiLevelBtns . forEach ( btn => {
console . log ( ` initializeLevelButtons: Found ${ multiLevelBtns . length } multi-view level buttons ` ) ;
multiLevelBtns . forEach ( ( btn , index ) => {
const level = btn . getAttribute ( 'data-level' ) ;
const containerId = btn . getAttribute ( 'data-container-id' ) ;
console . log ( ` initializeLevelButtons: Processing button ${ index + 1 } : level= ${ level } , containerId= ${ containerId } ` ) ;
// Инициализируем настройки контейнера, если их нет
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 } ;
console . log ( ` initializeLevelButtons: Initialized container levels for ${ containerId } : ` , state . containerLevels [ containerId ] ) ;
}
// Используем настройки контейнера
const isActive = state . containerLevels && state . containerLevels [ containerId ] ?
state . containerLevels [ containerId ] [ level ] : true ;
console . log ( ` initializeLevelButtons: Setting button state: level= ${ level } , containerId= ${ containerId } , isActive= ${ isActive } ` ) ;
btn . classList . toggle ( 'active' , isActive ) ;
btn . classList . toggle ( 'disabled' , ! isActive ) ;
console . log ( ` initializeLevelButtons: Button classes after toggle: ` , btn . className ) ;
} ) ;
// Обновляем стили логов после инициализации кнопок
@@ -714,6 +735,12 @@ function initializeLevelButtons() {
// Применяем настройки wrap text
applyWrapSettings ( ) ;
// Устанавливаем обработчик событий для кнопок уровней логирования
if ( window . levelButtonClickHandler ) {
document . addEventListener ( 'click' , window . levelButtonClickHandler ) ;
console . log ( 'initializeLevelButtons: Level button click handler installed' ) ;
}
}
/**
* Применяет фильтр к строке лога
@@ -1809,10 +1836,13 @@ async function setupMultiView() {
// Создаем панели для каждого выбранного контейнера
console . log ( ` setupMultiView: Creating panels for ${ state . selectedContainers . length } containers: ` , state . selectedContainers ) ;
console . log ( ` setupMultiView: Available services: ` , state . services . map ( s => ( { id : s . id , name : s . name } ) ) ) ;
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 } ` ) ;
console . error ( ` setupMultiView: Available service IDs: ` , state . services . map ( s => s . id ) ) ;
return ;
}
@@ -1869,10 +1899,16 @@ async function setupMultiView() {
// Применяем сохраненный порядок панелей
setTimeout ( ( ) => {
console . log ( 'setupMultiView: Starting panel order restoration...' ) ;
console . log ( 'setupMultiView: Current selectedContainers:' , state . selectedContainers ) ;
console . log ( 'setupMultiView: Current multiViewMode:' , state . multiViewMode ) ;
// Сначала очищаем дубликаты, если они есть
cleanupDuplicatePanels ( ) ;
// Затем применяем порядок
applyPanelOrder ( ) ;
console . log ( 'setupMultiView: Panel order restoration completed' ) ;
} , 100 ) ; // Небольшая задержка для завершения создания панелей
// Обновляем счетчики для multi view
@@ -1946,30 +1982,22 @@ function createMultiViewPanel(service) {
}
// Добавляем drag & drop функциональность
setupDragAndDrop ( panel ) ;
if ( window . setupDragAndDrop ) {
window . setupDragAndDrop ( panel ) ;
} else {
console . warn ( 'setupDragAndDrop not available yet, will be set up later' ) ;
// Устанавливаем drag & drop позже, когда функция будет доступна
setTimeout ( ( ) => {
if ( window . setupDragAndDrop ) {
window . setupDragAndDrop ( panel ) ;
}
} , 100 ) ;
}
// Инициализируем состояние кнопок уровней логирования для этого контейнера
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 ) ;
} ) ;
console . log ( ` createMultiViewPanel: Initializing level buttons for ${ service . name } ( ${ service . id } ) ` ) ;
initializeLevelButtons ( ) ;
} , 100 ) ;
console . log ( ` Multi-view panel created for ${ service . name } ` ) ;
@@ -1993,10 +2021,24 @@ function openMultiViewWs(service) {
console . log ( ` openMultiViewWs: Current multiViewMode: ${ state . multiViewMode } ` ) ;
console . log ( ` openMultiViewWs: Selected containers: ${ state . selectedContainers . join ( ', ' ) } ` ) ;
// Закрываем существующее соединение
closeWs ( containerId ) ;
// Закрываем существующее соединение только если оно действительно существует
const existingConnection = state . open [ containerId ] ;
if ( existingConnection && existingConnection . ws ) {
console . log ( ` openMultiViewWs: Closing existing connection for ${ service . name } ( ${ containerId } ) ` ) ;
closeWs ( containerId ) ;
// Добавляем небольшую задержку перед созданием нового соединения
setTimeout ( ( ) => {
createWebSocketConnection ( service , containerId ) ;
} , 100 ) ;
return ;
}
// Создаем новое WebSocket соединение
createWebSocketConnection ( service , containerId ) ;
}
function createWebSocketConnection ( service , containerId ) {
console . log ( ` createWebSocketConnection: Creating WebSocket for ${ service . name } ( ${ containerId } ) ` ) ;
const ws = new WebSocket ( wsUrl ( containerId , service . service , service . project ) ) ;
ws . onopen = ( ) => {
@@ -2090,6 +2132,9 @@ function openMultiViewWs(service) {
allLogs : [ ] // Добавляем буфер для логов
} ;
// Также сохраняем WebSocket в wsConnections для проверки в applyPanelOrder
state . wsConnections [ containerId ] = ws ;
console . log ( ` openMultiViewWs: WebSocket setup completed for ${ service . name } ( ${ containerId } ) ` ) ;
}
@@ -2189,6 +2234,14 @@ async function fetchServices(){
buildTabs ( ) ;
// Проверяем, нужно ли пропустить восстановление (например, после автоматического обновления)
const skipRestore = localStorage . getItem ( 'lb_skip_restore' ) ;
if ( skipRestore === 'true' ) {
console . log ( 'Skipping panel restoration due to lb_skip_restore flag' ) ;
localStorage . removeItem ( 'lb_skip_restore' ) ;
return ;
}
// Восстанавливаем режим просмотра из localStorage
const savedViewMode = getViewModeFromStorage ( ) ;
if ( savedViewMode ) {
@@ -2214,6 +2267,56 @@ async function fetchServices(){
// Настраиваем Multi View
await setupMultiView ( ) ;
// Проверяем настройку автоматического обновления логов при восстановлении панелей
const autoRefreshOnRestore = localStorage . getItem ( 'lb_auto_refresh_on_restore' ) ;
if ( autoRefreshOnRestore === 'true' ) {
console . log ( 'Auto-refresh logs on restore is enabled, refreshing logs in 1 second...' ) ;
setTimeout ( ( ) => {
// Обновляем логи панелей вместо обновления страницы
refreshLogsAndCounters ( ) ;
console . log ( 'Logs refreshed after panel restoration' ) ;
// Дополнительная прокрутка через небольшую задержку
setTimeout ( ( ) => {
scrollToBottom ( ) ;
} , 1500 ) ;
} , 1000 ) ;
} else {
// Если это восстановление из localStorage, проверяем через некоторое время
// нужно ли обновить страницу для корректной работы обработчиков
setTimeout ( ( ) => {
const hasLevelButtons = document . querySelectorAll ( '.level-btn' ) . length > 0 ;
if ( hasLevelButtons ) {
console . log ( 'Panel restoration completed, checking event handlers in 2 seconds...' ) ;
setTimeout ( ( ) => {
// Простая проверка - если кнопки есть, но клики не работают, обновляем страницу
const testButton = document . querySelector ( '.level-btn' ) ;
if ( testButton ) {
// Симулируем клик для проверки
const clickEvent = new MouseEvent ( 'click' , { bubbles : true } ) ;
const originalHandler = testButton . onclick ;
// Временно устанавливаем обработчик для проверки
testButton . onclick = ( ) => {
console . log ( 'Test click handler works' ) ;
testButton . onclick = originalHandler ;
} ;
testButton . dispatchEvent ( clickEvent ) ;
// Если через 100ms обработчик не сработал, обновляем логи
setTimeout ( ( ) => {
if ( testButton . onclick && testButton . onclick . toString ( ) . includes ( 'Test click handler works' ) ) {
console . log ( 'Event handlers not working after restoration, refreshing logs...' ) ;
refreshLogsAndCounters ( ) ;
}
} , 100 ) ;
}
} , 2000 ) ;
}
} , 1000 ) ;
}
} else if ( savedViewMode . selectedContainers . length === 1 ) {
// Восстанавливаем Single View режим
console . log ( 'Restoring Single View mode for container:' , savedViewMode . selectedContainers [ 0 ] ) ;
@@ -3947,6 +4050,27 @@ function updateLogLevelsVisibility() {
}
// Функция для обновления логов и счетчиков
/**
* Автоматически прокручивает логи к самому низу (последние логи)
* Работает как для single-view, так и для multi-view режимов
*/
function scrollToBottom ( ) {
if ( state . multiViewMode && state . selectedContainers . length > 0 ) {
// Для multi-view прокручиваем все панели
state . selectedContainers . forEach ( containerId => {
const multiViewLog = document . querySelector ( ` .multi-view-log[data-container-id=" ${ containerId } "] ` ) ;
if ( multiViewLog ) {
multiViewLog . scrollTop = multiViewLog . scrollHeight ;
console . log ( ` Scrolled to bottom for container ${ containerId } ` ) ;
}
} ) ;
} else if ( els . logContent ) {
// Для single-view прокручиваем основной контент
els . logContent . scrollTop = els . logContent . scrollHeight ;
console . log ( 'Scrolled to bottom for single view' ) ;
}
}
async function refreshLogsAndCounters ( ) {
if ( state . multiViewMode && state . selectedContainers . length > 0 ) {
// Обновляем мультипросмотр
@@ -3979,6 +4103,8 @@ async function refreshLogsAndCounters() {
recalculateMultiViewCounters ( ) ;
// Применяем настройки wrap text после обновления
applyWrapSettings ( ) ;
// Прокручиваем к последним логам
scrollToBottom ( ) ;
} , 1000 ) ; // Небольшая задержка для завершения переподключения
} else if ( state . current ) {
@@ -4012,6 +4138,8 @@ async function refreshLogsAndCounters() {
recalculateCounters ( ) ;
// Применяем настройки wrap text после обновления
applyWrapSettings ( ) ;
// Прокручиваем к последним логам
scrollToBottom ( ) ;
} , 1000 ) ; // Небольшая задержка для завершения переподключения
} else {
@@ -5020,6 +5148,7 @@ function reinitializeElements() {
els . sidebar = document . getElementById ( 'sidebar' ) ;
els . sidebarToggle = document . getElementById ( 'sidebarToggle' ) ;
els . header = document . getElementById ( 'header' ) ;
els . autoRefreshOnRestore = document . getElementById ( 'autoRefreshOnRestore' ) ;
console . log ( 'Elements reinitialized:' , {
filter : ! ! els . filter ,
@@ -5282,13 +5411,40 @@ function reinitializeElements() {
} ) ;
// Обработчики для кнопок уровней логирования в заголовках
document . addEventListener ( 'click' , ( e ) => {
if ( e . target . closest ( '.level-btn' ) ) {
const levelBtn = e . target . closest ( '.level-btn' ) ;
console . log ( 'Setting up level button click handler...' ) ;
// Удаляем предыдущий обработчик, если он существует
if ( window . levelButtonClickHandler ) {
document . removeEventListener ( 'click' , window . levelButtonClickHandler ) ;
console . log ( 'Removed previous level button click handler' ) ;
}
// Создаем новый обработчик
window . levelButtonClickHandler = ( e ) => {
console . log ( 'Document click event:' , e . target ) ;
console . log ( 'Event target closest level-btn:' , e . target . closest ( '.level-btn' ) ) ;
// Проверяем, что клик произошел на кнопке уровня логирования или на е е дочернем элементе
let levelBtn = null ;
// Если клик произошел на самой кнопке
if ( e . target . classList . contains ( 'level-btn' ) ) {
levelBtn = e . target ;
console . log ( 'Click on button itself' ) ;
}
// Если клик произошел на дочернем элементе кнопки
else if ( e . target . closest ( '.level-btn' ) ) {
levelBtn = e . target . closest ( '.level-btn' ) ;
console . log ( 'Click on child element of button' ) ;
}
if ( levelBtn ) {
const level = levelBtn . getAttribute ( 'data-level' ) ;
const containerId = levelBtn . getAttribute ( 'data-container-id' ) ;
console . log ( ` Клик по кнопке уровня логирования: level= ${ level } , containerId= ${ containerId } , multiViewMode= ${ state . multiViewMode } ` ) ;
console . log ( ` Кнопка найдена: ` , levelBtn ) ;
console . log ( ` Текущие классы кнопки: ` , levelBtn . className ) ;
// Переключаем состояние кнопки
const isActive = levelBtn . classList . contains ( 'active' ) ;
@@ -5341,7 +5497,7 @@ function reinitializeElements() {
} , 100 ) ;
}
}
} ) ;
} ;
// Добавляем тестовые функции в глобальную область для отладки
window . testDuplicateRemoval = testDuplicateRemoval ;
@@ -5765,6 +5921,22 @@ function reinitializeElements() {
checkbox . checked = true ;
ajaxUpdateEnabled = true ;
// Инициализируем чекбокс автообновления при восстановлении панелей
const autoRefreshCheckbox = els . autoRefreshOnRestore ;
if ( autoRefreshCheckbox ) {
// Восстанавливаем состояние из localStorage
const savedState = localStorage . getItem ( 'lb_auto_refresh_on_restore' ) ;
autoRefreshCheckbox . checked = savedState === 'true' ;
// Добавляем обработчик изменения
autoRefreshCheckbox . addEventListener ( 'change' , function ( ) {
localStorage . setItem ( 'lb_auto_refresh_on_restore' , this . checked ? 'true' : 'false' ) ;
console . log ( 'Auto-refresh on restore setting changed:' , this . checked ) ;
} ) ;
autoRefreshCheckbox . title = 'Автоматически обновлять логи панелей при восстановлении из localStorage' ;
}
// Обновляем видимость кнопки refresh и состояние кнопки update при инициализации
updateRefreshButtonVisibility ( ) ;
@@ -6136,15 +6308,21 @@ function reinitializeElements() {
function loadPanelOrder ( ) {
try {
const savedOrder = localStorage . getItem ( 'lb_panel_order' ) ;
console . log ( 'loadPanelOrder: Raw savedOrder from localStorage:' , savedOrder ) ;
if ( savedOrder ) {
const order = JSON . parse ( savedOrder ) ;
console . log ( 'loadPanelOrder: Parsed order:' , order ) ;
// Удаляем дубликаты из загруженного порядка
const uniqueOrder = [ ... new Set ( order ) ] ;
console . log ( 'Panel o rder loa ded :' , uniqueOrder ) ;
console . log ( 'load PanelO rder: Unique or der :' , uniqueOrder ) ;
return uniqueOrder ;
} else {
console . log ( 'loadPanelOrder: No saved order found in localStorage' ) ;
}
} catch ( error ) {
console . error ( 'Error loading panel order:' , error ) ;
console . error ( 'loadPanelOrder: Error loading panel order:' , error ) ;
}
return null ;
}
@@ -6153,13 +6331,29 @@ function reinitializeElements() {
* Применяет сохраненный порядок панелей
*/
function applyPanelOrder ( ) {
if ( ! state . multiViewMode ) return ;
console . log ( 'applyPanelOrder: Starting...' ) ;
console . log ( 'applyPanelOrder: multiViewMode:' , state . multiViewMode ) ;
if ( ! state . multiViewMode ) {
console . log ( 'applyPanelOrder: Not in multiViewMode, exiting' ) ;
return ;
}
const savedOrder = loadPanelOrder ( ) ;
if ( ! savedOrder || savedOrder . length === 0 ) return ;
console . log ( 'applyPanelOrder: savedOrder:' , savedOrder ) ;
if ( ! savedOrder || savedOrder . length === 0 ) {
console . log ( 'applyPanelOrder: No saved order found, exiting' ) ;
return ;
}
const grid = document . getElementById ( 'multiViewGrid' ) ;
if ( ! grid ) return ;
console . log ( 'applyPanelOrder: grid element:' , grid ) ;
if ( ! grid ) {
console . log ( 'applyPanelOrder: Grid not found, exiting' ) ;
return ;
}
// Создаем карту панелей по ID контейнера
const panels = Array . from ( grid . children ) ;
@@ -6176,6 +6370,19 @@ function reinitializeElements() {
const panel = panelMap [ containerId ] ;
if ( panel && panel . parentNode === grid ) {
grid . appendChild ( panel ) ;
// Убеждаемся, что WebSocket соединение установлено для переставленной панели
const service = state . services . find ( s => s . id === containerId ) ;
if ( service ) {
// Проверяем, есть ли уже WebSocket соединение
const existingWs = state . wsConnections && state . wsConnections [ containerId ] ;
if ( ! existingWs || existingWs . readyState !== WebSocket . OPEN ) {
setTimeout ( ( ) => {
console . log ( ` Re-establishing WebSocket for reordered panel: ${ service . name } ( ${ containerId } ) ` ) ;
openMultiViewWs ( service ) ;
} , 100 ) ;
}
}
}
} ) ;
@@ -6192,6 +6399,12 @@ function reinitializeElements() {
console . log ( ` Creating new panel for container: ${ service . name } ( ${ containerId } ) ` ) ;
const panel = createMultiViewPanel ( service ) ;
grid . appendChild ( panel ) ;
// Создаем WebSocket соединение для новой панели
setTimeout ( ( ) => {
console . log ( ` Setting up WebSocket for new panel: ${ service . name } ( ${ containerId } ) ` ) ;
openMultiViewWs ( service ) ;
} , 100 ) ;
}
} else {
console . log ( ` Panel for container ${ containerId } already exists, skipping creation ` ) ;
@@ -6216,6 +6429,52 @@ function reinitializeElements() {
console . log ( ` Updated grid template columns to: repeat( ${ columns } , 1fr) for ${ totalPanels } panels ` ) ;
console . log ( 'Applied panel order:' , state . selectedContainers ) ;
// Инициализируем кнопки уровней логирования для восстановленных панелей
setTimeout ( ( ) => {
console . log ( 'applyPanelOrder: Initializing level buttons for restored panels' ) ;
initializeLevelButtons ( ) ;
// Обновляем логи для восстановленных панелей
setTimeout ( ( ) => {
console . log ( 'applyPanelOrder: Refreshing logs for restored panels' ) ;
refreshLogsAndCounters ( ) ;
// Дополнительная прокрутка к последним логам
setTimeout ( ( ) => {
scrollToBottom ( ) ;
} , 1300 ) ;
} , 300 ) ;
// Проверяем, работают ли обработчики событий корректно
// Если нет, обновляем страницу для полной инициализации
setTimeout ( ( ) => {
const testButton = document . querySelector ( '.level-btn' ) ;
if ( testButton ) {
console . log ( 'applyPanelOrder: Testing event handlers functionality' ) ;
// Создаем тестовое событие для проверки
const testEvent = new MouseEvent ( 'click' , {
bubbles : true ,
cancelable : true ,
view : window
} ) ;
// Проверяем, есть ли обработчик на кнопке
const hasHandler = testButton . onclick !== null ||
testButton . getAttribute ( 'onclick' ) !== null ||
( window . levelButtonClickHandler && document . addEventListener ) ;
if ( ! hasHandler ) {
console . log ( 'applyPanelOrder: Event handlers not working properly, refreshing logs' ) ;
// Обновляем логи панелей вместо обновления страницы
refreshLogsAndCounters ( ) ;
} else {
console . log ( 'applyPanelOrder: Event handlers working correctly' ) ;
}
}
} , 500 ) ;
} , 200 ) ;
}
/**