Исправление ошибки 500 при сохранении исключенных контейнеров
- Исправлен путь к файлу excluded_containers.json в функциях load_excluded_containers и save_excluded_containers - Добавлено создание директории при необходимости - Улучшено логирование операций с файлом - Протестирована функциональность API endpoints - Обновлен список исключенных контейнеров Автор: Сергей Антропов Сайт: https://devops.org.ru
This commit is contained in:
parent
4077142f79
commit
d570807c02
@ -30,17 +30,20 @@ def load_excluded_containers() -> List[str]:
|
||||
Сайт: https://devops.org.ru
|
||||
"""
|
||||
try:
|
||||
with open("app/excluded_containers.json", "r", encoding="utf-8") as f:
|
||||
# Определяем путь к файлу относительно корня проекта
|
||||
file_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "excluded_containers.json")
|
||||
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
return data.get("excluded_containers", [])
|
||||
except FileNotFoundError:
|
||||
docker_logger.warning("Файл app/excluded_containers.json не найден, используем пустой список")
|
||||
docker_logger.warning(f"Файл excluded_containers.json не найден по пути {file_path}, используем пустой список")
|
||||
return []
|
||||
except json.JSONDecodeError as e:
|
||||
docker_logger.error(f"Ошибка парсинга app/excluded_containers.json: {e}")
|
||||
docker_logger.error(f"Ошибка парсинга excluded_containers.json: {e}")
|
||||
return []
|
||||
except Exception as e:
|
||||
docker_logger.error(f"Ошибка загрузки app/excluded_containers.json: {e}")
|
||||
docker_logger.error(f"Ошибка загрузки excluded_containers.json: {e}")
|
||||
return []
|
||||
|
||||
def save_excluded_containers(containers: List[str]) -> bool:
|
||||
@ -50,15 +53,24 @@ def save_excluded_containers(containers: List[str]) -> bool:
|
||||
Сайт: https://devops.org.ru
|
||||
"""
|
||||
try:
|
||||
# Определяем путь к файлу относительно корня проекта
|
||||
file_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "excluded_containers.json")
|
||||
|
||||
data = {
|
||||
"excluded_containers": containers,
|
||||
"description": "Список контейнеров, которые генерируют слишком много логов и исключаются из отображения"
|
||||
}
|
||||
with open("app/excluded_containers.json", "w", encoding="utf-8") as f:
|
||||
|
||||
# Создаем директорию, если она не существует
|
||||
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
||||
|
||||
with open(file_path, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
|
||||
docker_logger.info(f"Список исключенных контейнеров сохранен в {file_path}")
|
||||
return True
|
||||
except Exception as e:
|
||||
docker_logger.error(f"Ошибка сохранения app/excluded_containers.json: {e}")
|
||||
docker_logger.error(f"Ошибка сохранения excluded_containers.json: {e}")
|
||||
return False
|
||||
|
||||
def get_all_projects() -> List[str]:
|
||||
|
@ -1,4 +1,6 @@
|
||||
{
|
||||
"excluded_containers": [],
|
||||
"excluded_containers": [
|
||||
"buildx_buildkit_multiarch-builder0"
|
||||
],
|
||||
"description": "Список контейнеров, которые генерируют слишком много логов и исключаются из отображения"
|
||||
}
|
@ -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 order loaded:', uniqueOrder);
|
||||
console.log('loadPanelOrder: Unique order:', 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -77,7 +77,11 @@
|
||||
</div>
|
||||
<div class="checkbox-item">
|
||||
<input type="checkbox" id="autoupdate" checked>
|
||||
<label for="autoupdate">Auto-update logs</label>
|
||||
<label for="autoupdate">Update logs</label>
|
||||
</div>
|
||||
<div class="checkbox-item">
|
||||
<input type="checkbox" id="autoRefreshOnRestore" checked>
|
||||
<label for="autoRefreshOnRestore">Refresh logs on panel restore</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user