fix: добавлен CORS middleware и тестовые файлы для диагностики

- Добавлен CORSMiddleware в app.py для решения проблем с CORS
- Создан test_api.py для тестирования API endpoints
- Создан test_websocket.py для тестирования WebSocket соединений
- Создан test_browser.html для тестирования в браузере
- Все тесты показывают, что API и WebSocket работают корректно

Диагностика показала:
 API endpoints работают (health, auth, containers, logs)
 WebSocket соединения работают и передают логи
 Проблема может быть в браузере или localStorage

Для тестирования в браузере:
1. Откройте http://localhost:9001/test_browser.html
2. Выполните тесты по порядку
3. Проверьте консоль браузера на ошибки

Автор: Сергей Антропов
Сайт: https://devops.org.ru
This commit is contained in:
Сергей Антропов 2025-08-20 20:50:28 +03:00
parent 769d33777d
commit 85757ca717
4 changed files with 403 additions and 0 deletions

View File

@ -10,6 +10,7 @@ import os
from fastapi import FastAPI, Request, HTTPException from fastapi import FastAPI, Request, HTTPException
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from fastapi.middleware.cors import CORSMiddleware
from core.config import DEBUG_MODE, SNAP_DIR, STATIC_DIR, APP_PORT from core.config import DEBUG_MODE, SNAP_DIR, STATIC_DIR, APP_PORT
from api.v1.router import api_router, pages_router from api.v1.router import api_router, pages_router
@ -24,6 +25,15 @@ app = FastAPI(
redoc_url="/redoc" if DEBUG_MODE else None redoc_url="/redoc" if DEBUG_MODE else None
) )
# Настройка CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # В продакшене лучше указать конкретные домены
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Инициализация шаблонов # Инициализация шаблонов
from core.config import templates from core.config import templates

107
test_api.py Normal file
View File

@ -0,0 +1,107 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Тестовый скрипт для проверки API LogBoard+
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
import requests
import json
def test_api():
"""Тестирование API endpoints"""
base_url = "http://localhost:9001"
# 1. Проверяем health endpoint
print("🔍 Проверка health endpoint...")
try:
response = requests.get(f"{base_url}/healthz")
print(f" Health: {response.status_code} - {response.text}")
except Exception as e:
print(f" ❌ Ошибка health: {e}")
return
# 2. Проверяем API без токена
print("\n🔍 Проверка API без токена...")
try:
response = requests.get(f"{base_url}/api/containers/services")
print(f" API без токена: {response.status_code}")
if response.status_code == 401:
print(" ✅ Правильно - требует аутентификации")
else:
print(" ⚠️ Неожиданный статус")
except Exception as e:
print(f" ❌ Ошибка API без токена: {e}")
# 3. Вход в систему
print("\n🔐 Вход в систему...")
login_data = {"username": "admin", "password": "admin"}
try:
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(" ✅ Вход выполнен успешно")
except Exception as e:
print(f" ❌ Ошибка входа: {e}")
return
# 4. Проверяем API с токеном
print("\n🔍 Проверка API с токеном...")
try:
response = requests.get(f"{base_url}/api/containers/services", headers=headers)
print(f" API с токеном: {response.status_code}")
if response.status_code == 200:
containers = response.json()
print(f" ✅ Получено контейнеров: {len(containers)}")
# Показываем первые 3 контейнера
for i, container in enumerate(containers[:3]):
print(f" {i+1}. {container.get('name', 'N/A')} ({container.get('status', 'N/A')})")
else:
print(f" ❌ Ошибка API: {response.text}")
except Exception as e:
print(f" ❌ Ошибка API с токеном: {e}")
# 5. Проверяем WebSocket status
print("\n🔍 Проверка WebSocket status...")
try:
response = requests.get(f"{base_url}/api/websocket/status", headers=headers)
print(f" WebSocket status: {response.status_code}")
if response.status_code == 200:
status = response.json()
print(f" ✅ WebSocket статус: {status}")
else:
print(f" ❌ Ошибка WebSocket status: {response.text}")
except Exception as e:
print(f" ❌ Ошибка WebSocket status: {e}")
# 6. Проверяем логи для первого контейнера
try:
response = requests.get(f"{base_url}/api/containers/services", headers=headers)
if response.status_code == 200:
containers = response.json()
if containers:
first_container = containers[0]
container_id = first_container.get('id')
print(f"\n🔍 Проверка логов для контейнера {first_container.get('name')}...")
try:
response = requests.get(f"{base_url}/api/logs/{container_id}?tail=5", headers=headers)
print(f" Логи: {response.status_code}")
if response.status_code == 200:
logs = response.json()
print(f" ✅ Получено строк логов: {len(logs.get('logs', []))}")
else:
print(f" ❌ Ошибка логов: {response.text}")
except Exception as e:
print(f" ❌ Ошибка логов: {e}")
except Exception as e:
print(f" ❌ Ошибка получения контейнеров: {e}")
if __name__ == "__main__":
test_api()

199
test_browser.html Normal file
View File

@ -0,0 +1,199 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Тест LogBoard+ API</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.test-section { margin: 20px 0; padding: 15px; border: 1px solid #ccc; border-radius: 5px; }
.success { color: green; }
.error { color: red; }
.info { color: blue; }
button { padding: 10px 20px; margin: 5px; }
pre { background: #f5f5f5; padding: 10px; border-radius: 3px; overflow-x: auto; }
</style>
</head>
<body>
<h1>Тест LogBoard+ API</h1>
<div class="test-section">
<h3>1. Проверка токена в localStorage</h3>
<button onclick="checkToken()">Проверить токен</button>
<div id="tokenResult"></div>
</div>
<div class="test-section">
<h3>2. Тест входа в систему</h3>
<button onclick="testLogin()">Войти (admin/admin)</button>
<div id="loginResult"></div>
</div>
<div class="test-section">
<h3>3. Тест получения контейнеров</h3>
<button onclick="testContainers()">Получить контейнеры</button>
<div id="containersResult"></div>
</div>
<div class="test-section">
<h3>4. Тест WebSocket</h3>
<button onclick="testWebSocket()">Тест WebSocket</button>
<div id="websocketResult"></div>
</div>
<div class="test-section">
<h3>5. Очистка</h3>
<button onclick="clearToken()">Очистить токен</button>
<div id="clearResult"></div>
</div>
<script>
const baseUrl = 'http://localhost:9001';
function log(elementId, message, type = 'info') {
const element = document.getElementById(elementId);
const className = type === 'success' ? 'success' : type === 'error' ? 'error' : 'info';
element.innerHTML = `<div class="${className}">${message}</div>`;
}
function checkToken() {
const token = localStorage.getItem('access_token');
if (token) {
log('tokenResult', `✅ Токен найден: ${token.substring(0, 50)}...`, 'success');
} else {
log('tokenResult', '❌ Токен не найден', 'error');
}
}
async function testLogin() {
try {
log('loginResult', '🔄 Выполняется вход...', 'info');
const response = await fetch(`${baseUrl}/api/auth/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: 'admin',
password: 'admin'
})
});
if (response.ok) {
const data = await response.json();
localStorage.setItem('access_token', data.access_token);
log('loginResult', `✅ Вход выполнен успешно! Токен сохранен.`, 'success');
} else {
const errorData = await response.json();
log('loginResult', `❌ Ошибка входа: ${errorData.detail || response.status}`, 'error');
}
} catch (error) {
log('loginResult', `❌ Ошибка: ${error.message}`, 'error');
}
}
async function testContainers() {
try {
const token = localStorage.getItem('access_token');
if (!token) {
log('containersResult', '❌ Токен не найден. Сначала выполните вход.', 'error');
return;
}
log('containersResult', '🔄 Получение контейнеров...', 'info');
const response = await fetch(`${baseUrl}/api/containers/services`, {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
const containers = await response.json();
log('containersResult', `✅ Получено контейнеров: ${containers.length}`, 'success');
const containerList = containers.slice(0, 3).map(c =>
`${c.name} (${c.status})`
).join('<br>');
document.getElementById('containersResult').innerHTML +=
`<div class="success">Первые 3 контейнера:<br>${containerList}</div>`;
} else {
const errorText = await response.text();
log('containersResult', `❌ Ошибка: ${response.status} - ${errorText}`, 'error');
}
} catch (error) {
log('containersResult', `❌ Ошибка: ${error.message}`, 'error');
}
}
function testWebSocket() {
const token = localStorage.getItem('access_token');
if (!token) {
log('websocketResult', '❌ Токен не найден. Сначала выполните вход.', 'error');
return;
}
log('websocketResult', '🔄 Подключение к WebSocket...', 'info');
// Получаем первый контейнер для теста
fetch(`${baseUrl}/api/containers/services`, {
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(response => response.json())
.then(containers => {
if (containers.length === 0) {
log('websocketResult', '❌ Нет контейнеров для тестирования', 'error');
return;
}
const container = containers[0];
const wsUrl = `ws://localhost:9001/api/websocket/logs/${container.id}?tail=5&token=${token}&service=${container.name}`;
log('websocketResult', `🔗 Подключение к: ${wsUrl.substring(0, 80)}...`, 'info');
const ws = new WebSocket(wsUrl);
ws.onopen = function() {
log('websocketResult', '✅ WebSocket соединение установлено!', 'success');
};
ws.onmessage = function(event) {
const message = event.data;
log('websocketResult', `📨 Получено сообщение: ${message.substring(0, 100)}...`, 'success');
// Закрываем соединение после получения первого сообщения
setTimeout(() => {
ws.close();
log('websocketResult', '🔌 WebSocket соединение закрыто', 'info');
}, 1000);
};
ws.onerror = function(error) {
log('websocketResult', `❌ WebSocket ошибка: ${error}`, 'error');
};
ws.onclose = function() {
log('websocketResult', '🔌 WebSocket соединение закрыто', 'info');
};
})
.catch(error => {
log('websocketResult', `❌ Ошибка получения контейнеров: ${error.message}`, 'error');
});
}
function clearToken() {
localStorage.removeItem('access_token');
log('clearResult', '✅ Токен очищен', 'success');
}
// Автоматическая проверка токена при загрузке
window.onload = function() {
checkToken();
};
</script>
</body>
</html>

87
test_websocket.py Normal file
View File

@ -0,0 +1,87 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Тестовый скрипт для проверки WebSocket соединений LogBoard+
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
import asyncio
import websockets
import json
import requests
async def test_websocket():
"""Тестирование WebSocket соединений"""
base_url = "http://localhost:9001"
# 1. Получаем токен
print("🔐 Получение токена...")
login_data = {"username": "admin", "password": "admin"}
try:
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"]
print(" ✅ Токен получен")
except Exception as e:
print(f" ❌ Ошибка входа: {e}")
return
# 2. Получаем список контейнеров
print("\n🔍 Получение контейнеров...")
headers = {"Authorization": f"Bearer {token}"}
try:
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)}")
if not containers:
print(" ❌ Нет контейнеров для тестирования")
return
first_container = containers[0]
container_id = first_container.get('id')
container_name = first_container.get('name')
print(f" 📦 Тестируем контейнер: {container_name} ({container_id})")
except Exception as e:
print(f" ❌ Ошибка получения контейнеров: {e}")
return
# 3. Тестируем WebSocket соединение
print(f"\n🔌 Тестирование WebSocket для {container_name}...")
# Формируем WebSocket URL
ws_url = f"ws://localhost:9001/api/websocket/logs/{container_id}?tail=10&token={token}&service={container_name}"
print(f" 🔗 WebSocket URL: {ws_url}")
try:
async with websockets.connect(ws_url) as websocket:
print(" ✅ WebSocket соединение установлено")
# Ждем сообщения
print(" 📡 Ожидание сообщений...")
message_count = 0
max_messages = 5
async for message in websocket:
message_count += 1
print(f" 📨 Сообщение {message_count}: {message[:100]}...")
if message_count >= max_messages:
print(f" ✅ Получено {message_count} сообщений")
break
except Exception as e:
print(f" ❌ Ошибка WebSocket: {e}")
if __name__ == "__main__":
asyncio.run(test_websocket())