Обновление docker-compose.yml и templates/index.html, удаление test_ws.py
This commit is contained in:
parent
1745d4b5d4
commit
cf7d7c8f5a
@ -35,6 +35,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
- ./snapshots:/app/snapshots
|
- ./snapshots:/app/snapshots
|
||||||
|
- ./:/app
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
user: 0:0
|
user: 0:0
|
||||||
networks:
|
networks:
|
||||||
|
@ -1182,12 +1182,9 @@ footer{position:fixed;right:10px;bottom:10px;opacity:.6;font-size:11px}
|
|||||||
</div>
|
</div>
|
||||||
<div class="control-content" id="tail-content">
|
<div class="control-content" id="tail-content">
|
||||||
<select id="tail">
|
<select id="tail">
|
||||||
<option value="50">50</option>
|
<option value="50" selected>50</option>
|
||||||
<option value="100">100</option>
|
<option value="100">100</option>
|
||||||
<option value="200">200</option>
|
<option value="200">200</option>
|
||||||
<option value="300">300</option>
|
|
||||||
<option value="500" selected>500</option>
|
|
||||||
<option value="1000">1000</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1336,6 +1333,8 @@ footer{position:fixed;right:10px;bottom:10px;opacity:.6;font-size:11px}
|
|||||||
<footer style="display: none;">© LogBoard+</footer>
|
<footer style="display: none;">© LogBoard+</footer>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
console.log('LogBoard+ script loaded - VERSION 2');
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
token: (document.querySelector('meta[name="x-token"]')?.content)||'',
|
token: (document.querySelector('meta[name="x-token"]')?.content)||'',
|
||||||
services: [],
|
services: [],
|
||||||
@ -1465,11 +1464,6 @@ function refreshAllLogs() {
|
|||||||
const multiViewLog = document.querySelector(`.multi-view-log[data-container-id="${containerId}"]`);
|
const multiViewLog = document.querySelector(`.multi-view-log[data-container-id="${containerId}"]`);
|
||||||
if (multiViewLog) {
|
if (multiViewLog) {
|
||||||
multiViewLog.innerHTML = filteredHtml.join('');
|
multiViewLog.innerHTML = filteredHtml.join('');
|
||||||
// Отладочная информация
|
|
||||||
console.log(`Multi-view refresh: Container ${containerId}, ${filteredHtml.length} lines, HTML preview:`, filteredHtml.slice(0, 2).join('').substring(0, 200));
|
|
||||||
// Проверяем, что HTML действительно содержит цветные элементы
|
|
||||||
const coloredElements = multiViewLog.querySelectorAll('.ok, .warn, .err, .dbg, .ansi-red, .ansi-green, .ansi-yellow, .ansi-blue');
|
|
||||||
console.log(`Multi-view: Found ${coloredElements.length} colored elements in container ${containerId}`);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1479,34 +1473,25 @@ function escapeHtml(s){ return s.replace(/[&<>"']/g, m => ({'&':'&','<':'<
|
|||||||
function classify(line){
|
function classify(line){
|
||||||
const l = line.toLowerCase();
|
const l = line.toLowerCase();
|
||||||
|
|
||||||
// Отладочная информация для понимания что происходит
|
|
||||||
if (line.includes('INFO') || line.includes('WARNING') || line.includes('ERROR') || line.includes('DEBUG')) {
|
|
||||||
console.log(`Classifying line: "${line.substring(0, 100)}..."`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Проверяем различные форматы уровней логирования (более специфичные сначала)
|
// Проверяем различные форматы уровней логирования (более специфичные сначала)
|
||||||
|
|
||||||
// DEBUG - ищем точное совпадение уровня логирования
|
// DEBUG - ищем точное совпадение уровня логирования
|
||||||
if (/\s- DEBUG -|\s\[debug\]|level=debug|\bdebug\b(?=\s|$)/.test(l)) {
|
if (/\s- DEBUG -|\s\[debug\]|level=debug|\bdebug\b(?=\s|$)/.test(l)) {
|
||||||
console.log(`Classified as DEBUG: "${line.substring(0, 100)}..."`);
|
|
||||||
return 'dbg';
|
return 'dbg';
|
||||||
}
|
}
|
||||||
|
|
||||||
// ERROR - ищем точное совпадение уровня логирования
|
// ERROR - ищем точное совпадение уровня логирования
|
||||||
if (/\s- ERROR -|\s\[error\]|level=error/.test(l)) {
|
if (/\s- ERROR -|\s\[error\]|level=error/.test(l)) {
|
||||||
console.log(`Classified as ERROR: "${line.substring(0, 100)}..."`);
|
|
||||||
return 'err';
|
return 'err';
|
||||||
}
|
}
|
||||||
|
|
||||||
// WARNING - ищем точное совпадение уровня логирования
|
// WARNING - ищем точное совпадение уровня логирования
|
||||||
if (/\s- WARNING -|\s\[warn\]|level=warn/.test(l)) {
|
if (/\s- WARNING -|\s\[warn\]|level=warn/.test(l)) {
|
||||||
console.log(`Classified as WARNING: "${line.substring(0, 100)}..."`);
|
|
||||||
return 'warn';
|
return 'warn';
|
||||||
}
|
}
|
||||||
|
|
||||||
// INFO - ищем точное совпадение уровня логирования
|
// INFO - ищем точное совпадение уровня логирования
|
||||||
if (/\s- INFO -|\s\[info\]|level=info/.test(l)) {
|
if (/\s- INFO -|\s\[info\]|level=info/.test(l)) {
|
||||||
console.log(`Classified as INFO: "${line.substring(0, 100)}..."`);
|
|
||||||
return 'ok';
|
return 'ok';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1516,12 +1501,6 @@ function classify(line){
|
|||||||
if (/\bwarning\b/i.test(l)) return 'warn';
|
if (/\bwarning\b/i.test(l)) return 'warn';
|
||||||
if (/\binfo\b/i.test(l)) return 'ok';
|
if (/\binfo\b/i.test(l)) return 'ok';
|
||||||
|
|
||||||
// Отладочная информация для неклассифицированных строк
|
|
||||||
if (line.includes('level=')) {
|
|
||||||
console.log(`Unclassified line with level: "${line.substring(0, 200)}..."`);
|
|
||||||
console.log(`Lowercase version: "${l.substring(0, 200)}..."`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'other';
|
return 'other';
|
||||||
}
|
}
|
||||||
function allowedByLevel(cls){
|
function allowedByLevel(cls){
|
||||||
@ -1648,7 +1627,7 @@ function buildTabs(){
|
|||||||
b.className='tab' + ((state.current && svc.id===state.current.id) ? ' active':'');
|
b.className='tab' + ((state.current && svc.id===state.current.id) ? ' active':'');
|
||||||
b.textContent = (svc.project?(`[${svc.project}] `):'') + (svc.service||svc.name);
|
b.textContent = (svc.project?(`[${svc.project}] `):'') + (svc.service||svc.name);
|
||||||
b.title = `${svc.name} • ${svc.image} • ${svc.status}`;
|
b.title = `${svc.name} • ${svc.image} • ${svc.status}`;
|
||||||
b.onclick = ()=> switchToSingle(svc);
|
b.onclick = async ()=> await switchToSingle(svc);
|
||||||
els.tabs.appendChild(b);
|
els.tabs.appendChild(b);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1680,18 +1659,23 @@ function buildTabs(){
|
|||||||
${svc.url ? `<a href="${svc.url}" target="_blank" class="container-link" title="Открыть сайт"><i class="fas fa-external-link-alt"></i></a>` : ''}
|
${svc.url ? `<a href="${svc.url}" target="_blank" class="container-link" title="Открыть сайт"><i class="fas fa-external-link-alt"></i></a>` : ''}
|
||||||
</div>
|
</div>
|
||||||
<div class="container-select">
|
<div class="container-select">
|
||||||
<input type="checkbox" id="select-${svc.id}" class="container-checkbox" data-container-id="${svc.id}">
|
<input type="checkbox" id="select-${svc.id}" class="container-checkbox" data-container-id="${svc.id}" ${state.selectedContainers.includes(svc.id) ? 'checked' : ''}>
|
||||||
<label for="select-${svc.id}" class="container-checkbox-label" title="Выбрать для мультипросмотра"></label>
|
<label for="select-${svc.id}" class="container-checkbox-label" title="Выбрать для мультипросмотра"></label>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
item.onclick = (e) => {
|
// Устанавливаем состояние selected для контейнера
|
||||||
|
if (state.selectedContainers.includes(svc.id)) {
|
||||||
|
item.classList.add('selected');
|
||||||
|
}
|
||||||
|
|
||||||
|
item.onclick = async (e) => {
|
||||||
// Не переключаем контейнер, если кликнули на ссылку или чекбокс
|
// Не переключаем контейнер, если кликнули на ссылку или чекбокс
|
||||||
if (e.target.closest('.container-link') || e.target.closest('.container-select')) {
|
if (e.target.closest('.container-link') || e.target.closest('.container-select')) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switchToSingle(svc);
|
await switchToSingle(svc);
|
||||||
};
|
};
|
||||||
els.containerList.appendChild(item);
|
els.containerList.appendChild(item);
|
||||||
});
|
});
|
||||||
@ -1936,29 +1920,45 @@ async function removeExcludedContainer(containerName) {
|
|||||||
|
|
||||||
// Функции для мультивыбора контейнеров
|
// Функции для мультивыбора контейнеров
|
||||||
function toggleContainerSelection(containerId) {
|
function toggleContainerSelection(containerId) {
|
||||||
|
console.log('toggleContainerSelection called for:', containerId);
|
||||||
|
console.log('Current state.selectedContainers before:', [...state.selectedContainers]);
|
||||||
|
|
||||||
const index = state.selectedContainers.indexOf(containerId);
|
const index = state.selectedContainers.indexOf(containerId);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
state.selectedContainers.splice(index, 1);
|
state.selectedContainers.splice(index, 1);
|
||||||
|
console.log('Removed container from selection:', containerId);
|
||||||
} else {
|
} else {
|
||||||
state.selectedContainers.push(containerId);
|
state.selectedContainers.push(containerId);
|
||||||
|
console.log('Added container to selection:', containerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('Current selected containers after:', state.selectedContainers);
|
||||||
|
|
||||||
updateContainerSelectionUI();
|
updateContainerSelectionUI();
|
||||||
updateMultiViewMode();
|
updateMultiViewMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateContainerSelectionUI() {
|
function updateContainerSelectionUI() {
|
||||||
|
console.log('updateContainerSelectionUI called, selected containers:', state.selectedContainers);
|
||||||
|
|
||||||
// Обновляем чекбоксы
|
// Обновляем чекбоксы
|
||||||
document.querySelectorAll('.container-checkbox').forEach(checkbox => {
|
const checkboxes = document.querySelectorAll('.container-checkbox');
|
||||||
|
console.log('Found checkboxes:', checkboxes.length);
|
||||||
|
|
||||||
|
checkboxes.forEach(checkbox => {
|
||||||
const containerId = checkbox.getAttribute('data-container-id');
|
const containerId = checkbox.getAttribute('data-container-id');
|
||||||
const containerItem = checkbox.closest('.container-item');
|
const containerItem = checkbox.closest('.container-item');
|
||||||
|
|
||||||
|
console.log('Processing checkbox for container:', containerId, 'checked:', checkbox.checked, 'should be:', state.selectedContainers.includes(containerId));
|
||||||
|
|
||||||
if (state.selectedContainers.includes(containerId)) {
|
if (state.selectedContainers.includes(containerId)) {
|
||||||
checkbox.checked = true;
|
checkbox.checked = true;
|
||||||
containerItem.classList.add('selected');
|
containerItem.classList.add('selected');
|
||||||
|
console.log('Container selected:', containerId);
|
||||||
} else {
|
} else {
|
||||||
checkbox.checked = false;
|
checkbox.checked = false;
|
||||||
containerItem.classList.remove('selected');
|
containerItem.classList.remove('selected');
|
||||||
|
console.log('Container deselected:', containerId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1966,19 +1966,24 @@ function updateContainerSelectionUI() {
|
|||||||
updateLogTitle();
|
updateLogTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateMultiViewMode() {
|
async function updateMultiViewMode() {
|
||||||
console.log(`updateMultiViewMode called: selectedContainers.length = ${state.selectedContainers.length}, containers:`, state.selectedContainers);
|
console.log(`updateMultiViewMode called: selectedContainers.length = ${state.selectedContainers.length}, containers:`, state.selectedContainers);
|
||||||
|
|
||||||
if (state.selectedContainers.length > 1) {
|
if (state.selectedContainers.length > 1) {
|
||||||
state.multiViewMode = true;
|
state.multiViewMode = true;
|
||||||
state.current = null; // Сбрасываем текущий контейнер
|
state.current = null; // Сбрасываем текущий контейнер
|
||||||
console.log('Setting up multi-view mode');
|
console.log('Setting up multi-view mode');
|
||||||
setupMultiView();
|
await setupMultiView();
|
||||||
} else if (state.selectedContainers.length === 1) {
|
} else if (state.selectedContainers.length === 1) {
|
||||||
|
// Переключаемся в single view для одного контейнера
|
||||||
|
console.log('Switching from multi-view to single view');
|
||||||
state.multiViewMode = false;
|
state.multiViewMode = false;
|
||||||
const selectedService = state.services.find(s => s.id === state.selectedContainers[0]);
|
const selectedService = state.services.find(s => s.id === state.selectedContainers[0]);
|
||||||
if (selectedService) {
|
if (selectedService) {
|
||||||
switchToSingle(selectedService);
|
console.log('Switching to single view for:', selectedService.name);
|
||||||
|
console.log('updateMultiViewMode: About to call switchToSingle - VERSION 2');
|
||||||
|
await switchToSingle(selectedService);
|
||||||
|
console.log('updateMultiViewMode: switchToSingle call completed - VERSION 2');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Когда снимаем все галочки, переключаемся в single view
|
// Когда снимаем все галочки, переключаемся в single view
|
||||||
@ -2004,8 +2009,32 @@ function updateMultiViewMode() {
|
|||||||
console.log(`Multi-view mode updated: multiViewMode = ${state.multiViewMode}`);
|
console.log(`Multi-view mode updated: multiViewMode = ${state.multiViewMode}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupMultiView() {
|
async function setupMultiView() {
|
||||||
console.log('setupMultiView called');
|
console.log('setupMultiView called');
|
||||||
|
|
||||||
|
// Проверяем, что у нас действительно больше одного контейнера
|
||||||
|
if (state.selectedContainers.length <= 1) {
|
||||||
|
console.log('setupMultiView: Not enough containers for multi-view, switching to single view');
|
||||||
|
if (state.selectedContainers.length === 1) {
|
||||||
|
const selectedService = state.services.find(s => s.id === state.selectedContainers[0]);
|
||||||
|
if (selectedService) {
|
||||||
|
console.log('setupMultiView: Calling switchToSingle for:', selectedService.name);
|
||||||
|
await switchToSingle(selectedService);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('setupMultiView: No containers selected, clearing log area');
|
||||||
|
clearLogArea();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Дополнительная проверка - если уже есть мультипросмотр, удаляем его для пересоздания
|
||||||
|
const existingMultiView = document.getElementById('multiViewGrid');
|
||||||
|
if (existingMultiView) {
|
||||||
|
console.log('setupMultiView: Multi-view already exists, removing for recreation');
|
||||||
|
existingMultiView.remove();
|
||||||
|
}
|
||||||
|
|
||||||
const logArea = document.querySelector('.log-area');
|
const logArea = document.querySelector('.log-area');
|
||||||
if (!logArea) {
|
if (!logArea) {
|
||||||
console.log('Log area not found');
|
console.log('Log area not found');
|
||||||
@ -2025,39 +2054,63 @@ function setupMultiView() {
|
|||||||
|
|
||||||
// Определяем количество колонок в зависимости от количества контейнеров
|
// Определяем количество колонок в зависимости от количества контейнеров
|
||||||
let columns = 1;
|
let columns = 1;
|
||||||
if (state.selectedContainers.length <= 2) columns = 2;
|
if (state.selectedContainers.length === 1) columns = 1;
|
||||||
|
else if (state.selectedContainers.length === 2) columns = 2;
|
||||||
else if (state.selectedContainers.length <= 4) columns = 2;
|
else if (state.selectedContainers.length <= 4) columns = 2;
|
||||||
else if (state.selectedContainers.length <= 6) columns = 3;
|
else if (state.selectedContainers.length <= 6) columns = 3;
|
||||||
else columns = 4;
|
else columns = 4;
|
||||||
|
|
||||||
|
console.log(`setupMultiView: Creating grid with ${columns} columns for ${state.selectedContainers.length} containers`);
|
||||||
gridContainer.style.gridTemplateColumns = `repeat(${columns}, 1fr)`;
|
gridContainer.style.gridTemplateColumns = `repeat(${columns}, 1fr)`;
|
||||||
|
console.log(`setupMultiView: Grid template columns set to: repeat(${columns}, 1fr)`);
|
||||||
|
|
||||||
// Создаем панели для каждого выбранного контейнера
|
// Создаем панели для каждого выбранного контейнера
|
||||||
state.selectedContainers.forEach(containerId => {
|
console.log(`setupMultiView: Creating panels for ${state.selectedContainers.length} containers:`, state.selectedContainers);
|
||||||
|
state.selectedContainers.forEach((containerId, index) => {
|
||||||
const service = state.services.find(s => s.id === containerId);
|
const service = state.services.find(s => s.id === containerId);
|
||||||
if (!service) return;
|
if (!service) {
|
||||||
|
console.error(`setupMultiView: Service not found for container ID: ${containerId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`setupMultiView: Creating panel ${index + 1} for service: ${service.name} (${containerId})`);
|
||||||
const panel = createMultiViewPanel(service);
|
const panel = createMultiViewPanel(service);
|
||||||
gridContainer.appendChild(panel);
|
gridContainer.appendChild(panel);
|
||||||
|
console.log(`setupMultiView: Panel ${index + 1} added to grid, total children: ${gridContainer.children.length}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (logContent) {
|
if (logContent) {
|
||||||
logContent.appendChild(gridContainer);
|
logContent.appendChild(gridContainer);
|
||||||
|
console.log(`setupMultiView: Grid added to log content, grid children: ${gridContainer.children.length}`);
|
||||||
|
|
||||||
|
// Проверяем, что все панели созданы правильно
|
||||||
|
const panels = gridContainer.querySelectorAll('.multi-view-panel');
|
||||||
|
console.log(`setupMultiView: Total panels found in grid: ${panels.length}`);
|
||||||
|
panels.forEach((panel, index) => {
|
||||||
|
const containerId = panel.getAttribute('data-container-id');
|
||||||
|
const title = panel.querySelector('.multi-view-title');
|
||||||
|
console.log(`setupMultiView: Panel ${index + 1}: containerId=${containerId}, title="${title?.textContent}"`);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error('setupMultiView: logContent not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Применяем настройки wrap lines
|
// Применяем настройки wrap lines
|
||||||
applyWrapSettings();
|
applyWrapSettings();
|
||||||
|
|
||||||
// Подключаем WebSocket для каждого контейнера
|
// Подключаем WebSocket для каждого контейнера
|
||||||
state.selectedContainers.forEach(containerId => {
|
console.log(`setupMultiView: Setting up WebSockets for ${state.selectedContainers.length} containers`);
|
||||||
|
state.selectedContainers.forEach((containerId, index) => {
|
||||||
const service = state.services.find(s => s.id === containerId);
|
const service = state.services.find(s => s.id === containerId);
|
||||||
if (service) {
|
if (service) {
|
||||||
console.log(`Setting up WebSocket for multi-view container: ${service.name} (${containerId})`);
|
console.log(`setupMultiView: Setting up WebSocket ${index + 1} for multi-view container: ${service.name} (${containerId})`);
|
||||||
openMultiViewWs(service);
|
openMultiViewWs(service);
|
||||||
} else {
|
} else {
|
||||||
console.error(`Service not found for container ID: ${containerId}`);
|
console.error(`setupMultiView: Service not found for container ID: ${containerId}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(`setupMultiView: Multi-view setup completed for ${state.selectedContainers.length} containers`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMultiViewPanel(service) {
|
function createMultiViewPanel(service) {
|
||||||
@ -2065,6 +2118,7 @@ function createMultiViewPanel(service) {
|
|||||||
const panel = document.createElement('div');
|
const panel = document.createElement('div');
|
||||||
panel.className = 'multi-view-panel';
|
panel.className = 'multi-view-panel';
|
||||||
panel.setAttribute('data-container-id', service.id);
|
panel.setAttribute('data-container-id', service.id);
|
||||||
|
console.log(`createMultiViewPanel: Panel element created with data-container-id: ${service.id}`);
|
||||||
|
|
||||||
panel.innerHTML = `
|
panel.innerHTML = `
|
||||||
<div class="multi-view-header">
|
<div class="multi-view-header">
|
||||||
@ -2089,6 +2143,7 @@ function createMultiViewPanel(service) {
|
|||||||
|
|
||||||
function openMultiViewWs(service) {
|
function openMultiViewWs(service) {
|
||||||
const containerId = service.id;
|
const containerId = service.id;
|
||||||
|
console.log(`openMultiViewWs: Starting WebSocket setup for ${service.name} (${containerId})`);
|
||||||
|
|
||||||
// Закрываем существующее соединение
|
// Закрываем существующее соединение
|
||||||
closeWs(containerId);
|
closeWs(containerId);
|
||||||
@ -2127,18 +2182,34 @@ function openMultiViewWs(service) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Сохраняем соединение с полным набором полей как в openWs
|
// Сохраняем соединение с полным набором полей как в openWs
|
||||||
|
const logEl = document.querySelector(`.multi-view-log[data-container-id="${containerId}"]`);
|
||||||
|
console.log(`openMultiViewWs: Found log element for ${service.name}:`, !!logEl);
|
||||||
|
|
||||||
state.open[containerId] = {
|
state.open[containerId] = {
|
||||||
ws: ws,
|
ws: ws,
|
||||||
serviceName: service.service,
|
serviceName: service.service,
|
||||||
logEl: document.querySelector(`.multi-view-log[data-container-id="${containerId}"]`),
|
logEl: logEl,
|
||||||
wrapEl: document.querySelector(`.multi-view-log[data-container-id="${containerId}"]`),
|
wrapEl: logEl,
|
||||||
counters: {dbg:0, info:0, warn:0, err:0},
|
counters: {dbg:0, info:0, warn:0, err:0},
|
||||||
pausedBuffer: [],
|
pausedBuffer: [],
|
||||||
allLogs: [] // Добавляем буфер для логов
|
allLogs: [] // Добавляем буфер для логов
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log(`openMultiViewWs: WebSocket setup completed for ${service.name} (${containerId})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearLogArea() {
|
function clearLogArea() {
|
||||||
|
console.log('clearLogArea called');
|
||||||
|
|
||||||
|
// Очищаем мультипросмотр если он был активен
|
||||||
|
if (state.multiViewMode) {
|
||||||
|
console.log('Clearing multi-view grid');
|
||||||
|
const multiViewGrid = document.getElementById('multiViewGrid');
|
||||||
|
if (multiViewGrid) {
|
||||||
|
multiViewGrid.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const logContent = document.querySelector('.log-content');
|
const logContent = document.querySelector('.log-content');
|
||||||
if (logContent) {
|
if (logContent) {
|
||||||
logContent.innerHTML = '<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: var(--muted); font-size: 14px;">Выберите контейнер для просмотра логов</div>';
|
logContent.innerHTML = '<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: var(--muted); font-size: 14px;">Выберите контейнер для просмотра логов</div>';
|
||||||
@ -2154,13 +2225,20 @@ function updateLogTitle() {
|
|||||||
const logTitle = document.getElementById('logTitle');
|
const logTitle = document.getElementById('logTitle');
|
||||||
if (!logTitle) return;
|
if (!logTitle) return;
|
||||||
|
|
||||||
|
console.log('updateLogTitle called, selected containers:', state.selectedContainers.length);
|
||||||
|
|
||||||
if (state.selectedContainers.length === 0) {
|
if (state.selectedContainers.length === 0) {
|
||||||
logTitle.textContent = 'LogBoard+';
|
logTitle.textContent = 'LogBoard+';
|
||||||
|
console.log('Log title set to: LogBoard+');
|
||||||
} else if (state.selectedContainers.length === 1) {
|
} else if (state.selectedContainers.length === 1) {
|
||||||
const service = state.services.find(s => s.id === state.selectedContainers[0]);
|
const service = state.services.find(s => s.id === state.selectedContainers[0]);
|
||||||
logTitle.textContent = `${service.name} (${service.service || service.name})`;
|
if (service) {
|
||||||
|
logTitle.textContent = `${service.name} (${service.service || service.name})`;
|
||||||
|
console.log('Log title set to single container:', service.name);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
logTitle.textContent = `Multi-view: ${state.selectedContainers.length} containers`;
|
logTitle.textContent = `Multi-view: ${state.selectedContainers.length} containers`;
|
||||||
|
console.log('Log title set to multi-view:', state.selectedContainers.length, 'containers');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2201,7 +2279,7 @@ async function fetchServices(){
|
|||||||
state.services = data;
|
state.services = data;
|
||||||
|
|
||||||
buildTabs();
|
buildTabs();
|
||||||
if (!state.current && state.services.length) switchToSingle(state.services[0]);
|
if (!state.current && state.services.length) await switchToSingle(state.services[0]);
|
||||||
|
|
||||||
// Добавляем обработчики для счетчиков после загрузки сервисов
|
// Добавляем обработчики для счетчиков после загрузки сервисов
|
||||||
addCounterClickHandlers();
|
addCounterClickHandlers();
|
||||||
@ -2276,20 +2354,29 @@ function openWs(svc, panel){
|
|||||||
|
|
||||||
const ws = new WebSocket(wsUrl(id, (svc.service||svc.name), svc.project||''));
|
const ws = new WebSocket(wsUrl(id, (svc.service||svc.name), svc.project||''));
|
||||||
state.open[id] = {ws, logEl, wrapEl, counters, pausedBuffer:[], serviceName: (svc.service||svc.name)};
|
state.open[id] = {ws, logEl, wrapEl, counters, pausedBuffer:[], serviceName: (svc.service||svc.name)};
|
||||||
|
console.log(`openWs: Created state.open[${id}] with logEl:`, !!logEl, 'wrapEl:', !!wrapEl);
|
||||||
|
|
||||||
ws.onopen = ()=> setWsState('on');
|
ws.onopen = ()=> {
|
||||||
|
setWsState('on');
|
||||||
|
// Очищаем сообщение "Connecting..." когда соединение установлено
|
||||||
|
if (state.current && state.current.id === id && els.logContent) {
|
||||||
|
els.logContent.innerHTML = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
ws.onclose = ()=> setWsState(Object.keys(state.open).length? 'on':'off');
|
ws.onclose = ()=> setWsState(Object.keys(state.open).length? 'on':'off');
|
||||||
ws.onerror = ()=> setWsState('err');
|
ws.onerror = ()=> setWsState('err');
|
||||||
ws.onmessage = (ev)=>{
|
ws.onmessage = (ev)=>{
|
||||||
console.log(`Received WebSocket message: ${ev.data.substring(0, 100)}...`);
|
console.log(`Received WebSocket message: ${ev.data.substring(0, 100)}...`);
|
||||||
|
|
||||||
const parts = (ev.data||'').split(/\r?\n/);
|
const parts = (ev.data||'').split(/\r?\n/);
|
||||||
|
console.log(`openWs: Processing ${parts.length} lines for container ${id}`);
|
||||||
|
|
||||||
for (let i=0;i<parts.length;i++){
|
for (let i=0;i<parts.length;i++){
|
||||||
if (parts[i].length===0 && i===parts.length-1) continue;
|
if (parts[i].length===0 && i===parts.length-1) continue;
|
||||||
// harvest instance ids if present
|
// harvest instance ids if present
|
||||||
const pr = parsePrefixAndStrip(parts[i]);
|
const pr = parsePrefixAndStrip(parts[i]);
|
||||||
if (pr){ if (!(pr.id8 in inst.filters)) { inst.filters[pr.id8] = true; updateIdFiltersBar(); } }
|
if (pr){ if (!(pr.id8 in inst.filters)) { inst.filters[pr.id8] = true; updateIdFiltersBar(); } }
|
||||||
|
console.log(`openWs: Calling handleLine for container ${id}, line: "${parts[i].substring(0, 50)}..."`);
|
||||||
handleLine(id, parts[i]);
|
handleLine(id, parts[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2305,6 +2392,8 @@ function openWs(svc, panel){
|
|||||||
|
|
||||||
// Глобальная функция для обработки логов
|
// Глобальная функция для обработки логов
|
||||||
function handleLine(id, line){
|
function handleLine(id, line){
|
||||||
|
console.log(`handleLine: Called for container ${id}, line: "${line.substring(0, 50)}..."`);
|
||||||
|
|
||||||
const obj = state.open[id];
|
const obj = state.open[id];
|
||||||
if (!obj) {
|
if (!obj) {
|
||||||
console.error(`handleLine: Object not found for container ${id}, available containers:`, Object.keys(state.open));
|
console.error(`handleLine: Object not found for container ${id}, available containers:`, Object.keys(state.open));
|
||||||
@ -2325,23 +2414,9 @@ function handleLine(id, line){
|
|||||||
if (cls==='ok') obj.counters.info++;
|
if (cls==='ok') obj.counters.info++;
|
||||||
if (cls==='warn') obj.counters.warn++;
|
if (cls==='warn') obj.counters.warn++;
|
||||||
if (cls==='err') obj.counters.err++;
|
if (cls==='err') obj.counters.err++;
|
||||||
|
|
||||||
// Отладочная информация для первых нескольких строк
|
|
||||||
if (obj.counters.dbg + obj.counters.info + obj.counters.warn + obj.counters.err < 10) {
|
|
||||||
console.log(`Line: "${line.substring(0, 100)}..." -> Class: ${cls}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Отладочная информация о счетчиках
|
|
||||||
if (obj.counters.dbg + obj.counters.info + obj.counters.warn + obj.counters.err < 5) {
|
|
||||||
console.log(`Counters: DEBUG=${obj.counters.dbg}, INFO=${obj.counters.info}, WARN=${obj.counters.warn}, ERROR=${obj.counters.err}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const html = `<span class="line ${cls}">${ansiToHtml(line)}</span>\n`;
|
const html = `<span class="line ${cls}">${ansiToHtml(line)}</span>\n`;
|
||||||
// Отладочная информация для HTML
|
|
||||||
if (obj.counters && obj.counters.dbg + obj.counters.info + obj.counters.warn + obj.counters.err < 3) {
|
|
||||||
console.log(`Generated HTML for class ${cls}:`, html.substring(0, 200));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Сохраняем все логи в буфере (всегда)
|
// Сохраняем все логи в буфере (всегда)
|
||||||
if (!obj.allLogs) obj.allLogs = [];
|
if (!obj.allLogs) obj.allLogs = [];
|
||||||
@ -2363,18 +2438,22 @@ function handleLine(id, line){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update modern interface
|
// Update modern interface
|
||||||
|
console.log(`handleLine: Checking modern interface update - state.current:`, state.current, `id:`, id, `els.logContent:`, !!els.logContent);
|
||||||
if (state.current && state.current.id === id && els.logContent) {
|
if (state.current && state.current.id === id && els.logContent) {
|
||||||
els.logContent.innerHTML = obj.logEl.innerHTML;
|
console.log(`handleLine: Updating modern interface for container ${id} with html:`, html.substring(0, 100));
|
||||||
const logContent = document.querySelector('.log-content');
|
// Добавляем новую строку напрямую в современный интерфейс
|
||||||
if (logContent && els.autoscroll && els.autoscroll.checked) {
|
els.logContent.insertAdjacentHTML('beforeend', html);
|
||||||
logContent.scrollTop = logContent.scrollHeight;
|
console.log(`handleLine: Modern interface updated, logContent children count:`, els.logContent.children.length);
|
||||||
|
if (els.autoscroll && els.autoscroll.checked) {
|
||||||
|
els.logContent.scrollTop = els.logContent.scrollHeight;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
console.log(`handleLine: Modern interface update skipped - state.current:`, state.current?.id, `id:`, id, `logContent exists:`, !!els.logContent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update multi-view interface
|
// Update multi-view interface
|
||||||
if (state.multiViewMode && state.selectedContainers.includes(id)) {
|
if (state.multiViewMode && state.selectedContainers.includes(id)) {
|
||||||
console.log(`Multi-view processing: container ${id}, shouldShow: ${shouldShow}, multiViewMode: ${state.multiViewMode}, selectedContainers:`, state.selectedContainers);
|
|
||||||
const multiViewLog = document.querySelector(`.multi-view-log[data-container-id="${id}"]`);
|
const multiViewLog = document.querySelector(`.multi-view-log[data-container-id="${id}"]`);
|
||||||
if (multiViewLog) {
|
if (multiViewLog) {
|
||||||
if (shouldShow) {
|
if (shouldShow) {
|
||||||
@ -2382,15 +2461,10 @@ function handleLine(id, line){
|
|||||||
if (els.autoscroll && els.autoscroll.checked) {
|
if (els.autoscroll && els.autoscroll.checked) {
|
||||||
multiViewLog.scrollTop = multiViewLog.scrollHeight;
|
multiViewLog.scrollTop = multiViewLog.scrollHeight;
|
||||||
}
|
}
|
||||||
// Отладочная информация для multi-view
|
console.log(`handleLine: Updated multi-view for container ${id}, log element found: true`);
|
||||||
console.log(`Multi-view: Added line with class ${cls} to container ${id}, HTML:`, html.substring(0, 100));
|
|
||||||
} else {
|
|
||||||
// Отладочная информация для отфильтрованных строк
|
|
||||||
console.log(`Multi-view: Filtered out line with class ${cls} from container ${id}, line: "${line.substring(0, 100)}..."`);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Отладочная информация если элемент не найден
|
console.error(`handleLine: Multi-view log element not found for container ${id}`);
|
||||||
console.log(`Multi-view: Element not found for container ${id}, available elements:`, document.querySelectorAll('.multi-view-log').length);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2416,24 +2490,87 @@ function ensurePanel(svc){
|
|||||||
return panel;
|
return panel;
|
||||||
}
|
}
|
||||||
|
|
||||||
function switchToSingle(svc){
|
async function switchToSingle(svc){
|
||||||
// Legacy functionality
|
console.log('switchToSingle: ENTRY POINT - function called - VERSION 2');
|
||||||
|
console.log('switchToSingle: svc parameter:', svc);
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('switchToSingle called for:', svc.name);
|
||||||
|
console.log('switchToSingle: Starting function execution');
|
||||||
|
console.log('switchToSingle: svc name:', svc.name, 'id:', svc.id);
|
||||||
|
console.log('switchToSingle: state.current:', state.current?.name, 'multiViewMode:', state.multiViewMode);
|
||||||
|
|
||||||
|
// Всегда очищаем мультипросмотр при переключении в single view
|
||||||
|
console.log('Clearing multi-view mode');
|
||||||
|
state.multiViewMode = false;
|
||||||
|
|
||||||
|
// Закрываем WebSocket соединения для мультипросмотра
|
||||||
|
console.log('switchToSingle: Closing WebSocket connections for multi-view');
|
||||||
|
state.selectedContainers.forEach(containerId => {
|
||||||
|
closeWs(containerId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Очищаем область логов
|
||||||
|
console.log('switchToSingle: Clearing log content');
|
||||||
|
if (els.logContent) {
|
||||||
|
els.logContent.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Удаляем мультипросмотр из DOM
|
||||||
|
const multiViewGrid = document.getElementById('multiViewGrid');
|
||||||
|
if (multiViewGrid) {
|
||||||
|
console.log('Removing multi-view grid from DOM');
|
||||||
|
multiViewGrid.remove();
|
||||||
|
} else {
|
||||||
|
console.log('Multi-view grid not found in DOM');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy functionality (скрытая)
|
||||||
|
console.log('switchToSingle: Setting up legacy functionality');
|
||||||
setLayout('tabs');
|
setLayout('tabs');
|
||||||
els.grid.innerHTML='';
|
els.grid.innerHTML='';
|
||||||
const panel = ensurePanel(svc);
|
const panel = ensurePanel(svc);
|
||||||
panel.querySelector('.log').textContent='';
|
panel.querySelector('.log').textContent='';
|
||||||
closeWs(svc.id);
|
closeWs(svc.id);
|
||||||
|
console.log('switchToSingle: Calling openWs for:', svc.name, 'id:', svc.id);
|
||||||
openWs(svc, panel);
|
openWs(svc, panel);
|
||||||
state.current = svc;
|
state.current = svc;
|
||||||
|
console.log('switchToSingle: Set state.current to:', svc.name, 'id:', svc.id);
|
||||||
|
console.log('switchToSingle: state.current after setting:', state.current);
|
||||||
buildTabs();
|
buildTabs();
|
||||||
for (const p of [...els.grid.children]) if (p!==panel) p.remove();
|
for (const p of [...els.grid.children]) if (p!==panel) p.remove();
|
||||||
|
|
||||||
|
// Обновляем состояние выбранных контейнеров для корректного отображения заголовка
|
||||||
|
state.selectedContainers = [svc.id];
|
||||||
|
|
||||||
// Modern interface updates
|
// Modern interface updates
|
||||||
if (els.logTitle) {
|
if (els.logTitle) {
|
||||||
els.logTitle.textContent = `${svc.name} (${svc.service || svc.name})`;
|
els.logTitle.textContent = `${svc.name} (${svc.service || svc.name})`;
|
||||||
}
|
}
|
||||||
if (els.logContent) {
|
if (els.logContent) {
|
||||||
els.logContent.textContent = 'Connecting...';
|
els.logContent.innerHTML = '<span class="line info">Connecting...</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем logEl для современного интерфейса
|
||||||
|
const obj = state.open[svc.id];
|
||||||
|
if (obj && els.logContent) {
|
||||||
|
obj.logEl = els.logContent;
|
||||||
|
obj.wrapEl = els.logContent.parentElement;
|
||||||
|
console.log('switchToSingle: Updated obj.logEl and obj.wrapEl for modern interface');
|
||||||
|
|
||||||
|
// Если у нас уже есть логи в буфере, отображаем их
|
||||||
|
if (obj.allLogs && obj.allLogs.length > 0) {
|
||||||
|
console.log(`switchToSingle: Restoring ${obj.allLogs.length} buffered log lines`);
|
||||||
|
els.logContent.innerHTML = '';
|
||||||
|
obj.allLogs.forEach(logEntry => {
|
||||||
|
if (allowedByLevel(logEntry.cls) && applyFilter(logEntry.line)) {
|
||||||
|
els.logContent.insertAdjacentHTML('beforeend', logEntry.html);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (els.autoscroll && els.autoscroll.checked) {
|
||||||
|
els.logContent.scrollTop = els.logContent.scrollHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update active state in container list
|
// Update active state in container list
|
||||||
@ -2445,15 +2582,28 @@ function switchToSingle(svc){
|
|||||||
activeItem.classList.add('active');
|
activeItem.classList.add('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Обновляем состояние чекбоксов после переключения контейнера
|
||||||
|
updateContainerSelectionUI();
|
||||||
|
|
||||||
|
// Обновляем заголовок
|
||||||
|
updateLogTitle();
|
||||||
|
|
||||||
|
// Обновляем счетчики для нового контейнера
|
||||||
|
await updateCounters(svc.id);
|
||||||
|
|
||||||
// Добавляем обработчики для счетчиков после переключения контейнера
|
// Добавляем обработчики для счетчиков после переключения контейнера
|
||||||
addCounterClickHandlers();
|
addCounterClickHandlers();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('switchToSingle: Error occurred:', error);
|
||||||
|
console.error('switchToSingle: Error stack:', error.stack);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openMulti(ids){
|
async function openMulti(ids){
|
||||||
els.grid.innerHTML='';
|
els.grid.innerHTML='';
|
||||||
const chosen = state.services.filter(s=> ids.includes(s.id));
|
const chosen = state.services.filter(s=> ids.includes(s.id));
|
||||||
const n = chosen.length;
|
const n = chosen.length;
|
||||||
if (n<=1){ if (n===1) switchToSingle(chosen[0]); return; }
|
if (n<=1){ if (n===1) await switchToSingle(chosen[0]); return; }
|
||||||
setLayout(n>=4 ? 'grid4' : n===3 ? 'grid3' : 'grid2');
|
setLayout(n>=4 ? 'grid4' : n===3 ? 'grid3' : 'grid2');
|
||||||
for (const svc of chosen){
|
for (const svc of chosen){
|
||||||
const panel = ensurePanel(svc);
|
const panel = ensurePanel(svc);
|
||||||
@ -2671,7 +2821,7 @@ async function refreshLogsAndCounters() {
|
|||||||
const updatedContainer = state.services.find(s => s.id === currentId);
|
const updatedContainer = state.services.find(s => s.id === currentId);
|
||||||
if (updatedContainer) {
|
if (updatedContainer) {
|
||||||
// Переключаемся на обновленный контейнер
|
// Переключаемся на обновленный контейнер
|
||||||
switchToSingle(updatedContainer);
|
await switchToSingle(updatedContainer);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('No container selected');
|
console.log('No container selected');
|
||||||
@ -2728,11 +2878,11 @@ els.refreshBtn.onclick = async () => {
|
|||||||
const updatedContainer = state.services.find(s => s.id === currentId);
|
const updatedContainer = state.services.find(s => s.id === currentId);
|
||||||
if (updatedContainer) {
|
if (updatedContainer) {
|
||||||
// Переключаемся на обновленный контейнер
|
// Переключаемся на обновленный контейнер
|
||||||
switchToSingle(updatedContainer);
|
await switchToSingle(updatedContainer);
|
||||||
} else {
|
} else {
|
||||||
// Если контейнер больше не существует, переключаемся на первый доступный
|
// Если контейнер больше не существует, переключаемся на первый доступный
|
||||||
if (state.services.length > 0) {
|
if (state.services.length > 0) {
|
||||||
switchToSingle(state.services[0]);
|
await switchToSingle(state.services[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2837,7 +2987,7 @@ function addMultiSelectHandlers() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Обработчики кликов по опциям
|
// Обработчики кликов по опциям
|
||||||
dropdown.addEventListener('click', (e) => {
|
dropdown.addEventListener('click', async (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
const option = e.target.closest('.multi-select-option');
|
const option = e.target.closest('.multi-select-option');
|
||||||
@ -2865,7 +3015,7 @@ function addMultiSelectHandlers() {
|
|||||||
// Обновляем отображение и загружаем сервисы
|
// Обновляем отображение и загружаем сервисы
|
||||||
const selectedProjects = getSelectedProjects();
|
const selectedProjects = getSelectedProjects();
|
||||||
updateMultiSelect(selectedProjects);
|
updateMultiSelect(selectedProjects);
|
||||||
fetchServices();
|
await fetchServices();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Закрытие dropdown при клике вне его
|
// Закрытие dropdown при клике вне его
|
||||||
@ -3122,16 +3272,16 @@ if (els.lvlErr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Hotkeys: [ ] — tabs, M — multi, R — refresh
|
// Hotkeys: [ ] — tabs, M — multi, R — refresh
|
||||||
window.addEventListener('keydown', (e)=>{
|
window.addEventListener('keydown', async (e)=>{
|
||||||
if (e.key==='[' || (e.ctrlKey && e.key==='ArrowLeft')){
|
if (e.key==='[' || (e.ctrlKey && e.key==='ArrowLeft')){
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const idx = state.services.findIndex(s=> s.id===state.current?.id);
|
const idx = state.services.findIndex(s=> s.id===state.current?.id);
|
||||||
if (idx>0) switchToSingle(state.services[idx-1]);
|
if (idx>0) await switchToSingle(state.services[idx-1]);
|
||||||
}
|
}
|
||||||
if (e.key===']' || (e.ctrlKey && e.key==='ArrowRight')){
|
if (e.key===']' || (e.ctrlKey && e.key==='ArrowRight')){
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const idx = state.services.findIndex(s=> s.id===state.current?.id);
|
const idx = state.services.findIndex(s=> s.id===state.current?.id);
|
||||||
if (idx>=0 && idx<state.services.length-1) switchToSingle(state.services[idx+1]);
|
if (idx>=0 && idx<state.services.length-1) await switchToSingle(state.services[idx+1]);
|
||||||
}
|
}
|
||||||
if (e.key.toLowerCase()==='m'){
|
if (e.key.toLowerCase()==='m'){
|
||||||
const list = state.services.map(s=> `${s.id}:${s.service}`).join(', ');
|
const list = state.services.map(s=> `${s.id}:${s.service}`).join(', ');
|
||||||
@ -3166,6 +3316,9 @@ window.addEventListener('keydown', (e)=>{
|
|||||||
// Инициализируем видимость счетчиков
|
// Инициализируем видимость счетчиков
|
||||||
updateCounterVisibility();
|
updateCounterVisibility();
|
||||||
|
|
||||||
|
// Обновляем состояние чекбоксов после загрузки сервисов
|
||||||
|
updateContainerSelectionUI();
|
||||||
|
|
||||||
// Добавляем обработчики для счетчиков
|
// Добавляем обработчики для счетчиков
|
||||||
addCounterClickHandlers();
|
addCounterClickHandlers();
|
||||||
|
|
||||||
@ -3200,6 +3353,21 @@ window.addEventListener('keydown', (e)=>{
|
|||||||
toggleContainerSelection(containerId);
|
toggleContainerSelection(containerId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Добавляем обработчики кликов на label чекбоксов
|
||||||
|
document.addEventListener('click', (e) => {
|
||||||
|
if (e.target.classList.contains('container-checkbox-label')) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
const label = e.target;
|
||||||
|
const checkbox = label.previousElementSibling;
|
||||||
|
if (checkbox && checkbox.classList.contains('container-checkbox')) {
|
||||||
|
checkbox.checked = !checkbox.checked;
|
||||||
|
const containerId = checkbox.getAttribute('data-container-id');
|
||||||
|
toggleContainerSelection(containerId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
42
test_ws.py
42
test_ws.py
@ -1,42 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Простой тест WebSocket соединения
|
|
||||||
Автор: Сергей Антропов
|
|
||||||
Сайт: https://devops.org.ru
|
|
||||||
"""
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import websockets
|
|
||||||
import base64
|
|
||||||
|
|
||||||
async def test_websocket():
|
|
||||||
"""Тестирует WebSocket соединение"""
|
|
||||||
|
|
||||||
# Параметры подключения
|
|
||||||
uri = "ws://localhost:9001/ws/logs/c90f6c8bfbb6?tail=10&token=YWRtaW46YWRtaW4%3D"
|
|
||||||
|
|
||||||
print(f"🔍 Тестирование WebSocket соединения...")
|
|
||||||
print(f"URI: {uri}")
|
|
||||||
print("-" * 50)
|
|
||||||
|
|
||||||
try:
|
|
||||||
async with websockets.connect(uri) as websocket:
|
|
||||||
print("✅ WebSocket соединение установлено")
|
|
||||||
|
|
||||||
# Ждем сообщения
|
|
||||||
try:
|
|
||||||
async for message in websocket:
|
|
||||||
print(f"📨 Получено сообщение: {message[:200]}...")
|
|
||||||
break # Получаем только первое сообщение
|
|
||||||
except websockets.exceptions.ConnectionClosed:
|
|
||||||
print("❌ WebSocket соединение закрыто")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Ошибка WebSocket: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
asyncio.run(test_websocket())
|
|
Loading…
x
Reference in New Issue
Block a user