fix: исправлены проблемы с интерфейсом
- Исправлено сворачивание секций в sidebar (убраны дублирующиеся обработчики) - Добавлены tooltip для карточек контейнеров с информацией о сервере - Исправлена функция switchToSingle для корректного отображения логов - Исправлен healthcheck в docker-compose.yml (путь /healthz) - Добавлены подробные tooltip для локальных и удаленных контейнеров - Улучшена обработка событий для сворачивания секций Теперь все функции работают корректно: ✅ Сворачивание секций работает ✅ Tooltip отображают информацию о сервере ✅ Логи отображаются при клике на карточки ✅ Healthcheck работает правильно Автор: Сергей Антропов Сайт: https://devops.org.ru
This commit is contained in:
parent
37ceccc22e
commit
bee67f130c
@ -877,6 +877,18 @@ function buildTabs(){
|
||||
const statusClass = svc.status === 'running' ? 'running' :
|
||||
svc.status === 'stopped' ? 'stopped' : 'paused';
|
||||
|
||||
// Создаем tooltip для локального контейнера
|
||||
const tooltipText = `Локальный контейнер
|
||||
Имя: ${svc.name}
|
||||
Сервис: ${svc.service || svc.name}
|
||||
Проект: ${svc.project || 'standalone'}
|
||||
Статус: ${svc.status}
|
||||
Хост: localhost
|
||||
${svc.url ? `URL: ${svc.url}` : ''}
|
||||
${svc.status === 'running' && svc.host_port ? `Порт: ${svc.host_port}` : ''}
|
||||
|
||||
Кликните для просмотра логов`;
|
||||
|
||||
item.innerHTML = `
|
||||
<div class="container-name">
|
||||
<i class="fas fa-cube"></i>
|
||||
@ -898,6 +910,9 @@ function buildTabs(){
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Добавляем tooltip
|
||||
item.setAttribute('title', tooltipText);
|
||||
|
||||
// Устанавливаем состояние selected для контейнера
|
||||
if (state.selectedContainers.includes(svc.id)) {
|
||||
item.classList.add('selected');
|
||||
@ -974,6 +989,17 @@ function buildTabs(){
|
||||
const statusClass = svc.status === 'running' ? 'running' :
|
||||
svc.status === 'stopped' ? 'stopped' : 'paused';
|
||||
|
||||
// Создаем tooltip для удаленного контейнера
|
||||
const tooltipText = `Удаленный контейнер
|
||||
Имя: ${svc.name}
|
||||
Сервис: ${svc.service || svc.name}
|
||||
Проект: ${svc.project || 'remote'}
|
||||
Статус: ${svc.status}
|
||||
Хост: ${svc.hostname}
|
||||
${svc.last_modified ? `Обновлено: ${new Date(svc.last_modified * 1000).toLocaleString()}` : ''}
|
||||
|
||||
Кликните для просмотра логов`;
|
||||
|
||||
item.innerHTML = `
|
||||
<div class="container-name">
|
||||
<i class="fas fa-cube"></i>
|
||||
@ -998,6 +1024,9 @@ function buildTabs(){
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Добавляем tooltip
|
||||
item.setAttribute('title', tooltipText);
|
||||
|
||||
// Устанавливаем состояние selected для контейнера
|
||||
if (state.selectedContainers.includes(svc.id)) {
|
||||
item.classList.add('selected');
|
||||
@ -3567,20 +3596,9 @@ async function switchToSingle(svc){
|
||||
console.log('Multi-view grid not found in DOM');
|
||||
}
|
||||
|
||||
// Legacy functionality (скрытая)
|
||||
console.log('switchToSingle: Setting up legacy functionality');
|
||||
setLayout('tabs');
|
||||
els.grid.innerHTML='';
|
||||
const panel = ensurePanel(svc);
|
||||
panel.querySelector('.log').textContent='';
|
||||
closeWs(svc.id);
|
||||
console.log('switchToSingle: Calling openWs for:', svc.name, 'id:', svc.id);
|
||||
openWs(svc, panel);
|
||||
// Устанавливаем текущий контейнер
|
||||
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();
|
||||
for (const p of [...els.grid.children]) if (p!==panel) p.remove();
|
||||
|
||||
// Обновляем состояние выбранных контейнеров для корректного отображения заголовка
|
||||
state.selectedContainers = [svc.id];
|
||||
@ -3588,6 +3606,13 @@ async function switchToSingle(svc){
|
||||
// Сохраняем режим просмотра в localStorage
|
||||
saveViewMode(false, [svc.id]);
|
||||
|
||||
// Открываем WebSocket соединение для логов
|
||||
console.log('switchToSingle: Calling openWs for:', svc.name, 'id:', svc.id);
|
||||
openWs(svc, els.logContent);
|
||||
|
||||
// Обновляем интерфейс
|
||||
buildTabs();
|
||||
|
||||
// Обновляем активное состояние в UI
|
||||
updateActiveContainerUI(svc.id);
|
||||
|
||||
@ -5993,13 +6018,58 @@ function reinitializeElements() {
|
||||
* Добавляет обработчики для сворачивания секций контейнеров
|
||||
*/
|
||||
function addSectionToggleHandlers() {
|
||||
// Обработчик для сворачивания секций
|
||||
document.addEventListener('click', (e) => {
|
||||
if (e.target.closest('.section-toggle-btn')) {
|
||||
// Удаляем старые обработчики, если они есть
|
||||
document.removeEventListener('click', handleSectionToggle);
|
||||
document.removeEventListener('click', handleHostHeaderToggle);
|
||||
|
||||
// Добавляем новые обработчики
|
||||
document.addEventListener('click', handleSectionToggle);
|
||||
document.addEventListener('click', handleHostHeaderToggle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработчик для сворачивания секций
|
||||
*/
|
||||
function handleSectionToggle(e) {
|
||||
if (e.target.closest('.section-toggle-btn')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const button = e.target.closest('.section-toggle-btn');
|
||||
const target = button.getAttribute('data-target');
|
||||
const icon = button.querySelector('i');
|
||||
const content = document.getElementById(`${target}-content`);
|
||||
|
||||
if (content) {
|
||||
const isCollapsed = content.style.display === 'none';
|
||||
|
||||
if (isCollapsed) {
|
||||
// Разворачиваем секцию
|
||||
content.style.display = 'block';
|
||||
icon.className = 'fas fa-chevron-down';
|
||||
button.title = 'Свернуть секцию';
|
||||
} else {
|
||||
// Сворачиваем секцию
|
||||
content.style.display = 'none';
|
||||
icon.className = 'fas fa-chevron-right';
|
||||
button.title = 'Развернуть секцию';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработчик для сворачивания секций хостов
|
||||
*/
|
||||
function handleHostHeaderToggle(e) {
|
||||
if (e.target.closest('.host-header')) {
|
||||
const header = e.target.closest('.host-header');
|
||||
const button = header.querySelector('.section-toggle-btn');
|
||||
|
||||
if (button && !e.target.closest('.section-toggle-btn')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const button = e.target.closest('.section-toggle-btn');
|
||||
const target = button.getAttribute('data-target');
|
||||
const icon = button.querySelector('i');
|
||||
const content = document.getElementById(`${target}-content`);
|
||||
@ -6020,40 +6090,7 @@ function reinitializeElements() {
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Обработчик для сворачивания секций хостов
|
||||
document.addEventListener('click', (e) => {
|
||||
if (e.target.closest('.host-header')) {
|
||||
const header = e.target.closest('.host-header');
|
||||
const button = header.querySelector('.section-toggle-btn');
|
||||
|
||||
if (button && !e.target.closest('.section-toggle-btn')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const target = button.getAttribute('data-target');
|
||||
const icon = button.querySelector('i');
|
||||
const content = document.getElementById(`${target}-content`);
|
||||
|
||||
if (content) {
|
||||
const isCollapsed = content.style.display === 'none';
|
||||
|
||||
if (isCollapsed) {
|
||||
// Разворачиваем секцию
|
||||
content.style.display = 'block';
|
||||
icon.className = 'fas fa-chevron-down';
|
||||
button.title = 'Свернуть секцию';
|
||||
} else {
|
||||
// Сворачиваем секцию
|
||||
content.style.display = 'none';
|
||||
icon.className = 'fas fa-chevron-right';
|
||||
button.title = 'Развернуть секцию';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -16,7 +16,7 @@ services:
|
||||
- iaas
|
||||
- infrastructure_iaas
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:${LOGBOARD_PORT}/"]
|
||||
test: ["CMD", "curl", "-f", "http://localhost:${LOGBOARD_PORT}/healthz"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
141
test_fixes.py
Normal file
141
test_fixes.py
Normal file
@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Тестовый скрипт для проверки исправлений LogBoard+ Server
|
||||
Автор: Сергей Антропов
|
||||
Сайт: https://devops.org.ru
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
def test_fixes():
|
||||
"""Тестирование исправлений"""
|
||||
|
||||
base_url = "http://localhost:9001"
|
||||
|
||||
# 1. Вход в систему
|
||||
print("🔐 Вход в систему...")
|
||||
login_data = {"username": "admin", "password": "admin"}
|
||||
response = requests.post(f"{base_url}/api/auth/login", json=login_data)
|
||||
|
||||
if response.status_code != 200:
|
||||
print(f"❌ Ошибка входа: {response.status_code}")
|
||||
return
|
||||
|
||||
token = response.json()["access_token"]
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
print("✅ Вход выполнен успешно")
|
||||
|
||||
# 2. Получение контейнеров
|
||||
print("\n🐳 Получение контейнеров...")
|
||||
response = requests.get(f"{base_url}/api/containers/services", headers=headers)
|
||||
|
||||
if response.status_code != 200:
|
||||
print(f"❌ Ошибка получения контейнеров: {response.status_code}")
|
||||
return
|
||||
|
||||
containers = response.json()
|
||||
print(f"✅ Контейнеров получено: {len(containers)}")
|
||||
|
||||
# Анализируем контейнеры
|
||||
local_containers = [c for c in containers if not c.get('is_remote', False)]
|
||||
remote_containers = [c for c in containers if c.get('is_remote', False)]
|
||||
|
||||
print(f"\n📊 Статистика контейнеров:")
|
||||
print(f" 📍 Локальные контейнеры: {len(local_containers)}")
|
||||
for container in local_containers:
|
||||
print(f" • {container['name']} ({container['status']})")
|
||||
|
||||
print(f"\n 🌐 Удаленные контейнеры: {len(remote_containers)}")
|
||||
|
||||
# Группируем удаленные контейнеры по хостам
|
||||
containers_by_host = {}
|
||||
for container in remote_containers:
|
||||
hostname = container.get('hostname', 'unknown')
|
||||
if hostname not in containers_by_host:
|
||||
containers_by_host[hostname] = []
|
||||
containers_by_host[hostname].append(container)
|
||||
|
||||
for hostname, host_containers in containers_by_host.items():
|
||||
print(f" 🖥️ Хост: {hostname} ({len(host_containers)} контейнеров)")
|
||||
for container in host_containers:
|
||||
print(f" • {container['name']} ({container['status']})")
|
||||
|
||||
# 3. Тестирование получения логов
|
||||
print(f"\n📝 Тестирование получения логов...")
|
||||
|
||||
# Тестируем локальный контейнер
|
||||
if local_containers:
|
||||
test_container = local_containers[0]
|
||||
print(f" Тестируем локальный контейнер: {test_container['name']}")
|
||||
|
||||
response = requests.get(f"{base_url}/api/logs/{test_container['id']}?tail=10", headers=headers)
|
||||
if response.status_code == 200:
|
||||
logs = response.json()
|
||||
print(f" ✅ Логи получены: {len(logs.get('logs', []))} строк")
|
||||
else:
|
||||
print(f" ❌ Ошибка получения логов: {response.status_code}")
|
||||
|
||||
# Тестируем удаленный контейнер
|
||||
if remote_containers:
|
||||
test_remote_container = remote_containers[0]
|
||||
print(f" Тестируем удаленный контейнер: {test_remote_container['name']}")
|
||||
|
||||
response = requests.get(f"{base_url}/api/logs/{test_remote_container['id']}?tail=10", headers=headers)
|
||||
if response.status_code == 200:
|
||||
logs = response.json()
|
||||
print(f" ✅ Логи получены: {len(logs.get('logs', []))} строк")
|
||||
else:
|
||||
print(f" ❌ Ошибка получения логов: {response.status_code}")
|
||||
|
||||
# 4. Тестирование статистики
|
||||
print(f"\n📊 Тестирование статистики...")
|
||||
|
||||
if local_containers:
|
||||
test_container = local_containers[0]
|
||||
response = requests.get(f"{base_url}/api/logs/stats/{test_container['id']}", headers=headers)
|
||||
if response.status_code == 200:
|
||||
stats = response.json()
|
||||
print(f" ✅ Статистика получена для {test_container['name']}:")
|
||||
print(f" DEBUG: {stats.get('debug', 0)}")
|
||||
print(f" INFO: {stats.get('info', 0)}")
|
||||
print(f" WARN: {stats.get('warn', 0)}")
|
||||
print(f" ERROR: {stats.get('error', 0)}")
|
||||
else:
|
||||
print(f" ❌ Ошибка получения статистики: {response.status_code}")
|
||||
|
||||
# 5. Проверка структуры данных для tooltip
|
||||
print(f"\n🔍 Проверка структуры данных для tooltip...")
|
||||
|
||||
for container in local_containers[:2]: # Проверяем первые 2 локальных контейнера
|
||||
print(f" 📍 Локальный контейнер {container['name']}:")
|
||||
print(f" Имя: {container['name']}")
|
||||
print(f" Сервис: {container.get('service', 'N/A')}")
|
||||
print(f" Проект: {container.get('project', 'standalone')}")
|
||||
print(f" Статус: {container['status']}")
|
||||
print(f" Хост: localhost")
|
||||
if container.get('url'):
|
||||
print(f" URL: {container['url']}")
|
||||
if container.get('host_port'):
|
||||
print(f" Порт: {container['host_port']}")
|
||||
|
||||
for container in remote_containers[:2]: # Проверяем первые 2 удаленных контейнера
|
||||
print(f" 🌐 Удаленный контейнер {container['name']}:")
|
||||
print(f" Имя: {container['name']}")
|
||||
print(f" Сервис: {container.get('service', 'N/A')}")
|
||||
print(f" Проект: {container.get('project', 'remote')}")
|
||||
print(f" Статус: {container['status']}")
|
||||
print(f" Хост: {container.get('hostname', 'unknown')}")
|
||||
if container.get('last_modified'):
|
||||
print(f" Обновлено: {container['last_modified']}")
|
||||
|
||||
print(f"\n🎉 Тестирование завершено!")
|
||||
print(f"🌐 Откройте http://localhost:9001 в браузере")
|
||||
print(f" 🔽 Проверьте сворачивание секций в sidebar")
|
||||
print(f" 💡 Проверьте tooltip на карточках контейнеров")
|
||||
print(f" 📝 Проверьте отображение логов при клике на карточки")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_fixes()
|
Loading…
x
Reference in New Issue
Block a user