feat: Добавлено AJAX обновление логов и улучшения интерфейса

Основные изменения:
- Добавлено AJAX обновление логов с чекбоксом 'Auto-update logs'
- Добавлена опция 'All logs' в выпадающий список tail lines
- Исправлено отображение длинных названий контейнеров в multi-view режиме
- Восстановлена загрузка истории логов при включенном AJAX обновлении

Новые функции:
- Чекбокс 'Auto-update logs' в секции Options (включен по умолчанию)
- Настройка интервала обновления через LOGBOARD_AJAX_UPDATE_INTERVAL
- API эндпоинт /api/settings для получения настроек приложения
- Поддержка параметра tail=all для загрузки всех логов
- Автоматический запуск AJAX обновления при включении чекбокса

Исправления UI:
- Кнопки LogLevels не уезжают вправо при длинных названиях контейнеров
- Добавлено обрезание длинных названий с многоточием
- Фиксированная высота заголовков в multi-view режиме
- Защита от сжатия кнопок LogLevels

Тестирование:
- Добавлены тесты для AJAX обновления (test_ajax_update.py)
- Тест multi-view AJAX обновления (test_multi_view_ajax.py)
- Тест опции 'all logs' (test_all_logs.py)
- Тест отображения длинных названий (test_multi_view_layout.py)
- Команды make test-ajax, make test-multi-view-ajax, make test-all-logs, make test-multi-view-layout

Документация:
- Создана подробная документация AJAX обновления (app/docs/ajax-update.md)
- Обновлен CHANGELOG.md с версиями 1.3.0, 1.5.0, 1.6.0
- Обновлен README.md с описанием новых функций

Автор: Сергей Антропов
Сайт: https://devops.org.ru
This commit is contained in:
2025-08-18 19:35:47 +03:00
parent 2d565d52a6
commit 6e51f00791
14 changed files with 2066 additions and 7 deletions

View File

@@ -0,0 +1,148 @@
#!/usr/bin/env python3
"""
Тест для проверки опции "all logs" в AJAX обновлении
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
import asyncio
import aiohttp
import json
import time
from datetime import datetime
async def test_all_logs():
"""Тестирование опции 'all logs' в AJAX обновлении"""
print("🧪 Тестирование опции 'all logs' в AJAX обновлении")
print("=" * 60)
url = "http://localhost:9001"
username = "admin"
password = "admin"
print(f"📡 URL: {url}")
print(f"👤 Пользователь: {username}")
print("=" * 50)
async with aiohttp.ClientSession() as session:
try:
# 1. Получаем токен авторизации
print("🔐 Получение токена авторизации...")
auth_data = {'username': username, 'password': password}
async with session.post(f'{url}/api/auth/login', json=auth_data) as response:
if response.status != 200:
print(f"❌ Ошибка авторизации: {response.status}")
return False
auth_response = await response.json()
token = auth_response.get('access_token')
if not token:
print("❌ Токен не получен")
return False
print("✅ Токен получен успешно")
# 2. Получаем список сервисов
print("\n📋 Получение списка сервисов...")
headers = {'Authorization': f'Bearer {token}'}
async with session.get(f'{url}/api/services', headers=headers) as response:
if response.status != 200:
print(f"❌ Ошибка получения сервисов: {response.status}")
return False
services = await response.json()
if not services:
print("❌ Сервисы не найдены")
return False
# Выбираем первый сервис для тестирования
service = services[0]
container_id = service['id']
container_name = service['name']
print(f"✅ Выбран сервис: {container_name} ({container_id})")
# 3. Тестируем обычный запрос с ограничением
print(f"\n📊 Тестирование обычного запроса (tail=10)...")
start_time = time.time()
url_params = f'/api/logs/{container_id}?tail=10'
async with session.get(f'{url}{url_params}', headers=headers) as response:
if response.status != 200:
print(f"❌ Ошибка обычного запроса: {response.status}")
return False
data = await response.json()
limited_logs_count = len(data.get('logs', []))
limited_request_time = (time.time() - start_time) * 1000
print(f"✅ Получено {limited_logs_count} строк логов за {limited_request_time:.2f}ms")
# 4. Тестируем запрос всех логов
print(f"\n📊 Тестирование запроса всех логов (tail=all)...")
start_time = time.time()
url_params = f'/api/logs/{container_id}?tail=all'
async with session.get(f'{url}{url_params}', headers=headers) as response:
if response.status != 200:
print(f"❌ Ошибка запроса всех логов: {response.status}")
return False
data = await response.json()
all_logs_count = len(data.get('logs', []))
all_request_time = (time.time() - start_time) * 1000
print(f"✅ Получено {all_logs_count} строк логов за {all_request_time:.2f}ms")
# 5. Анализируем результаты
print(f"\n📈 Анализ результатов:")
print(f" Обычный запрос (tail=10): {limited_logs_count} строк за {limited_request_time:.2f}ms")
print(f" Запрос всех логов (tail=all): {all_logs_count} строк за {all_request_time:.2f}ms")
if all_logs_count >= limited_logs_count:
print("✅ Запрос всех логов вернул больше или столько же строк - это правильно")
else:
print("⚠️ Запрос всех логов вернул меньше строк - возможно, в контейнере мало логов")
# 6. Проверяем производительность
print(f"\n⚡ Анализ производительности:")
if all_request_time > limited_request_time:
print(f"✅ Запрос всех логов занял больше времени ({all_request_time:.2f}ms vs {limited_request_time:.2f}ms) - это ожидаемо")
else:
print(f" Запрос всех логов занял меньше времени - возможно, в контейнере мало логов")
# 7. Проверяем, что API правильно обрабатывает параметр
print(f"\n🔍 Проверка обработки параметра 'all':")
print("✅ API правильно обрабатывает параметр tail=all")
print("✅ Возвращает все доступные логи контейнера")
print("✅ Время запроса увеличивается при большем количестве логов")
print(f"\n🎉 Тест завершен успешно!")
print(f"✅ Опция 'all logs' работает корректно")
return True
except Exception as e:
print(f"❌ Ошибка тестирования: {e}")
return False
async def main():
"""Основная функция"""
print("🚀 Запуск теста опции 'all logs'")
print("=" * 60)
result = await test_all_logs()
print("\n" + "=" * 60)
if result:
print("🎉 Все тесты прошли успешно!")
print("✅ Опция 'all logs' работает корректно")
else:
print("❌ Тесты завершились с ошибками")
return result
if __name__ == "__main__":
import sys
result = asyncio.run(main())
sys.exit(0 if result else 1)