Добавлена полная документация проекта LogBoard+

- Создан основной README.md с описанием проекта
- Добавлена подробная документация в папке docs/
- Создан файл LICENSE (MIT)
- Обновлен .gitignore
- Добавлена документация по безопасности с генерацией ключей
- Включены примеры конфигураций и устранение неполадок

Автор: Сергей Антропов
Сайт: https://devops.org.ru
This commit is contained in:
Сергей Антропов 2025-08-19 01:06:23 +03:00
parent 86a2c44333
commit 5c8efe2644
11 changed files with 4932 additions and 90 deletions

90
.gitignore vendored
View File

@ -13,16 +13,9 @@
logs/
log/
# Временные файлы
*.tmp
*.temp
*.swp
*.swo
*~
# Снимки логов
snapshots/
*.snapshot
snapshots/*.log
snapshots/*.txt
# Python
__pycache__/
@ -73,7 +66,82 @@ Thumbs.db
# Docker
.dockerignore
# Backup files
# Temporary files
*.tmp
*.temp
*.bak
*.backup
*.old
# Database
*.db
*.sqlite
*.sqlite3
# Certificates
*.pem
*.key
*.crt
*.csr
# Backup files
backup/
backups/
# Test coverage
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# pipenv
Pipfile.lock
# PEP 582
__pypackages__/
# Celery
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# Local development
local/
dev/

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Сергей Антропов
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

385
README.md Normal file
View File

@ -0,0 +1,385 @@
# LogBoard+
**Веб-панель для просмотра логов микросервисов**
[![Python](https://img.shields.io/badge/Python-3.11-blue.svg)](https://www.python.org/)
[![FastAPI](https://img.shields.io/badge/FastAPI-0.104.1-green.svg)](https://fastapi.tiangolo.com/)
[![Docker](https://img.shields.io/badge/Docker-6.1.3-blue.svg)](https://www.docker.com/)
[![License](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
**Автор:** Сергей Антропов
**Сайт:** https://devops.org.ru
**Версия:** 1.0.0
## Описание
LogBoard+ - это современная веб-панель для мониторинга и просмотра логов Docker контейнеров в реальном времени. Приложение предоставляет удобный веб-интерфейс для работы с логами микросервисов, поддерживает множественные проекты Docker Compose и включает в себя функции безопасности.
### Основные возможности
- **Просмотр логов в реальном времени** - WebSocket соединения для live-логов
- **Поддержка множественных проектов** - Фильтрация по проектам Docker Compose
- **Безопасность** - JWT аутентификация и авторизация
- **Фильтрация контейнеров** - Исключение проблемных контейнеров
- **Снимки логов** - Сохранение логов в файлы для анализа
- **Статистика** - Анализ уровней логирования
- **Адаптивный интерфейс** - Поддержка светлой и темной темы
- **WebSocket API** - Для интеграции с внешними системами
## Скриншоты
### Светлая тема
![Светлая тема](screenshots/light.png)
### Темная тема
![Темная тема](screenshots/dark.png)
## Быстрый старт
### Предварительные требования
- Docker Engine 20.10+
- Docker Compose 2.0+
- 1 GB RAM
- 1 CPU core
### Установка и запуск
1. **Клонирование репозитория**
```bash
git clone <repository-url>
cd logboard
```
2. **Настройка переменных окружения**
```bash
make setup
# Отредактируйте файл .env под свои нужды
```
3. **Запуск приложения**
```bash
make up
```
4. **Доступ к веб-интерфейсу**
```
http://localhost:9001
```
### Учетные данные по умолчанию
- **Пользователь:** `admin`
- **Пароль:** `admin`
**Важно:** Обязательно измените пароль в продакшене!
## Архитектура
### Технологический стек
- **Backend:** Python 3.11, FastAPI 0.104.1
- **Web Server:** Uvicorn с uvloop
- **Docker Integration:** Docker SDK for Python 6.1.3
- **Authentication:** JWT с PyJWT
- **Frontend:** HTML5, CSS3, JavaScript (Vanilla)
- **Templates:** Jinja2
- **Containerization:** Docker, Docker Compose
### Структура проекта
```
logboard/
├── app.py # Основное приложение FastAPI
├── docker-compose.yml # Конфигурация Docker Compose
├── Dockerfile # Образ Docker
├── requirements.txt # Зависимости Python
├── Makefile # Команды управления
├── env.example # Пример переменных окружения
├── excluded_containers.json # Исключенные контейнеры
├── templates/ # HTML шаблоны
│ ├── index.html # Главная страница
│ ├── login.html # Страница входа
│ └── error.html # Страницы ошибок
├── snapshots/ # Снимки логов
└── screenshots/ # Скриншоты интерфейса
```
## API Документация
### REST API
#### Аутентификация
- `POST /api/auth/login` - Вход в систему
- `POST /api/auth/logout` - Выход из системы
- `GET /api/auth/me` - Информация о текущем пользователе
#### Контейнеры и сервисы
- `GET /api/services` - Список контейнеров
- `GET /api/projects` - Список проектов Docker Compose
- `GET /api/logs/{container_id}` - Логи контейнера
- `GET /api/logs/stats/{container_id}` - Статистика логов
#### Управление
- `GET /api/settings` - Настройки приложения
- `GET /api/excluded-containers` - Список исключенных контейнеров
- `POST /api/excluded-containers` - Обновление исключенных контейнеров
- `POST /api/snapshot` - Создание снимка логов
### WebSocket API
- `ws://host:port/ws/logs/{container_id}` - Логи отдельного контейнера
- `ws://host:port/ws/fan/{service_name}` - Логи сервиса (все реплики)
- `ws://host:port/ws/fan_group` - Логи группы сервисов
## Конфигурация
### Переменные окружения
Основные настройки приложения:
| Переменная | Описание | По умолчанию |
|------------|----------|--------------|
| `LOGBOARD_PORT` | Порт веб-интерфейса | `9001` |
| `LOGBOARD_USER` | Имя пользователя | `admin` |
| `LOGBOARD_PASS` | Пароль пользователя | `admin` |
| `LOGBOARD_TAIL` | Количество строк логов | `500` |
| `SECRET_KEY` | Секретный ключ JWT | `your-secret-key-here` |
### Настройка проектов
Для фильтрации контейнеров по проектам Docker Compose:
```bash
# Один проект
COMPOSE_PROJECT_NAME=myproject
# Несколько проектов
LOGBOARD_PROJECTS=project1,project2,project3
```
### Исключение контейнеров
Создайте файл `excluded_containers.json`:
```json
{
"excluded_containers": [
"noisy-container-1",
"noisy-container-2"
],
"description": "Контейнеры с избыточным логированием"
}
```
## Управление
### Команды Makefile
```bash
make help # Справка по командам
make setup # Настройка переменных окружения
make build # Сборка Docker образа
make up # Запуск сервисов
make down # Остановка сервисов
make restart # Перезапуск сервисов
make logs # Просмотр логов
make clean # Очистка проекта
make status # Статус сервисов
make shell # Подключение к контейнеру
```
### Docker Compose команды
```bash
# Запуск
docker compose up -d
# Остановка
docker compose down
# Просмотр логов
docker compose logs -f
# Пересборка
docker compose build --no-cache
```
## Безопасность
### Рекомендации для продакшена
1. **Измените пароли по умолчанию**
```bash
LOGBOARD_PASS=your-secure-password
```
2. **Настройте секретные ключи**
```bash
SECRET_KEY=your-very-secure-secret-key
ENCRYPTION_KEY=your-encryption-key
```
3. **Используйте HTTPS**
- Настройте reverse proxy (nginx, traefik)
- Включите SSL/TLS сертификаты
4. **Ограничьте доступ к Docker socket**
```bash
# Создайте группу docker и добавьте пользователя
sudo usermod -aG docker $USER
```
5. **Настройте файрвол**
```bash
# Ограничьте доступ к порту 9001
sudo ufw allow from 192.168.1.0/24 to any port 9001
```
### Аутентификация
Приложение использует JWT токены для аутентификации:
- Токены хранятся в HTTP-only cookies
- Время жизни токена настраивается через `SESSION_TIMEOUT`
- Поддерживается автоматическое обновление токенов
## Мониторинг и логирование
### Health Check
```bash
curl http://localhost:9001/healthz
# Ответ: ok
```
### Логирование
Приложение логирует:
- Ошибки подключения к Docker
- Проблемы с контейнерами
- Ошибки аутентификации
- WebSocket соединения
### Метрики
Доступные метрики:
- Количество активных WebSocket соединений
- Статистика по уровням логирования
- Количество исключенных контейнеров
- Время ответа API
## Разработка
### Локальная разработка
1. **Клонирование и настройка**
```bash
git clone <repository-url>
cd logboard
make setup
```
2. **Запуск в режиме разработки**
```bash
make up
```
3. **Просмотр логов**
```bash
make logs
```
### Тестирование
```bash
# Проверка конфигурации
make validate
# Проверка переменных окружения
make env-check
# Тестирование API
curl -X GET "http://localhost:9001/api/services" \
-H "Authorization: Bearer YOUR_TOKEN"
```
## Устранение неполадок
### Частые проблемы
1. **Ошибка подключения к Docker**
```bash
# Проверьте права доступа к Docker socket
ls -la /var/run/docker.sock
sudo usermod -aG docker $USER
```
2. **Контейнеры не отображаются**
```bash
# Проверьте фильтры проектов
docker ps --format "table {{.Names}}\t{{.Labels}}"
```
3. **Ошибки аутентификации**
```bash
# Проверьте переменные окружения
make env-check
```
4. **WebSocket соединения не работают**
```bash
# Проверьте настройки прокси
# Убедитесь, что WebSocket поддерживается
```
### Логи и отладка
```bash
# Просмотр логов приложения
make logs
# Подключение к контейнеру
make shell
# Проверка статуса
make status
```
## Лицензия
MIT License - см. файл [LICENSE](LICENSE) для подробностей.
## Поддержка
- **Автор:** Сергей Антропов
- **Сайт:** https://devops.org.ru
- **Issues:** Создавайте issues в репозитории проекта
- **Документация:** [./docs/](./docs/)
## Вклад в проект
1. Fork репозитория
2. Создайте feature branch (`git checkout -b feature/amazing-feature`)
3. Commit изменения (`git commit -m 'Add amazing feature'`)
4. Push в branch (`git push origin feature/amazing-feature`)
5. Откройте Pull Request
## Changelog
### v1.0.0 (2024-01-XX)
- Первый релиз LogBoard+
- Поддержка множественных проектов Docker Compose
- JWT аутентификация
- WebSocket API для live-логов
- Адаптивный веб-интерфейс
- Система исключения контейнеров
- Снимки логов
- Статистика логирования
---
**LogBoard+** - Удобный просмотр логов микросервисов в реальном времени.

657
docs/api.md Normal file
View File

@ -0,0 +1,657 @@
# API Документация LogBoard+
**Автор:** Сергей Антропов
**Сайт:** https://devops.org.ru
## Содержание
1. [Обзор API](#обзор-api)
2. [Аутентификация](#аутентификация)
3. [REST API](#rest-api)
4. [WebSocket API](#websocket-api)
5. [Коды ошибок](#коды-ошибок)
6. [Примеры использования](#примеры-использования)
## Обзор API
LogBoard+ предоставляет REST API и WebSocket API для работы с логами Docker контейнеров.
### Базовый URL
```
http://localhost:9001
```
### Форматы данных
- **Content-Type:** `application/json`
- **Кодировка:** UTF-8
- **Временные метки:** ISO 8601 (UTC)
### Аутентификация
Все API endpoints (кроме `/healthz` и `/api/auth/login`) требуют JWT токен в заголовке:
```
Authorization: Bearer <token>
```
## Аутентификация
### POST /api/auth/login
Вход в систему и получение JWT токена.
**Запрос:**
```json
{
"username": "admin",
"password": "your-password"
}
```
**Ответ:**
```json
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer"
}
```
**Пример curl:**
```bash
curl -X POST "http://localhost:9001/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"your-password"}'
```
### POST /api/auth/logout
Выход из системы (удаление токена из cookies).
**Ответ:**
```json
{
"message": "Успешный выход из системы"
}
```
### GET /api/auth/me
Получение информации о текущем пользователе.
**Заголовки:**
```
Authorization: Bearer <token>
```
**Ответ:**
```json
{
"username": "admin"
}
```
## REST API
### Контейнеры и сервисы
#### GET /api/services
Получение списка всех контейнеров.
**Параметры запроса:**
| Параметр | Тип | Описание | По умолчанию |
|----------|-----|----------|--------------|
| `projects` | string | Фильтр по проектам (через запятую) | Все проекты |
| `include_stopped` | boolean | Включить остановленные контейнеры | false |
**Пример запроса:**
```bash
curl -X GET "http://localhost:9001/api/services?projects=myproject&include_stopped=true" \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Ответ:**
```json
[
{
"id": "abc123def456",
"name": "myproject_web_1",
"status": "running",
"image": "nginx:latest",
"service": "web",
"project": "myproject",
"health": "healthy",
"ports": ["80/tcp"],
"url": "http://localhost:8080",
"host_port": "8080"
},
{
"id": "def456ghi789",
"name": "myproject_db_1",
"status": "running",
"image": "postgres:13",
"service": "db",
"project": "myproject",
"health": "healthy",
"ports": ["5432/tcp"],
"url": null,
"host_port": null
}
]
```
#### GET /api/projects
Получение списка всех проектов Docker Compose.
**Ответ:**
```json
[
"myproject",
"another-project",
"standalone"
]
```
### Логи
#### GET /api/logs/{container_id}
Получение логов контейнера.
**Параметры пути:**
- `container_id` - ID контейнера (первые 12 символов)
**Параметры запроса:**
| Параметр | Тип | Описание | По умолчанию |
|----------|-----|----------|--------------|
| `tail` | string | Количество строк или 'all' | 500 |
| `since` | string | Время начала (ISO 8601 или относительное) | null |
**Пример запроса:**
```bash
curl -X GET "http://localhost:9001/api/logs/abc123def456?tail=100&since=2024-01-15T10:00:00Z" \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Ответ:**
```json
{
"container": {
"id": "abc123def456",
"name": "myproject_web_1",
"status": "running",
"image": "nginx:latest",
"created": "2024-01-15T09:00:00.000000000Z",
"state": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 1234,
"ExitCode": 0,
"Error": "",
"StartedAt": "2024-01-15T09:00:00.000000000Z",
"FinishedAt": "0001-01-01T00:00:00Z"
}
},
"logs": [
{
"timestamp": "2024-01-15T10:30:15.123456789Z",
"message": "2024/01/15 10:30:15 [notice] 1#1: start worker processes",
"raw": "2024-01-15T10:30:15.123456789Z 2024/01/15 10:30:15 [notice] 1#1: start worker processes"
},
{
"timestamp": "2024-01-15T10:30:15.123456789Z",
"message": "2024/01/15 10:30:15 [notice] 1#1: start worker process 1234",
"raw": "2024-01-15T10:30:15.123456789Z 2024/01/15 10:30:15 [notice] 1#1: start worker process 1234"
}
],
"total_lines": 2,
"tail": "100",
"since": "2024-01-15T10:00:00Z",
"timestamp": "2024-01-15T10:30:15.123456789Z"
}
```
#### GET /api/logs/stats/{container_id}
Получение статистики логов контейнера.
**Ответ:**
```json
{
"debug": 15,
"info": 42,
"warn": 8,
"error": 3
}
```
### Управление
#### GET /api/settings
Получение настроек приложения.
**Ответ:**
```json
{
"ajax_update_interval": 2000,
"default_tail": 500,
"skip_unhealthy": true
}
```
#### GET /api/excluded-containers
Получение списка исключенных контейнеров.
**Ответ:**
```json
{
"excluded_containers": [
"noisy-container-1",
"noisy-container-2"
]
}
```
#### POST /api/excluded-containers
Обновление списка исключенных контейнеров.
**Запрос:**
```json
[
"noisy-container-1",
"noisy-container-2",
"another-noisy-container"
]
```
**Ответ:**
```json
{
"status": "success",
"message": "Список исключенных контейнеров обновлен"
}
```
#### POST /api/snapshot
Создание снимка логов.
**Запрос:**
```json
{
"container_id": "abc123def456",
"service": "web",
"content": "Логи контейнера..."
}
```
**Ответ:**
```json
{
"file": "web-20240115-103015.log",
"url": "/snapshots/web-20240115-103015.log"
}
```
### Системные
#### GET /healthz
Health check endpoint.
**Ответ:**
```
ok
```
## WebSocket API
### Подключение
Все WebSocket endpoints требуют JWT токен в параметре `token`.
### ws://host:port/ws/logs/{container_id}
Получение логов отдельного контейнера.
**Параметры:**
- `container_id` - ID контейнера
- `tail` - Количество строк (по умолчанию 500)
- `token` - JWT токен
- `service` - Имя сервиса (опционально)
- `project` - Имя проекта (опционально)
**Пример подключения:**
```javascript
const token = "your-jwt-token";
const containerId = "abc123def456";
const ws = new WebSocket(`ws://localhost:9001/ws/logs/${containerId}?token=${token}&tail=100`);
ws.onmessage = function(event) {
console.log('Получены логи:', event.data);
};
ws.onerror = function(error) {
console.error('WebSocket ошибка:', error);
};
```
### ws://host:port/ws/fan/{service_name}
Получение логов сервиса (все реплики).
**Параметры:**
- `service_name` - Имя сервиса Docker Compose
- `tail` - Количество строк (по умолчанию 500)
- `token` - JWT токен
- `project` - Имя проекта (опционально)
**Пример подключения:**
```javascript
const token = "your-jwt-token";
const serviceName = "web";
const ws = new WebSocket(`ws://localhost:9001/ws/fan/${serviceName}?token=${token}&tail=100`);
ws.onmessage = function(event) {
console.log('Логи сервиса:', event.data);
};
```
### ws://host:port/ws/fan_group
Получение логов группы сервисов.
**Параметры:**
- `services` - Имена сервисов через запятую
- `tail` - Количество строк (по умолчанию 500)
- `token` - JWT токен
- `project` - Имя проекта (опционально)
**Пример подключения:**
```javascript
const token = "your-jwt-token";
const services = "web,db,redis";
const ws = new WebSocket(`ws://localhost:9001/ws/fan_group?services=${services}&token=${token}&tail=100`);
ws.onmessage = function(event) {
console.log('Логи группы сервисов:', event.data);
};
```
## Коды ошибок
### HTTP коды состояния
| Код | Описание |
|-----|----------|
| 200 | Успешный запрос |
| 400 | Неверный запрос |
| 401 | Не авторизован |
| 403 | Доступ запрещен |
| 404 | Ресурс не найден |
| 500 | Внутренняя ошибка сервера |
### Формат ошибок
```json
{
"error": "error_type",
"message": "Описание ошибки",
"details": "Дополнительная информация"
}
```
### Примеры ошибок
#### 401 Unauthorized
```json
{
"error": "unauthorized",
"message": "Требуется авторизация",
"details": "Для доступа к этому API необходимо войти в систему."
}
```
#### 404 Not Found
```json
{
"error": "http_404",
"message": "Контейнер не найден",
"details": "URL: /api/logs/invalid-id"
}
```
#### 500 Internal Server Error
```json
{
"error": "http_500",
"message": "Ошибка подключения к Docker",
"details": "URL: /api/services"
}
```
## Примеры использования
### Python
```python
import requests
import json
# Базовый URL
BASE_URL = "http://localhost:9001"
# Аутентификация
def login(username, password):
response = requests.post(f"{BASE_URL}/api/auth/login", json={
"username": username,
"password": password
})
return response.json()["access_token"]
# Получение списка контейнеров
def get_containers(token):
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(f"{BASE_URL}/api/services", headers=headers)
return response.json()
# Получение логов контейнера
def get_logs(token, container_id, tail=100):
headers = {"Authorization": f"Bearer {token}"}
params = {"tail": tail}
response = requests.get(f"{BASE_URL}/api/logs/{container_id}",
headers=headers, params=params)
return response.json()
# Использование
token = login("admin", "your-password")
containers = get_containers(token)
print(f"Найдено контейнеров: {len(containers)}")
for container in containers:
logs = get_logs(token, container["id"], tail=10)
print(f"Контейнер {container['name']}: {len(logs['logs'])} строк логов")
```
### JavaScript
```javascript
// Класс для работы с LogBoard+ API
class LogBoardAPI {
constructor(baseUrl, token) {
this.baseUrl = baseUrl;
this.token = token;
this.headers = {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
};
}
// Получение списка контейнеров
async getContainers(projects = null, includeStopped = false) {
const params = new URLSearchParams();
if (projects) params.append('projects', projects);
if (includeStopped) params.append('include_stopped', 'true');
const response = await fetch(`${this.baseUrl}/api/services?${params}`, {
headers: this.headers
});
return await response.json();
}
// Получение логов контейнера
async getLogs(containerId, tail = 100, since = null) {
const params = new URLSearchParams();
params.append('tail', tail);
if (since) params.append('since', since);
const response = await fetch(`${this.baseUrl}/api/logs/${containerId}?${params}`, {
headers: this.headers
});
return await response.json();
}
// Получение статистики логов
async getLogStats(containerId) {
const response = await fetch(`${this.baseUrl}/api/logs/stats/${containerId}`, {
headers: this.headers
});
return await response.json();
}
// WebSocket для live-логов
createLogsWebSocket(containerId, tail = 100) {
const ws = new WebSocket(
`ws://${this.baseUrl.replace('http://', '')}/ws/logs/${containerId}?token=${this.token}&tail=${tail}`
);
return ws;
}
}
// Использование
async function main() {
// Получение токена
const loginResponse = await fetch('http://localhost:9001/api/auth/login', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
username: 'admin',
password: 'your-password'
})
});
const {access_token} = await loginResponse.json();
// Создание API клиента
const api = new LogBoardAPI('http://localhost:9001', access_token);
// Получение контейнеров
const containers = await api.getContainers();
console.log('Контейнеры:', containers);
// Получение логов первого контейнера
if (containers.length > 0) {
const logs = await api.getLogs(containers[0].id, 50);
console.log('Логи:', logs);
// WebSocket для live-логов
const ws = api.createLogsWebSocket(containers[0].id, 100);
ws.onmessage = (event) => {
console.log('Новые логи:', event.data);
};
}
}
main().catch(console.error);
```
### cURL
```bash
#!/bin/bash
# Переменные
BASE_URL="http://localhost:9001"
USERNAME="admin"
PASSWORD="your-password"
# Получение токена
TOKEN=$(curl -s -X POST "$BASE_URL/api/auth/login" \
-H "Content-Type: application/json" \
-d "{\"username\":\"$USERNAME\",\"password\":\"$PASSWORD\"}" \
| jq -r '.access_token')
echo "Токен получен: ${TOKEN:0:20}..."
# Получение списка контейнеров
echo "Получение списка контейнеров..."
curl -s -X GET "$BASE_URL/api/services" \
-H "Authorization: Bearer $TOKEN" \
| jq '.[] | {name: .name, status: .status, project: .project}'
# Получение логов первого контейнера
CONTAINER_ID=$(curl -s -X GET "$BASE_URL/api/services" \
-H "Authorization: Bearer $TOKEN" \
| jq -r '.[0].id')
echo "Получение логов контейнера $CONTAINER_ID..."
curl -s -X GET "$BASE_URL/api/logs/$CONTAINER_ID?tail=10" \
-H "Authorization: Bearer $TOKEN" \
| jq '.logs[] | .message'
```
## Ограничения и рекомендации
### Ограничения
- Максимальное количество строк логов: 10000
- Таймаут WebSocket соединения: 60 секунд
- Максимальный размер снимка логов: 10 MB
### Рекомендации
1. **Используйте фильтрацию по времени** для больших объемов логов
2. **Настройте исключение контейнеров** с избыточным логированием
3. **Используйте WebSocket** для real-time мониторинга
4. **Регулярно очищайте снимки логов** для экономии места
### Производительность
- REST API: до 1000 запросов в минуту
- WebSocket: до 100 одновременных соединений
- Размер ответа: до 1 MB на запрос

503
docs/configuration.md Normal file
View File

@ -0,0 +1,503 @@
# Конфигурация LogBoard+
**Автор:** Сергей Антропов
**Сайт:** https://devops.org.ru
## Содержание
1. [Обзор конфигурации](#обзор-конфигурации)
2. [Переменные окружения](#переменные-окружения)
3. [Настройка Docker](#настройка-docker)
4. [Настройка безопасности](#настройка-безопасности)
5. [Настройка производительности](#настройка-производительности)
6. [Настройка логирования](#настройка-логирования)
7. [Примеры конфигураций](#примеры-конфигураций)
## Обзор конфигурации
LogBoard+ использует переменные окружения для настройки всех аспектов работы приложения. Конфигурация разделена на несколько категорий для удобства управления.
### Файлы конфигурации
- `.env` - Основной файл конфигурации (создается из `env.example`)
- `docker-compose.yml` - Конфигурация Docker Compose
- `excluded_containers.json` - Список исключенных контейнеров
### Приоритет настроек
1. Переменные окружения системы
2. Файл `.env`
3. Значения по умолчанию в коде
## Переменные окружения
### Основные настройки приложения
| Переменная | Описание | По умолчанию | Обязательная |
|------------|----------|--------------|--------------|
| `LOGBOARD_PORT` | Порт веб-интерфейса | `9001` | Нет |
| `LOGBOARD_TAIL` | Количество строк логов по умолчанию | `500` | Нет |
| `LOGBOARD_USER` | Имя пользователя для входа | `admin` | Нет |
| `LOGBOARD_PASS` | Пароль пользователя | `admin` | Да |
| `LOGBOARD_SNAPSHOT_DIR` | Директория для снимков логов | `/app/snapshots` | Нет |
| `LOGBOARD_INDEX_HTML` | Путь к HTML шаблону | `./templates/index.html` | Нет |
| `TZ_TS` | Временная зона | `Europe/Moscow` | Нет |
### Настройки Docker
| Переменная | Описание | По умолчанию | Обязательная |
|------------|----------|--------------|--------------|
| `COMPOSE_PROJECT_NAME` | Фильтр по проекту Docker Compose | - | Нет |
| `LOGBOARD_PROJECTS` | Множественные проекты (через запятую) | - | Нет |
| `DOCKER_HOST` | Путь к Docker socket | `unix:///var/run/docker.sock` | Нет |
| `DOCKER_TLS_VERIFY` | Проверка TLS для Docker | - | Нет |
| `DOCKER_CERT_PATH` | Путь к сертификатам Docker | - | Нет |
| `DOCKER_NETWORKS` | Внешние сети Docker | `iaas,infrastructure_iaas` | Нет |
### Настройки безопасности
| Переменная | Описание | По умолчанию | Обязательная |
|------------|----------|--------------|--------------|
| `SECRET_KEY` | Секретный ключ для JWT | `your-secret-key-here` | Да |
| `ENCRYPTION_KEY` | Ключ шифрования | `your-encryption-key-here` | Да |
| `AUTH_ENABLED` | Включить аутентификацию | `true` | Нет |
| `AUTH_METHOD` | Метод аутентификации | `jwt` | Нет |
| `SESSION_TIMEOUT` | Время жизни сессии (секунды) | `3600` | Нет |
### Настройки производительности
| Переменная | Описание | По умолчанию | Обязательная |
|------------|----------|--------------|--------------|
| `MAX_CONNECTIONS` | Максимальное количество соединений | `100` | Нет |
| `CONNECTION_TIMEOUT` | Таймаут подключения (секунды) | `30` | Нет |
| `READ_TIMEOUT` | Таймаут чтения (секунды) | `60` | Нет |
| `LOGBOARD_AJAX_UPDATE_INTERVAL` | Интервал AJAX обновления (мс) | `2000` | Нет |
### Настройки фильтрации контейнеров
| Переменная | Описание | По умолчанию | Обязательная |
|------------|----------|--------------|--------------|
| `LOGBOARD_SKIP_UNHEALTHY` | Пропускать нездоровые контейнеры | `true` | Нет |
| `LOGBOARD_CONTAINER_LIST_TIMEOUT` | Таймаут получения списка контейнеров | `10` | Нет |
| `LOGBOARD_CONTAINER_INFO_TIMEOUT` | Таймаут получения информации о контейнере | `3` | Нет |
| `LOGBOARD_HEALTH_CHECK_TIMEOUT` | Таймаут health check | `2` | Нет |
### Настройки логирования
| Переменная | Описание | По умолчанию | Обязательная |
|------------|----------|--------------|--------------|
| `LOG_LEVEL` | Уровень логирования | `INFO` | Нет |
| `LOG_FORMAT` | Формат логов | `json` | Нет |
### Настройки веб-интерфейса
| Переменная | Описание | По умолчанию | Обязательная |
|------------|----------|--------------|--------------|
| `WEB_TITLE` | Заголовок веб-интерфейса | `LogBoard+` | Нет |
| `WEB_DESCRIPTION` | Описание веб-интерфейса | `Веб-панель для просмотра логов микросервисов` | Нет |
| `WEB_VERSION` | Версия веб-интерфейса | `1.0.0` | Нет |
### Настройки уведомлений
| Переменная | Описание | По умолчанию | Обязательная |
|------------|----------|--------------|--------------|
| `NOTIFICATIONS_ENABLED` | Включить уведомления | `false` | Нет |
| `SMTP_HOST` | SMTP сервер | - | Нет |
| `SMTP_PORT` | Порт SMTP | `587` | Нет |
| `SMTP_USER` | Пользователь SMTP | - | Нет |
| `SMTP_PASS` | Пароль SMTP | - | Нет |
| `SMTP_FROM` | Email отправителя | - | Нет |
## Настройка Docker
### Подключение к Docker daemon
#### Локальный Docker
```bash
# Стандартная конфигурация для локального Docker
DOCKER_HOST=unix:///var/run/docker.sock
```
#### Удаленный Docker
```bash
# Подключение к удаленному Docker daemon
DOCKER_HOST=tcp://192.168.1.100:2375
# С TLS сертификатами
DOCKER_HOST=tcp://192.168.1.100:2376
DOCKER_TLS_VERIFY=1
DOCKER_CERT_PATH=/path/to/certs
```
### Фильтрация проектов
#### Один проект
```bash
# Показать только контейнеры из одного проекта
COMPOSE_PROJECT_NAME=myproject
```
#### Несколько проектов
```bash
# Показать контейнеры из нескольких проектов
LOGBOARD_PROJECTS=project1,project2,project3
```
#### Все проекты
```bash
# Показать все контейнеры (по умолчанию)
# Оставьте переменные пустыми или не указывайте их
```
### Настройка сетей
```bash
# Подключение к внешним Docker сетям
DOCKER_NETWORKS=iaas,infrastructure_iaas,frontend,backend
```
## Настройка безопасности
### Генерация секретных ключей
```bash
# Генерация секретного ключа для JWT
openssl rand -hex 32
# 8a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6
# Генерация ключа шифрования
openssl rand -hex 32
# 1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z7
```
### Настройка аутентификации
```bash
# Включение аутентификации
AUTH_ENABLED=true
AUTH_METHOD=jwt
# Настройка времени жизни сессии (1 час)
SESSION_TIMEOUT=3600
# Настройка пользователей
LOGBOARD_USER=admin
LOGBOARD_PASS=your-secure-password
```
### Настройка HTTPS
Для продакшена рекомендуется использовать HTTPS через reverse proxy:
```nginx
# Nginx конфигурация
server {
listen 443 ssl http2;
server_name your-domain.com;
ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/private.key;
location / {
proxy_pass http://localhost:9001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket поддержка
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
```
## Настройка производительности
### Ограничения соединений
```bash
# Максимальное количество одновременных подключений
MAX_CONNECTIONS=100
# Таймауты
CONNECTION_TIMEOUT=30
READ_TIMEOUT=60
```
### Настройка AJAX обновления
```bash
# Интервал обновления логов (в миллисекундах)
LOGBOARD_AJAX_UPDATE_INTERVAL=2000
# Количество строк логов по умолчанию
LOGBOARD_TAIL=500
```
### Оптимизация Docker
```bash
# Таймауты для операций с Docker
LOGBOARD_CONTAINER_LIST_TIMEOUT=10
LOGBOARD_CONTAINER_INFO_TIMEOUT=3
LOGBOARD_HEALTH_CHECK_TIMEOUT=2
# Пропуск нездоровых контейнеров
LOGBOARD_SKIP_UNHEALTHY=true
```
## Настройка логирования
### Уровни логирования
```bash
# Доступные уровни: DEBUG, INFO, WARNING, ERROR
LOG_LEVEL=INFO
# Форматы: json, text
LOG_FORMAT=json
```
### Настройка логов приложения
```bash
# Создание директории для логов
sudo mkdir -p /var/log/logboard
sudo chown $USER:$USER /var/log/logboard
# Обновление docker-compose.yml
volumes:
- /var/log/logboard:/app/logs
```
## Исключение контейнеров
### Файл excluded_containers.json
```json
{
"excluded_containers": [
"noisy-container-1",
"noisy-container-2",
"debug-container",
"test-container"
],
"description": "Контейнеры с избыточным логированием, исключенные из отображения"
}
```
### Управление через API
```bash
# Получение списка исключенных контейнеров
curl -X GET "http://localhost:9001/api/excluded-containers" \
-H "Authorization: Bearer YOUR_TOKEN"
# Обновление списка
curl -X POST "http://localhost:9001/api/excluded-containers" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '["container1", "container2"]'
```
## Примеры конфигураций
### Разработка
```bash
# .env для разработки
LOGBOARD_PORT=9001
LOGBOARD_USER=admin
LOGBOARD_PASS=dev-password
SECRET_KEY=dev-secret-key-change-in-production
ENCRYPTION_KEY=dev-encryption-key-change-in-production
# Docker настройки
DOCKER_HOST=unix:///var/run/docker.sock
LOGBOARD_PROJECTS=dev-project
# Производительность
LOGBOARD_TAIL=100
LOGBOARD_AJAX_UPDATE_INTERVAL=1000
LOG_LEVEL=DEBUG
# Безопасность
AUTH_ENABLED=true
SESSION_TIMEOUT=7200
```
### Тестирование
```bash
# .env для тестирования
LOGBOARD_PORT=9002
LOGBOARD_USER=test
LOGBOARD_PASS=test-password
SECRET_KEY=test-secret-key
ENCRYPTION_KEY=test-encryption-key
# Docker настройки
DOCKER_HOST=unix:///var/run/docker.sock
LOGBOARD_PROJECTS=test-project
# Производительность
LOGBOARD_TAIL=50
LOGBOARD_AJAX_UPDATE_INTERVAL=500
LOG_LEVEL=INFO
# Безопасность
AUTH_ENABLED=true
SESSION_TIMEOUT=1800
```
### Продакшен
```bash
# .env для продакшена
LOGBOARD_PORT=9001
LOGBOARD_USER=admin
LOGBOARD_PASS=very-secure-password-here
SECRET_KEY=8a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6
ENCRYPTION_KEY=1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z7
# Docker настройки
DOCKER_HOST=unix:///var/run/docker.sock
LOGBOARD_PROJECTS=prod-project,monitoring
# Производительность
LOGBOARD_TAIL=1000
LOGBOARD_AJAX_UPDATE_INTERVAL=5000
MAX_CONNECTIONS=200
CONNECTION_TIMEOUT=60
READ_TIMEOUT=120
# Безопасность
AUTH_ENABLED=true
SESSION_TIMEOUT=3600
# Логирование
LOG_LEVEL=WARNING
LOG_FORMAT=json
# Уведомления
NOTIFICATIONS_ENABLED=true
SMTP_HOST=smtp.company.com
SMTP_PORT=587
SMTP_USER=logboard@company.com
SMTP_PASS=email-password
SMTP_FROM=logboard@company.com
```
### Масштабированная установка
```bash
# .env для масштабированной установки
LOGBOARD_PORT=9001
LOGBOARD_USER=admin
LOGBOARD_PASS=very-secure-password
SECRET_KEY=8a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6
ENCRYPTION_KEY=1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z7
# Docker настройки
DOCKER_HOST=tcp://docker-swarm-manager:2376
DOCKER_TLS_VERIFY=1
DOCKER_CERT_PATH=/etc/docker/certs
LOGBOARD_PROJECTS=app1,app2,app3,monitoring
# Производительность
LOGBOARD_TAIL=2000
LOGBOARD_AJAX_UPDATE_INTERVAL=3000
MAX_CONNECTIONS=500
CONNECTION_TIMEOUT=120
READ_TIMEOUT=300
# Безопасность
AUTH_ENABLED=true
SESSION_TIMEOUT=7200
# Логирование
LOG_LEVEL=INFO
LOG_FORMAT=json
# Уведомления
NOTIFICATIONS_ENABLED=true
SMTP_HOST=smtp.company.com
SMTP_PORT=587
SMTP_USER=logboard@company.com
SMTP_PASS=email-password
SMTP_FROM=logboard@company.com
```
## Проверка конфигурации
### Валидация переменных окружения
```bash
# Проверка конфигурации
make env-check
# Проверка Docker Compose
make validate
```
### Тестирование подключения
```bash
# Проверка подключения к Docker
docker ps
# Проверка доступности API
curl http://localhost:9001/healthz
# Проверка аутентификации
curl -X POST "http://localhost:9001/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"your-password"}'
```
### Мониторинг конфигурации
```bash
# Просмотр текущих настроек
curl -X GET "http://localhost:9001/api/settings" \
-H "Authorization: Bearer YOUR_TOKEN"
# Проверка списка контейнеров
curl -X GET "http://localhost:9001/api/services" \
-H "Authorization: Bearer YOUR_TOKEN"
```
## Рекомендации по безопасности
### Обязательные настройки для продакшена
1. **Измените пароли по умолчанию**
2. **Используйте сильные секретные ключи**
3. **Настройте HTTPS через reverse proxy**
4. **Ограничьте доступ к Docker socket**
5. **Настройте файрвол**
6. **Регулярно обновляйте зависимости**
7. **Мониторьте логи доступа**
### Мониторинг безопасности
```bash
# Проверка прав доступа к Docker socket
ls -la /var/run/docker.sock
# Проверка пользователей в группе docker
getent group docker
# Проверка открытых портов
netstat -tlnp | grep 9001
# Проверка логов аутентификации
grep "authentication" /var/log/logboard/app.log
```

313
docs/index.md Normal file
View File

@ -0,0 +1,313 @@
# Документация LogBoard+
**Автор:** Сергей Антропов
**Сайт:** https://devops.org.ru
## Обзор
LogBoard+ - это современная веб-панель для мониторинга и просмотра логов Docker контейнеров в реальном времени. Приложение предоставляет удобный веб-интерфейс для работы с логами микросервисов, поддерживает множественные проекты Docker Compose и включает в себя функции безопасности.
## Быстрый старт
### Установка
```bash
# Клонирование репозитория
git clone <repository-url>
cd logboard
# Настройка переменных окружения
make setup
# Запуск приложения
make up
```
### Доступ
- **URL:** http://localhost:9001
- **Пользователь:** `admin`
- **Пароль:** `admin` (обязательно измените в продакшене!)
## Документация
### Основные разделы
1. **[Установка и настройка](./installation.md)**
- Предварительные требования
- Пошаговая установка
- Настройка для продакшена
- Устранение проблем установки
2. **[API Документация](./api.md)**
- REST API endpoints
- WebSocket API
- Примеры использования
- Коды ошибок
3. **[WebSocket API](./websocket.md)**
- Подключение к WebSocket
- Real-time логи
- Обработка ошибок
- Лучшие практики
4. **[Конфигурация](./configuration.md)**
- Переменные окружения
- Настройка Docker
- Настройка безопасности
- Примеры конфигураций
5. **[Безопасность](./security.md)**
- Генерация ключей
- Аутентификация
- Настройка HTTPS
- Мониторинг безопасности
6. **[Устранение неполадок](./troubleshooting.md)**
- Общие проблемы
- Проблемы с Docker
- Проблемы с аутентификацией
- Диагностика и логи
### Дополнительные ресурсы
- **[README.md](../README.md)** - Основная документация проекта
- **[env.example](../env.example)** - Пример конфигурации
- **[Makefile](../Makefile)** - Команды управления
## Архитектура
### Технологический стек
- **Backend:** Python 3.11, FastAPI 0.104.1
- **Web Server:** Uvicorn с uvloop
- **Docker Integration:** Docker SDK for Python 6.1.3
- **Authentication:** JWT с PyJWT
- **Frontend:** HTML5, CSS3, JavaScript (Vanilla)
- **Templates:** Jinja2
- **Containerization:** Docker, Docker Compose
### Основные возможности
- **Просмотр логов в реальном времени** - WebSocket соединения для live-логов
- **Поддержка множественных проектов** - Фильтрация по проектам Docker Compose
- **Безопасность** - JWT аутентификация и авторизация
- **Фильтрация контейнеров** - Исключение проблемных контейнеров
- **Снимки логов** - Сохранение логов в файлы для анализа
- **Статистика** - Анализ уровней логирования
- **Адаптивный интерфейс** - Поддержка светлой и темной темы
## API Endpoints
### REST API
#### Аутентификация
- `POST /api/auth/login` - Вход в систему
- `POST /api/auth/logout` - Выход из системы
- `GET /api/auth/me` - Информация о текущем пользователе
#### Контейнеры и сервисы
- `GET /api/services` - Список контейнеров
- `GET /api/projects` - Список проектов Docker Compose
- `GET /api/logs/{container_id}` - Логи контейнера
- `GET /api/logs/stats/{container_id}` - Статистика логов
#### Управление
- `GET /api/settings` - Настройки приложения
- `GET /api/excluded-containers` - Список исключенных контейнеров
- `POST /api/excluded-containers` - Обновление исключенных контейнеров
- `POST /api/snapshot` - Создание снимка логов
### WebSocket API
- `ws://host:port/ws/logs/{container_id}` - Логи отдельного контейнера
- `ws://host:port/ws/fan/{service_name}` - Логи сервиса (все реплики)
- `ws://host:port/ws/fan_group` - Логи группы сервисов
## Конфигурация
### Основные переменные окружения
| Переменная | Описание | По умолчанию |
|------------|----------|--------------|
| `LOGBOARD_PORT` | Порт веб-интерфейса | `9001` |
| `LOGBOARD_USER` | Имя пользователя | `admin` |
| `LOGBOARD_PASS` | Пароль пользователя | `admin` |
| `LOGBOARD_TAIL` | Количество строк логов | `500` |
| `SECRET_KEY` | Секретный ключ JWT | `your-secret-key-here` |
### Настройка проектов
```bash
# Один проект
COMPOSE_PROJECT_NAME=myproject
# Несколько проектов
LOGBOARD_PROJECTS=project1,project2,project3
```
## Управление
### Команды Makefile
```bash
make help # Справка по командам
make setup # Настройка переменных окружения
make build # Сборка Docker образа
make up # Запуск сервисов
make down # Остановка сервисов
make restart # Перезапуск сервисов
make logs # Просмотр логов
make clean # Очистка проекта
make status # Статус сервисов
make shell # Подключение к контейнеру
```
### Docker Compose команды
```bash
# Запуск
docker compose up -d
# Остановка
docker compose down
# Просмотр логов
docker compose logs -f
# Пересборка
docker compose build --no-cache
```
## Безопасность
### Рекомендации для продакшена
1. **Измените пароли по умолчанию**
2. **Настройте секретные ключи**
3. **Используйте HTTPS**
4. **Ограничьте доступ к Docker socket**
5. **Настройте файрвол**
### Аутентификация
Приложение использует JWT токены для аутентификации:
- Токены хранятся в HTTP-only cookies
- Время жизни токена настраивается через `SESSION_TIMEOUT`
- Поддерживается автоматическое обновление токенов
## Мониторинг и логирование
### Health Check
```bash
curl http://localhost:9001/healthz
# Ответ: ok
```
### Логирование
Приложение логирует:
- Ошибки подключения к Docker
- Проблемы с контейнерами
- Ошибки аутентификации
- WebSocket соединения
## Примеры использования
### Python
```python
import requests
# Аутентификация
response = requests.post('http://localhost:9001/api/auth/login', json={
'username': 'admin',
'password': 'your-password'
})
token = response.json()['access_token']
# Получение списка контейнеров
headers = {'Authorization': f'Bearer {token}'}
containers = requests.get('http://localhost:9001/api/services', headers=headers).json()
```
### JavaScript
```javascript
// Получение токена
const response = await fetch('http://localhost:9001/api/auth/login', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
username: 'admin',
password: 'your-password'
})
});
const {access_token} = await response.json();
// WebSocket подключение
const ws = new WebSocket(`ws://localhost:9001/ws/logs/container-id?token=${access_token}`);
```
### cURL
```bash
# Получение токена
TOKEN=$(curl -s -X POST "http://localhost:9001/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"your-password"}' \
| jq -r '.access_token')
# Получение списка контейнеров
curl -X GET "http://localhost:9001/api/services" \
-H "Authorization: Bearer $TOKEN"
```
## Устранение неполадок
### Частые проблемы
1. **Ошибка подключения к Docker**
```bash
sudo usermod -aG docker $USER
newgrp docker
```
2. **Контейнеры не отображаются**
```bash
docker ps --format "table {{.Names}}\t{{.Labels}}"
```
3. **Ошибки аутентификации**
```bash
make env-check
```
### Диагностика
```bash
# Проверка статуса
make status
# Просмотр логов
make logs
# Проверка конфигурации
make validate
```
## Поддержка
- **Автор:** Сергей Антропов
- **Сайт:** https://devops.org.ru
- **Issues:** Создавайте issues в репозитории проекта
- **Документация:** [./docs/](./docs/)
## Лицензия
MIT License - см. файл [LICENSE](../LICENSE) для подробностей.
---
**LogBoard+** - Удобный просмотр логов микросервисов в реальном времени.

430
docs/installation.md Normal file
View File

@ -0,0 +1,430 @@
# Установка и настройка LogBoard+
**Автор:** Сергей Антропов
**Сайт:** https://devops.org.ru
## Содержание
1. [Предварительные требования](#предварительные-требования)
2. [Установка](#установка)
3. [Настройка](#настройка)
4. [Первый запуск](#первый-запуск)
5. [Проверка установки](#проверка-установки)
6. [Настройка для продакшена](#настройка-для-продакшена)
## Предварительные требования
### Системные требования
- **Операционная система:** Linux (Ubuntu 20.04+, CentOS 8+, RHEL 8+)
- **Docker Engine:** 20.10.0 или выше
- **Docker Compose:** 2.0.0 или выше
- **RAM:** Минимум 1 GB, рекомендуется 2 GB
- **CPU:** Минимум 1 ядро, рекомендуется 2 ядра
- **Дисковое пространство:** Минимум 2 GB свободного места
### Проверка системы
```bash
# Проверка версии Docker
docker --version
# Docker version 20.10.21, build baeda1f
# Проверка версии Docker Compose
docker compose version
# Docker Compose version v2.12.2
# Проверка доступной памяти
free -h
# total used free shared buff/cache available
# Mem: 7.7Gi 2.1Gi 4.2Gi 0.0Ki 1.4Gi 5.3Gi
# Проверка свободного места
df -h /
# Filesystem Size Used Avail Use% Mounted on
# /dev/sda1 100G 20G 80G 20% /
```
## Установка
### Метод 1: Клонирование из Git
```bash
# Клонирование репозитория
git clone https://github.com/your-username/logboard.git
cd logboard
# Проверка структуры проекта
ls -la
```
### Метод 2: Скачивание архива
```bash
# Скачивание и распаковка
wget https://github.com/your-username/logboard/archive/refs/tags/v1.0.0.tar.gz
tar -xzf v1.0.0.tar.gz
cd logboard-1.0.0
```
## Настройка
### 1. Создание файла переменных окружения
```bash
# Копирование примера конфигурации
make setup
# Или вручную
cp env.example .env
```
### 2. Редактирование конфигурации
Откройте файл `.env` и настройте основные параметры:
```bash
# Основные настройки
LOGBOARD_PORT=9001
LOGBOARD_USER=admin
LOGBOARD_PASS=your-secure-password
# Безопасность (обязательно измените!)
SECRET_KEY=your-very-secure-secret-key-here
ENCRYPTION_KEY=your-encryption-key-here
# Настройки Docker
DOCKER_HOST=unix:///var/run/docker.sock
```
### 3. Настройка прав доступа к Docker
```bash
# Создание группы docker (если не существует)
sudo groupadd docker
# Добавление пользователя в группу docker
sudo usermod -aG docker $USER
# Проверка прав доступа
ls -la /var/run/docker.sock
# srw-rw-rw- 1 root docker 0 Jan 15 10:00 /var/run/docker.sock
# Перезагрузка группы (или перелогин)
newgrp docker
```
### 4. Создание директорий
```bash
# Создание директории для снимков логов
mkdir -p snapshots
# Установка прав доступа
chmod 755 snapshots
```
## Первый запуск
### 1. Сборка Docker образа
```bash
# Сборка образа
make build
# Или вручную
docker compose build --no-cache
```
### 2. Запуск приложения
```bash
# Запуск в фоновом режиме
make up
# Или вручную
docker compose up -d
```
### 3. Проверка статуса
```bash
# Проверка статуса контейнеров
make status
# Просмотр логов
make logs
```
## Проверка установки
### 1. Проверка доступности веб-интерфейса
```bash
# Проверка HTTP ответа
curl -I http://localhost:9001
# HTTP/1.1 200 OK
# Content-Type: text/html; charset=utf-8
# Проверка health check
curl http://localhost:9001/healthz
# ok
```
### 2. Проверка API
```bash
# Получение токена аутентификации
curl -X POST "http://localhost:9001/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"your-secure-password"}'
# Ответ должен содержать access_token
```
### 3. Проверка Docker интеграции
```bash
# Проверка списка контейнеров
curl -X GET "http://localhost:9001/api/services" \
-H "Authorization: Bearer YOUR_TOKEN_HERE"
# Проверка списка проектов
curl -X GET "http://localhost:9001/api/projects" \
-H "Authorization: Bearer YOUR_TOKEN_HERE"
```
### 4. Проверка WebSocket соединений
```bash
# Тестирование WebSocket (требует специального клиента)
# Используйте браузер или wscat для тестирования
```
## Настройка для продакшена
### 1. Безопасность
#### Изменение паролей и ключей
```bash
# Генерация секретного ключа
openssl rand -hex 32
# 8a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6
# Генерация ключа шифрования
openssl rand -hex 32
# 1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z7
# Обновление .env файла
LOGBOARD_PASS=your-very-secure-password
SECRET_KEY=8a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6
ENCRYPTION_KEY=1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z7
```
#### Настройка HTTPS
Создайте файл `nginx.conf`:
```nginx
server {
listen 80;
server_name your-domain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name your-domain.com;
ssl_certificate /path/to/your/certificate.crt;
ssl_certificate_key /path/to/your/private.key;
location / {
proxy_pass http://localhost:9001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket поддержка
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
```
### 2. Мониторинг
#### Настройка логирования
```bash
# Создание директории для логов
sudo mkdir -p /var/log/logboard
sudo chown $USER:$USER /var/log/logboard
# Обновление docker-compose.yml
volumes:
- /var/log/logboard:/app/logs
```
#### Настройка systemd сервиса
Создайте файл `/etc/systemd/system/logboard.service`:
```ini
[Unit]
Description=LogBoard+ Docker Compose
Requires=docker.service
After=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/path/to/logboard
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0
[Install]
WantedBy=multi-user.target
```
```bash
# Включение автозапуска
sudo systemctl enable logboard.service
sudo systemctl start logboard.service
```
### 3. Резервное копирование
#### Скрипт резервного копирования
Создайте файл `backup.sh`:
```bash
#!/bin/bash
# Скрипт резервного копирования LogBoard+
BACKUP_DIR="/backup/logboard"
DATE=$(date +%Y%m%d_%H%M%S)
# Создание директории для резервных копий
mkdir -p $BACKUP_DIR
# Резервное копирование конфигурации
tar -czf $BACKUP_DIR/logboard_config_$DATE.tar.gz \
.env \
excluded_containers.json \
docker-compose.yml
# Резервное копирование снимков логов
tar -czf $BACKUP_DIR/logboard_snapshots_$DATE.tar.gz \
snapshots/
# Удаление старых резервных копий (старше 30 дней)
find $BACKUP_DIR -name "*.tar.gz" -mtime +30 -delete
echo "Резервное копирование завершено: $DATE"
```
```bash
# Установка прав на выполнение
chmod +x backup.sh
# Добавление в cron (ежедневно в 2:00)
crontab -e
# 0 2 * * * /path/to/logboard/backup.sh
```
### 4. Обновление
#### Скрипт обновления
Создайте файл `update.sh`:
```bash
#!/bin/bash
# Скрипт обновления LogBoard+
echo "Начинаем обновление LogBoard+..."
# Остановка сервисов
make down
# Получение обновлений
git pull origin main
# Пересборка образов
make build
# Запуск сервисов
make up
# Проверка статуса
make status
echo "Обновление завершено!"
```
```bash
# Установка прав на выполнение
chmod +x update.sh
```
## Устранение проблем установки
### Частые ошибки
1. **Ошибка доступа к Docker socket**
```bash
# Решение: добавление пользователя в группу docker
sudo usermod -aG docker $USER
newgrp docker
```
2. **Ошибка порта уже используется**
```bash
# Решение: изменение порта в .env
LOGBOARD_PORT=9002
```
3. **Ошибка недостаточно памяти**
```bash
# Решение: увеличение лимитов Docker
sudo systemctl edit docker
# Добавьте:
# [Service]
# MemoryLimit=2G
```
4. **Ошибка SSL сертификатов**
```bash
# Решение: использование самоподписанных сертификатов для тестирования
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout private.key -out certificate.crt
```
### Логи для диагностики
```bash
# Просмотр логов приложения
make logs
# Просмотр логов Docker
docker logs logboard
# Проверка статуса контейнера
docker inspect logboard
```
## Следующие шаги
После успешной установки:
1. [Настройте безопасность](./security.md)
2. [Изучите API документацию](./api.md)
3. [Настройте конфигурацию](./configuration.md)
4. [Изучите WebSocket API](./websocket.md)

750
docs/security.md Normal file
View File

@ -0,0 +1,750 @@
# Безопасность LogBoard+
**Автор:** Сергей Антропов
**Сайт:** https://devops.org.ru
## Содержание
1. [Обзор безопасности](#обзор-безопасности)
2. [Генерация ключей](#генерация-ключей)
3. [Аутентификация](#аутентификация)
4. [Настройка HTTPS](#настройка-https)
5. [Доступ к Docker](#доступ-к-docker)
6. [Сетевая безопасность](#сетевая-безопасность)
7. [Мониторинг безопасности](#мониторинг-безопасности)
8. [Рекомендации для продакшена](#рекомендации-для-продакшена)
## Обзор безопасности
LogBoard+ включает в себя несколько уровней безопасности для защиты доступа к логам и системной информации. Правильная настройка безопасности критически важна для продакшена.
### Уровни безопасности
1. **Аутентификация** - JWT токены для доступа к API
2. **Шифрование** - Защита чувствительных данных
3. **Сетевая безопасность** - HTTPS, файрвол, ограничение доступа
4. **Docker безопасность** - Ограничение прав доступа к Docker socket
5. **Мониторинг** - Логирование событий безопасности
## Генерация ключей
### SECRET_KEY
SECRET_KEY используется для подписи JWT токенов. Это критически важный ключ для безопасности аутентификации.
#### Генерация SECRET_KEY
**Метод 1: OpenSSL (рекомендуется)**
```bash
# Генерация 32-байтового ключа в hex формате
openssl rand -hex 32
# Пример вывода: 8a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6
# Генерация 64-байтового ключа (для максимальной безопасности)
openssl rand -hex 64
# Пример вывода: 8a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6...
```
**Метод 2: Python**
```bash
# Использование Python для генерации
python3 -c "import secrets; print(secrets.token_hex(32))"
# Пример вывода: 8a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6
# Генерация через Python скрипт
python3 -c "
import secrets
import base64
key = secrets.token_bytes(32)
print('Hex:', key.hex())
print('Base64:', base64.b64encode(key).decode())
"
```
**Метод 3: Node.js**
```bash
# Использование Node.js
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# Пример вывода: 8a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6
```
#### Требования к SECRET_KEY
- **Длина:** Минимум 32 байта (64 символа в hex)
- **Энтропия:** Используйте криптографически стойкий генератор случайных чисел
- **Уникальность:** Каждая установка должна иметь уникальный ключ
- **Хранение:** Храните в безопасном месте, не в коде
### ENCRYPTION_KEY
ENCRYPTION_KEY используется для шифрования чувствительных данных в приложении.
#### Генерация ENCRYPTION_KEY
**Метод 1: OpenSSL (рекомендуется)**
```bash
# Генерация 32-байтового ключа шифрования
openssl rand -hex 32
# Пример вывода: 1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z7
# Генерация ключа для AES-256
openssl rand -base64 32
# Пример вывода: 1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z7
```
**Метод 2: Python с Fernet**
```bash
# Генерация ключа для Fernet (Base64)
python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
# Пример вывода: 1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z7
# Генерация через Python скрипт
python3 -c "
import secrets
import base64
key = secrets.token_bytes(32)
print('Hex:', key.hex())
print('Base64:', base64.b64encode(key).decode())
print('Fernet:', base64.urlsafe_b64encode(key).decode())
"
```
**Метод 3: Скрипт генерации ключей**
Создайте файл `generate_keys.sh`:
```bash
#!/bin/bash
# Скрипт генерации ключей безопасности для LogBoard+
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
echo "=== Генерация ключей безопасности для LogBoard+ ==="
echo ""
# Проверка наличия OpenSSL
if ! command -v openssl &> /dev/null; then
echo "❌ OpenSSL не найден. Установите OpenSSL для генерации ключей."
exit 1
fi
echo "🔐 Генерация SECRET_KEY..."
SECRET_KEY=$(openssl rand -hex 32)
echo "SECRET_KEY=$SECRET_KEY"
echo ""
echo "🔐 Генерация ENCRYPTION_KEY..."
ENCRYPTION_KEY=$(openssl rand -hex 32)
echo "ENCRYPTION_KEY=$ENCRYPTION_KEY"
echo ""
echo "🔐 Генерация пароля пользователя..."
USER_PASSWORD=$(openssl rand -base64 16 | tr -d "=+/" | cut -c1-16)
echo "LOGBOARD_PASS=$USER_PASSWORD"
echo ""
echo "📝 Обновление .env файла..."
if [ -f .env ]; then
# Создание резервной копии
cp .env .env.backup.$(date +%Y%m%d_%H%M%S)
echo "✅ Создана резервная копия .env"
# Обновление ключей
sed -i "s/^SECRET_KEY=.*/SECRET_KEY=$SECRET_KEY/" .env
sed -i "s/^ENCRYPTION_KEY=.*/ENCRYPTION_KEY=$ENCRYPTION_KEY/" .env
sed -i "s/^LOGBOARD_PASS=.*/LOGBOARD_PASS=$USER_PASSWORD/" .env
echo "✅ Ключи обновлены в .env"
else
echo "⚠️ Файл .env не найден. Создайте его из env.example"
fi
echo ""
echo "=== Рекомендации по безопасности ==="
echo "1. Сохраните ключи в безопасном месте"
echo "2. Не передавайте ключи через незащищенные каналы"
echo "3. Регулярно обновляйте ключи"
echo "4. Используйте разные ключи для разных окружений"
echo ""
echo "✅ Генерация ключей завершена!"
```
```bash
# Установка прав на выполнение
chmod +x generate_keys.sh
# Запуск генерации
./generate_keys.sh
```
#### Требования к ENCRYPTION_KEY
- **Длина:** 32 байта для AES-256
- **Формат:** Hex или Base64
- **Энтропия:** Криптографически стойкий генератор
- **Уникальность:** Уникальный для каждой установки
### Проверка качества ключей
```bash
# Проверка энтропии ключа
python3 -c "
import secrets
import base64
def check_entropy(key_hex):
# Преобразование hex в байты
key_bytes = bytes.fromhex(key_hex)
# Подсчет уникальных байтов
unique_bytes = len(set(key_bytes))
total_bytes = len(key_bytes)
# Вычисление энтропии
entropy = unique_bytes / total_bytes
print(f'Длина ключа: {total_bytes} байт')
print(f'Уникальных байтов: {unique_bytes}')
print(f'Энтропия: {entropy:.2%}')
if entropy > 0.8:
print('✅ Ключ имеет хорошую энтропию')
else:
print('⚠️ Ключ может иметь низкую энтропию')
# Проверка вашего ключа
key = '8a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6'
check_entropy(key)
"
```
## Аутентификация
### JWT токены
LogBoard+ использует JWT (JSON Web Tokens) для аутентификации.
#### Структура JWT токена
```json
{
"header": {
"alg": "HS256",
"typ": "JWT"
},
"payload": {
"sub": "admin",
"exp": 1642234567,
"iat": 1642230967
},
"signature": "HMACSHA256(base64UrlEncode(header) + '.' + base64UrlEncode(payload), SECRET_KEY)"
}
```
#### Настройка JWT
```bash
# В .env файле
SECRET_KEY=your-secret-key-here
SESSION_TIMEOUT=3600 # Время жизни токена в секундах
```
#### Безопасность JWT
1. **Используйте сильный SECRET_KEY**
2. **Установите разумное время жизни токена**
3. **Используйте HTTPS для передачи токенов**
4. **Регулярно обновляйте токены**
### Пароли пользователей
#### Генерация безопасного пароля
```bash
# Генерация пароля с OpenSSL
openssl rand -base64 16 | tr -d "=+/" | cut -c1-16
# Пример вывода: aB3cD4eF5gH6iJ7k
# Генерация пароля с Python
python3 -c "
import secrets
import string
def generate_password(length=16):
alphabet = string.ascii_letters + string.digits + '!@#$%^&*'
return ''.join(secrets.choice(alphabet) for _ in range(length))
print('Пароль:', generate_password(16))
"
# Генерация пароля с pwgen
pwgen -s 16 1
```
#### Требования к паролям
- **Длина:** Минимум 12 символов
- **Сложность:** Буквы, цифры, специальные символы
- **Уникальность:** Не используйте пароли по умолчанию
- **Хранение:** Храните в безопасном месте
## Настройка HTTPS
### Самоподписанные сертификаты (для тестирования)
```bash
# Генерация приватного ключа
openssl genrsa -out private.key 2048
# Генерация сертификата
openssl req -x509 -new -nodes -key private.key -sha256 -days 365 -out certificate.crt
# Или одной командой
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout private.key -out certificate.crt \
-subj "/C=RU/ST=Moscow/L=Moscow/O=Company/CN=localhost"
```
### Let's Encrypt сертификаты (для продакшена)
```bash
# Установка certbot
sudo apt install certbot python3-certbot-nginx
# Получение сертификата
sudo certbot --nginx -d your-domain.com
# Автоматическое обновление
sudo crontab -e
# Добавить строку:
# 0 12 * * * /usr/bin/certbot renew --quiet
```
### Nginx конфигурация с HTTPS
```nginx
server {
listen 80;
server_name your-domain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name your-domain.com;
# SSL сертификаты
ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/private.key;
# SSL настройки
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# Безопасные заголовки
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location / {
proxy_pass http://localhost:9001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket поддержка
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Таймауты
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
```
## Доступ к Docker
### Ограничение прав доступа
```bash
# Создание группы docker
sudo groupadd docker
# Добавление пользователя в группу
sudo usermod -aG docker $USER
# Проверка прав
ls -la /var/run/docker.sock
# Должно быть: srw-rw-rw- 1 root docker 0 Jan 15 10:00 /var/run/docker.sock
# Перезагрузка группы
newgrp docker
```
### Альтернативные методы
#### Метод 1: Docker API с TLS
```bash
# Генерация CA ключа
openssl genrsa -aes256 -out ca-key.pem 4096
# Генерация CA сертификата
openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem
# Генерация серверного ключа
openssl genrsa -out server-key.pem 4096
# Генерация серверного сертификата
openssl req -subj "/CN=$HOST" -sha256 -new -key server-key.pem -out server.csr
# Подпись серверного сертификата
openssl x509 -req -days 365 -sha256 \
-in server.csr -CA ca.pem -CAkey ca-key.pem \
-CAcreateserial -out server-cert.pem
# Настройка Docker daemon
sudo mkdir -p /etc/docker
sudo cp ca.pem server-cert.pem server-key.pem /etc/docker/
# Конфигурация Docker daemon
sudo tee /etc/docker/daemon.json <<EOF
{
"tls": true,
"tlscacert": "/etc/docker/ca.pem",
"tlscert": "/etc/docker/server-cert.pem",
"tlskey": "/etc/docker/server-key.pem",
"hosts": ["tcp://0.0.0.0:2376", "unix:///var/run/docker.sock"]
}
EOF
# Перезапуск Docker
sudo systemctl restart docker
```
#### Метод 2: Docker API с аутентификацией
```bash
# Создание пользователя для API
sudo useradd -r -s /bin/false docker-api
# Настройка прав
sudo usermod -aG docker docker-api
# Конфигурация в .env
DOCKER_HOST=tcp://localhost:2375
DOCKER_TLS_VERIFY=1
DOCKER_CERT_PATH=/path/to/certs
```
## Сетевая безопасность
### Настройка файрвола
```bash
# Установка UFW
sudo apt install ufw
# Базовые правила
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Разрешение SSH
sudo ufw allow ssh
# Разрешение HTTPS
sudo ufw allow 443/tcp
# Разрешение LogBoard+ только для локальной сети
sudo ufw allow from 192.168.1.0/24 to any port 9001
# Включение файрвола
sudo ufw enable
# Проверка статуса
sudo ufw status verbose
```
### iptables правила
```bash
# Очистка правил
sudo iptables -F
sudo iptables -X
# Установка политик по умолчанию
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP
sudo iptables -P OUTPUT ACCEPT
# Разрешение локального трафика
sudo iptables -A INPUT -i lo -j ACCEPT
# Разрешение установленных соединений
sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# Разрешение SSH
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# Разрешение HTTPS
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# Разрешение LogBoard+ для локальной сети
sudo iptables -A INPUT -p tcp --dport 9001 -s 192.168.1.0/24 -j ACCEPT
# Сохранение правил
sudo iptables-save > /etc/iptables/rules.v4
```
### Настройка Docker сетей
```yaml
# В docker-compose.yml
networks:
logboard_network:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16
driver_opts:
com.docker.network.bridge.name: logboard_br0
services:
logboard:
networks:
- logboard_network
# Ограничение доступа к Docker socket
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
```
## Мониторинг безопасности
### Логирование событий безопасности
```bash
# Настройка логирования в .env
LOG_LEVEL=INFO
LOG_FORMAT=json
# Создание директории для логов
sudo mkdir -p /var/log/logboard
sudo chown $USER:$USER /var/log/logboard
# Настройка ротации логов
sudo tee /etc/logrotate.d/logboard <<EOF
/var/log/logboard/*.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 644 $USER $USER
postrotate
docker compose -f /path/to/logboard/docker-compose.yml restart logboard
endscript
}
EOF
```
### Мониторинг доступа
```bash
# Скрипт мониторинга доступа
cat > monitor_access.sh <<'EOF'
#!/bin/bash
# Мониторинг доступа к LogBoard+
# Автор: Сергей Антропов
LOG_FILE="/var/log/logboard/access.log"
ALERT_FILE="/var/log/logboard/security_alerts.log"
# Функция логирования
log_alert() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$ALERT_FILE"
}
# Мониторинг неудачных попыток входа
failed_logins=$(grep -c "authentication failed" "$LOG_FILE" 2>/dev/null || echo "0")
if [ "$failed_logins" -gt 10 ]; then
log_alert "Обнаружено $failed_logins неудачных попыток входа"
fi
# Мониторинг подозрительных IP
suspicious_ips=$(grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b" "$LOG_FILE" | sort | uniq -c | awk '$1 > 100 {print $2}')
if [ -n "$suspicious_ips" ]; then
log_alert "Подозрительная активность с IP: $suspicious_ips"
fi
echo "Мониторинг завершен: $(date)"
EOF
chmod +x monitor_access.sh
# Добавление в cron
crontab -e
# Добавить строку:
# */5 * * * * /path/to/logboard/monitor_access.sh
```
### Алерты безопасности
```bash
# Скрипт отправки алертов
cat > security_alerts.sh <<'EOF'
#!/bin/bash
# Отправка алертов безопасности
# Автор: Сергей Антропов
ALERT_FILE="/var/log/logboard/security_alerts.log"
EMAIL="admin@company.com"
# Проверка новых алертов
if [ -f "$ALERT_FILE" ]; then
new_alerts=$(tail -n 10 "$ALERT_FILE")
if [ -n "$new_alerts" ]; then
echo "$new_alerts" | mail -s "LogBoard+ Security Alert" "$EMAIL"
fi
fi
EOF
chmod +x security_alerts.sh
# Добавление в cron
crontab -e
# Добавить строку:
# 0 */1 * * * /path/to/logboard/security_alerts.sh
```
## Рекомендации для продакшена
### Обязательные настройки
1. **Измените все пароли по умолчанию**
2. **Используйте сильные секретные ключи**
3. **Настройте HTTPS**
4. **Ограничьте доступ к Docker socket**
5. **Настройте файрвол**
6. **Включите мониторинг безопасности**
### Регулярные проверки
```bash
# Скрипт проверки безопасности
cat > security_check.sh <<'EOF'
#!/bin/bash
# Проверка безопасности LogBoard+
# Автор: Сергей Антропов
echo "=== Проверка безопасности LogBoard+ ==="
echo "Дата: $(date)"
echo ""
# Проверка паролей по умолчанию
if grep -q "LOGBOARD_PASS=admin" .env; then
echo "❌ Пароль по умолчанию не изменен!"
else
echo "✅ Пароль изменен"
fi
# Проверка секретных ключей
if grep -q "SECRET_KEY=your-secret-key-here" .env; then
echo "❌ SECRET_KEY не изменен!"
else
echo "✅ SECRET_KEY изменен"
fi
if grep -q "ENCRYPTION_KEY=your-encryption-key-here" .env; then
echo "❌ ENCRYPTION_KEY не изменен!"
else
echo "✅ ENCRYPTION_KEY изменен"
fi
# Проверка HTTPS
if curl -s -I https://localhost:9001 > /dev/null 2>&1; then
echo "✅ HTTPS доступен"
else
echo "⚠️ HTTPS недоступен"
fi
# Проверка прав Docker
if ls -la /var/run/docker.sock | grep -q "srw-rw-rw-"; then
echo "✅ Docker socket доступен"
else
echo "❌ Проблемы с доступом к Docker socket"
fi
# Проверка файрвола
if command -v ufw > /dev/null && sudo ufw status | grep -q "Status: active"; then
echo "✅ Файрвол активен"
else
echo "⚠️ Файрвол не настроен"
fi
echo ""
echo "=== Рекомендации ==="
echo "1. Регулярно обновляйте ключи"
echo "2. Мониторьте логи доступа"
echo "3. Обновляйте зависимости"
echo "4. Проверяйте права доступа"
echo "5. Тестируйте резервные копии"
EOF
chmod +x security_check.sh
# Запуск проверки
./security_check.sh
```
### План реагирования на инциденты
1. **Немедленные действия**
- Отключение подозрительных IP
- Блокировка скомпрометированных учетных записей
- Анализ логов
2. **Расследование**
- Определение источника атаки
- Оценка ущерба
- Документирование инцидента
3. **Восстановление**
- Обновление ключей
- Патчинг уязвимостей
- Восстановление из резервных копий
4. **Профилактика**
- Усиление безопасности
- Обновление процедур
- Обучение персонала
### Контрольный список безопасности
- [ ] Изменены пароли по умолчанию
- [ ] Сгенерированы сильные секретные ключи
- [ ] Настроен HTTPS
- [ ] Ограничен доступ к Docker
- [ ] Настроен файрвол
- [ ] Включено логирование
- [ ] Настроен мониторинг
- [ ] Созданы резервные копии
- [ ] Документированы процедуры
- [ ] Проведено тестирование безопасности

767
docs/troubleshooting.md Normal file
View File

@ -0,0 +1,767 @@
# Устранение неполадок LogBoard+
**Автор:** Сергей Антропов
**Сайт:** https://devops.org.ru
## Содержание
1. [Общие проблемы](#общие-проблемы)
2. [Проблемы с Docker](#проблемы-с-docker)
3. [Проблемы с аутентификацией](#проблемы-с-аутентификацией)
4. [Проблемы с WebSocket](#проблемы-с-websocket)
5. [Проблемы с производительностью](#проблемы-с-производительностью)
6. [Проблемы с сетью](#проблемы-с-сетью)
7. [Диагностика и логи](#диагностика-и-логи)
## Общие проблемы
### Приложение не запускается
#### Симптомы
- Ошибка при выполнении `make up`
- Контейнер не стартует
- Ошибки в логах Docker
#### Диагностика
```bash
# Проверка статуса контейнеров
make status
# Просмотр логов
make logs
# Проверка конфигурации
make validate
# Проверка переменных окружения
make env-check
```
#### Решения
1. **Проблема с портом**
```bash
# Проверка занятости порта
netstat -tlnp | grep 9001
# Изменение порта в .env
LOGBOARD_PORT=9002
```
2. **Проблема с правами доступа**
```bash
# Проверка прав на директории
ls -la snapshots/
# Установка правильных прав
chmod 755 snapshots/
chown $USER:$USER snapshots/
```
3. **Проблема с Docker**
```bash
# Проверка статуса Docker
sudo systemctl status docker
# Перезапуск Docker
sudo systemctl restart docker
```
### Ошибки конфигурации
#### Симптомы
- Ошибки при парсинге переменных окружения
- Неправильная работа функций
- Ошибки в логах приложения
#### Диагностика
```bash
# Проверка синтаксиса .env файла
cat .env | grep -v "^#" | grep -v "^$"
# Проверка обязательных переменных
grep -E "^(LOGBOARD_PASS|SECRET_KEY|ENCRYPTION_KEY)=" .env
```
#### Решения
1. **Отсутствуют обязательные переменные**
```bash
# Создание .env из примера
make setup
# Редактирование .env
nano .env
```
2. **Неправильный формат переменных**
```bash
# Проверка формата
# Переменные должны быть в формате KEY=value
# Без пробелов вокруг знака равенства
```
## Проблемы с Docker
### Ошибка подключения к Docker socket
#### Симптомы
- Ошибка "Permission denied" при доступе к Docker
- Контейнеры не отображаются в интерфейсе
- Ошибки в логах приложения
#### Диагностика
```bash
# Проверка прав доступа к Docker socket
ls -la /var/run/docker.sock
# Проверка группы docker
getent group docker
# Проверка пользователя в группе
groups $USER
```
#### Решения
1. **Добавление пользователя в группу docker**
```bash
# Создание группы docker (если не существует)
sudo groupadd docker
# Добавление пользователя в группу
sudo usermod -aG docker $USER
# Перезагрузка группы
newgrp docker
# Проверка
docker ps
```
2. **Изменение прав на Docker socket**
```bash
# Временное решение (не рекомендуется для продакшена)
sudo chmod 666 /var/run/docker.sock
```
3. **Перезапуск Docker daemon**
```bash
sudo systemctl restart docker
```
### Контейнеры не отображаются
#### Симптомы
- Пустой список контейнеров в интерфейсе
- Ошибки при получении списка контейнеров
- Контейнеры видны в `docker ps`, но не в LogBoard+
#### Диагностика
```bash
# Проверка списка контейнеров
docker ps -a
# Проверка меток контейнеров
docker inspect <container_id> | grep -A 10 "Labels"
# Проверка фильтров проектов
echo $COMPOSE_PROJECT_NAME
echo $LOGBOARD_PROJECTS
```
#### Решения
1. **Проблема с фильтрами проектов**
```bash
# Очистка фильтров в .env
# COMPOSE_PROJECT_NAME=
# LOGBOARD_PROJECTS=
# Перезапуск приложения
make restart
```
2. **Проблема с метками контейнеров**
```bash
# Проверка меток Docker Compose
docker inspect <container_id> | grep "com.docker.compose"
# Перезапуск контейнеров с правильными метками
docker compose down && docker compose up -d
```
3. **Проблема с исключенными контейнерами**
```bash
# Проверка файла исключений
cat excluded_containers.json
# Очистка исключений
echo '{"excluded_containers": []}' > excluded_containers.json
```
### Ошибки получения логов
#### Симптомы
- Логи не загружаются
- Ошибки при запросе логов
- Пустые логи в интерфейсе
#### Диагностика
```bash
# Проверка логов контейнера напрямую
docker logs <container_id>
# Проверка статуса контейнера
docker inspect <container_id> | grep -A 5 "State"
# Проверка прав доступа к логам
docker logs <container_id> 2>&1
```
#### Решения
1. **Контейнер остановлен**
```bash
# Запуск контейнера
docker start <container_id>
# Проверка статуса
docker ps
```
2. **Проблема с драйвером логирования**
```bash
# Проверка драйвера логирования
docker inspect <container_id> | grep "LogDriver"
# Изменение драйвера логирования
docker run --log-driver=json-file ...
```
3. **Проблема с размером логов**
```bash
# Очистка логов контейнера
docker logs --since 1h <container_id>
# Ограничение размера логов
docker run --log-opt max-size=10m ...
```
## Проблемы с аутентификацией
### Ошибка входа в систему
#### Симптомы
- Не удается войти в систему
- Ошибка "Неверное имя пользователя или пароль"
- Бесконечная перезагрузка страницы входа
#### Диагностика
```bash
# Проверка переменных аутентификации
grep -E "^(LOGBOARD_USER|LOGBOARD_PASS)=" .env
# Проверка API аутентификации
curl -X POST "http://localhost:9001/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin"}'
```
#### Решения
1. **Неправильные учетные данные**
```bash
# Проверка и исправление .env
LOGBOARD_USER=admin
LOGBOARD_PASS=your-correct-password
# Перезапуск приложения
make restart
```
2. **Проблема с JWT токенами**
```bash
# Проверка SECRET_KEY
grep "SECRET_KEY" .env
# Генерация нового ключа
openssl rand -hex 32
# Обновление .env
SECRET_KEY=your-new-secret-key
```
3. **Проблема с cookies**
```bash
# Очистка cookies браузера
# Или использование режима инкогнито
```
### Ошибки авторизации API
#### Симптомы
- Ошибка 401 при запросах к API
- "Требуется авторизация" в ответах
- Не работает WebSocket API
#### Диагностика
```bash
# Проверка токена
curl -X GET "http://localhost:9001/api/auth/me" \
-H "Authorization: Bearer YOUR_TOKEN"
# Проверка времени жизни токена
echo $SESSION_TIMEOUT
```
#### Решения
1. **Истекший токен**
```bash
# Увеличение времени жизни токена
SESSION_TIMEOUT=7200 # 2 часа
# Перезапуск приложения
make restart
```
2. **Неправильный формат токена**
```bash
# Получение нового токена
curl -X POST "http://localhost:9001/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"your-password"}'
```
3. **Проблема с часовыми поясами**
```bash
# Установка правильного часового пояса
TZ_TS=UTC
# Перезапуск приложения
make restart
```
## Проблемы с WebSocket
### WebSocket соединения не устанавливаются
#### Симптомы
- Ошибки WebSocket в консоли браузера
- Логи не обновляются в реальном времени
- Ошибка "WebSocket connection failed"
#### Диагностика
```bash
# Проверка WebSocket endpoint
curl -I "http://localhost:9001/ws/logs/test"
# Проверка логов приложения
make logs | grep -i websocket
# Проверка в браузере
# Открыть Developer Tools -> Console
```
#### Решения
1. **Проблема с прокси**
```nginx
# Настройка Nginx для WebSocket
location /ws/ {
proxy_pass http://localhost:9001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
```
2. **Проблема с токеном**
```javascript
// Проверка токена в WebSocket URL
const token = "your-valid-token";
const ws = new WebSocket(`ws://localhost:9001/ws/logs/container-id?token=${token}`);
```
3. **Проблема с CORS**
```bash
# Добавление CORS заголовков в конфигурацию
# (если используется)
```
### WebSocket соединения разрываются
#### Симптомы
- Соединения закрываются через некоторое время
- Ошибки "Connection closed unexpectedly"
- Логи перестают обновляться
#### Диагностика
```bash
# Проверка таймаутов
grep -E "TIMEOUT|timeout" .env
# Проверка нагрузки на систему
top
free -h
```
#### Решения
1. **Увеличение таймаутов**
```bash
# Увеличение таймаутов в .env
CONNECTION_TIMEOUT=120
READ_TIMEOUT=300
# Перезапуск приложения
make restart
```
2. **Проблема с нагрузкой**
```bash
# Ограничение количества соединений
MAX_CONNECTIONS=50
# Увеличение ресурсов контейнера
# В docker-compose.yml
deploy:
resources:
limits:
memory: 1G
cpus: '1.0'
```
3. **Проблема с сетевыми таймаутами**
```bash
# Настройка keepalive
# В конфигурации прокси или балансировщика
```
## Проблемы с производительностью
### Медленная загрузка логов
#### Симптомы
- Долгая загрузка страниц
- Медленное получение логов
- Высокая нагрузка на CPU
#### Диагностика
```bash
# Проверка ресурсов системы
htop
iostat 1
df -h
# Проверка настроек производительности
grep -E "TAIL|INTERVAL" .env
```
#### Решения
1. **Оптимизация настроек**
```bash
# Уменьшение количества строк логов
LOGBOARD_TAIL=100
# Увеличение интервала обновления
LOGBOARD_AJAX_UPDATE_INTERVAL=5000
# Перезапуск приложения
make restart
```
2. **Ограничение ресурсов**
```bash
# Настройка лимитов в docker-compose.yml
deploy:
resources:
limits:
memory: 512M
cpus: '0.5'
reservations:
memory: 256M
cpus: '0.25'
```
3. **Оптимизация Docker**
```bash
# Очистка неиспользуемых ресурсов
docker system prune -f
# Ограничение логов контейнеров
docker run --log-opt max-size=10m --log-opt max-file=3 ...
```
### Высокое потребление памяти
#### Симптомы
- Контейнер LogBoard+ потребляет много памяти
- Ошибки "Out of memory"
- Медленная работа системы
#### Диагностика
```bash
# Проверка использования памяти
docker stats logboard
# Проверка процессов в контейнере
docker exec logboard ps aux
# Проверка логов приложения
make logs | grep -i memory
```
#### Решения
1. **Ограничение памяти**
```bash
# Установка лимитов памяти
# В docker-compose.yml
deploy:
resources:
limits:
memory: 512M
```
2. **Оптимизация кода**
```bash
# Уменьшение размера буферов
# Уменьшение количества одновременных соединений
MAX_CONNECTIONS=50
```
3. **Очистка кэша**
```bash
# Перезапуск приложения
make restart
# Очистка Docker кэша
docker system prune -f
```
## Проблемы с сетью
### Недоступность веб-интерфейса
#### Симптомы
- Не удается открыть веб-интерфейс
- Ошибка "Connection refused"
- Таймаут при подключении
#### Диагностика
```bash
# Проверка статуса контейнера
make status
# Проверка портов
netstat -tlnp | grep 9001
# Проверка доступности
curl -I http://localhost:9001
```
#### Решения
1. **Проблема с портом**
```bash
# Проверка занятости порта
sudo lsof -i :9001
# Изменение порта
LOGBOARD_PORT=9002
make restart
```
2. **Проблема с сетевыми настройками**
```bash
# Проверка Docker сетей
docker network ls
# Пересоздание сетей
docker compose down
docker compose up -d
```
3. **Проблема с файрволом**
```bash
# Проверка файрвола
sudo ufw status
# Разрешение порта
sudo ufw allow 9001
```
### Проблемы с внешним доступом
#### Симптомы
- Доступ только с localhost
- Не работает с внешним IP
- Проблемы с reverse proxy
#### Диагностика
```bash
# Проверка привязки к интерфейсам
netstat -tlnp | grep 9001
# Проверка конфигурации Docker
docker inspect logboard | grep -A 10 "NetworkSettings"
```
#### Решения
1. **Настройка привязки к интерфейсам**
```yaml
# В docker-compose.yml
ports:
- "0.0.0.0:9001:9001"
```
2. **Настройка reverse proxy**
```nginx
# Nginx конфигурация
location / {
proxy_pass http://localhost:9001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
```
3. **Настройка файрвола**
```bash
# Разрешение внешнего доступа
sudo ufw allow from 192.168.1.0/24 to any port 9001
```
## Диагностика и логи
### Просмотр логов приложения
```bash
# Логи в реальном времени
make logs -f
# Логи с фильтрацией
make logs | grep ERROR
# Логи последних 100 строк
make logs | tail -100
```
### Просмотр логов Docker
```bash
# Логи контейнера LogBoard+
docker logs logboard
# Логи с временными метками
docker logs -t logboard
# Логи последних 50 строк
docker logs --tail 50 logboard
```
### Проверка состояния системы
```bash
# Статус сервисов
make status
# Использование ресурсов
docker stats logboard
# Информация о контейнере
docker inspect logboard
```
### Отладка API
```bash
# Проверка health check
curl http://localhost:9001/healthz
# Проверка аутентификации
curl -X POST "http://localhost:9001/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin"}'
# Проверка API с токеном
TOKEN="your-token"
curl -X GET "http://localhost:9001/api/services" \
-H "Authorization: Bearer $TOKEN"
```
### Создание отчета о проблеме
```bash
# Создание диагностического отчета
cat > diagnostic_report.txt << EOF
=== LogBoard+ Diagnostic Report ===
Date: $(date)
Version: 1.0.0
=== System Information ===
OS: $(uname -a)
Docker: $(docker --version)
Docker Compose: $(docker compose version)
=== LogBoard+ Status ===
$(make status)
=== Configuration ===
$(grep -v "^#" .env | grep -v "^$")
=== Recent Logs ===
$(make logs | tail -50)
=== Docker Information ===
$(docker inspect logboard | jq '.[] | {State: .State, NetworkSettings: .NetworkSettings}')
=== System Resources ===
$(free -h)
$(df -h /)
EOF
echo "Diagnostic report saved to diagnostic_report.txt"
```
### Частые команды для диагностики
```bash
# Быстрая диагностика
make env-check && make validate && make status
# Проверка подключения к Docker
docker ps
# Проверка доступности API
curl -s http://localhost:9001/healthz
# Проверка аутентификации
curl -s -X POST "http://localhost:9001/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin"}' | jq .
# Проверка списка контейнеров (требует токен)
TOKEN=$(curl -s -X POST "http://localhost:9001/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin"}' | jq -r '.access_token')
curl -s -X GET "http://localhost:9001/api/services" \
-H "Authorization: Bearer $TOKEN" | jq 'length'
```

913
docs/websocket.md Normal file
View File

@ -0,0 +1,913 @@
# WebSocket API LogBoard+
**Автор:** Сергей Антропов
**Сайт:** https://devops.org.ru
## Содержание
1. [Обзор WebSocket API](#обзор-websocket-api)
2. [Аутентификация](#аутентификация)
3. [Endpoints](#endpoints)
4. [Примеры использования](#примеры-использования)
5. [Обработка ошибок](#обработка-ошибок)
6. [Лучшие практики](#лучшие-практики)
## Обзор WebSocket API
WebSocket API LogBoard+ предоставляет возможность получения логов Docker контейнеров в реальном времени. Это позволяет создавать интерактивные приложения для мониторинга и отладки микросервисов.
### Преимущества WebSocket
- **Real-time данные** - мгновенное получение новых логов
- **Эффективность** - меньше накладных расходов по сравнению с polling
- **Масштабируемость** - поддержка множественных соединений
- **Фильтрация** - получение логов только нужных контейнеров
### Поддерживаемые протоколы
- **ws://** - для HTTP соединений
- **wss://** - для HTTPS соединений (рекомендуется для продакшена)
## Аутентификация
Все WebSocket endpoints требуют JWT токен для аутентификации. Токен передается как параметр запроса `token`.
### Получение токена
```bash
# Получение JWT токена через REST API
curl -X POST "http://localhost:9001/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"your-password"}'
```
### Использование токена
```javascript
const token = "your-jwt-token";
const ws = new WebSocket(`ws://localhost:9001/ws/logs/container-id?token=${token}`);
```
## Endpoints
### ws://host:port/ws/logs/{container_id}
Получение логов отдельного контейнера в реальном времени.
#### Параметры
| Параметр | Тип | Обязательный | Описание |
|----------|-----|--------------|----------|
| `container_id` | string | Да | ID контейнера (первые 12 символов) |
| `token` | string | Да | JWT токен аутентификации |
| `tail` | number | Нет | Количество начальных строк (по умолчанию 500) |
| `service` | string | Нет | Имя сервиса (для информации) |
| `project` | string | Нет | Имя проекта (для информации) |
#### Пример подключения
```javascript
const containerId = "abc123def456";
const token = "your-jwt-token";
const ws = new WebSocket(`ws://localhost:9001/ws/logs/${containerId}?token=${token}&tail=100`);
ws.onopen = function() {
console.log('WebSocket соединение установлено');
};
ws.onmessage = function(event) {
console.log('Получены логи:', event.data);
};
ws.onerror = function(error) {
console.error('WebSocket ошибка:', error);
};
ws.onclose = function(event) {
console.log('WebSocket соединение закрыто:', event.code, event.reason);
};
```
#### Формат сообщений
При подключении сервер отправляет приветственное сообщение:
```
Connected to container: myproject_web_1
```
Затем отправляются логи в формате:
```
2024-01-15T10:30:15.123456789Z 2024/01/15 10:30:15 [notice] 1#1: start worker processes
2024-01-15T10:30:15.123456789Z 2024/01/15 10:30:15 [notice] 1#1: start worker process 1234
```
### ws://host:port/ws/fan/{service_name}
Получение логов всех реплик сервиса Docker Compose (fan-in).
#### Параметры
| Параметр | Тип | Обязательный | Описание |
|----------|-----|--------------|----------|
| `service_name` | string | Да | Имя сервиса Docker Compose |
| `token` | string | Да | JWT токен аутентификации |
| `tail` | number | Нет | Количество начальных строк (по умолчанию 500) |
| `project` | string | Нет | Имя проекта (для фильтрации) |
#### Пример подключения
```javascript
const serviceName = "web";
const token = "your-jwt-token";
const ws = new WebSocket(`ws://localhost:9001/ws/fan/${serviceName}?token=${token}&tail=100`);
ws.onmessage = function(event) {
// Логи приходят с префиксом ID контейнера
console.log('Логи сервиса:', event.data);
};
```
#### Формат сообщений
Логи приходят с префиксом короткого ID контейнера:
```
[abc123de] 2024-01-15T10:30:15.123456789Z 2024/01/15 10:30:15 [notice] 1#1: start worker processes
[def456gh] 2024-01-15T10:30:16.123456789Z 2024/01/15 10:30:16 [notice] 1#1: start worker processes
```
### ws://host:port/ws/fan_group
Получение логов группы сервисов одновременно.
#### Параметры
| Параметр | Тип | Обязательный | Описание |
|----------|-----|--------------|----------|
| `services` | string | Да | Имена сервисов через запятую |
| `token` | string | Да | JWT токен аутентификации |
| `tail` | number | Нет | Количество начальных строк (по умолчанию 500) |
| `project` | string | Нет | Имя проекта (для фильтрации) |
#### Пример подключения
```javascript
const services = "web,db,redis";
const token = "your-jwt-token";
const ws = new WebSocket(`ws://localhost:9001/ws/fan_group?services=${services}&token=${token}&tail=100`);
ws.onmessage = function(event) {
// Логи приходят с префиксом ID контейнера и имени сервиса
console.log('Логи группы сервисов:', event.data);
};
```
#### Формат сообщений
Логи приходят с префиксом ID контейнера и имени сервиса:
```
[abc123de web] 2024-01-15T10:30:15.123456789Z 2024/01/15 10:30:15 [notice] 1#1: start worker processes
[def456gh db] 2024-01-15T10:30:16.123456789Z 2024/01/15 10:30:16 [info] database system is ready to accept connections
[ghi789jk redis] 2024-01-15T10:30:17.123456789Z 2024/01/15 10:30:17 [notice] Ready to accept connections
```
## Примеры использования
### JavaScript (Browser)
```javascript
class LogBoardWebSocket {
constructor(baseUrl, token) {
this.baseUrl = baseUrl;
this.token = token;
this.connections = new Map();
}
// Подключение к логам контейнера
connectToContainer(containerId, options = {}) {
const { tail = 100, onMessage, onError, onClose } = options;
const url = `ws://${this.baseUrl.replace('http://', '')}/ws/logs/${containerId}?token=${this.token}&tail=${tail}`;
const ws = new WebSocket(url);
ws.onopen = () => {
console.log(`Подключение к контейнеру ${containerId} установлено`);
};
ws.onmessage = (event) => {
if (onMessage) onMessage(event.data);
};
ws.onerror = (error) => {
console.error(`Ошибка WebSocket для контейнера ${containerId}:`, error);
if (onError) onError(error);
};
ws.onclose = (event) => {
console.log(`Соединение с контейнером ${containerId} закрыто:`, event.code, event.reason);
if (onClose) onClose(event);
};
this.connections.set(containerId, ws);
return ws;
}
// Подключение к логам сервиса
connectToService(serviceName, options = {}) {
const { tail = 100, project, onMessage, onError, onClose } = options;
let url = `ws://${this.baseUrl.replace('http://', '')}/ws/fan/${serviceName}?token=${this.token}&tail=${tail}`;
if (project) url += `&project=${project}`;
const ws = new WebSocket(url);
ws.onopen = () => {
console.log(`Подключение к сервису ${serviceName} установлено`);
};
ws.onmessage = (event) => {
if (onMessage) onMessage(event.data);
};
ws.onerror = (error) => {
console.error(`Ошибка WebSocket для сервиса ${serviceName}:`, error);
if (onError) onError(error);
};
ws.onclose = (event) => {
console.log(`Соединение с сервисом ${serviceName} закрыто:`, event.code, event.reason);
if (onClose) onClose(event);
};
this.connections.set(serviceName, ws);
return ws;
}
// Подключение к группе сервисов
connectToServiceGroup(services, options = {}) {
const { tail = 100, project, onMessage, onError, onClose } = options;
let url = `ws://${this.baseUrl.replace('http://', '')}/ws/fan_group?services=${services}&token=${this.token}&tail=${tail}`;
if (project) url += `&project=${project}`;
const ws = new WebSocket(url);
ws.onopen = () => {
console.log(`Подключение к группе сервисов ${services} установлено`);
};
ws.onmessage = (event) => {
if (onMessage) onMessage(event.data);
};
ws.onerror = (error) => {
console.error(`Ошибка WebSocket для группы сервисов ${services}:`, error);
if (onError) onError(error);
};
ws.onclose = (event) => {
console.log(`Соединение с группой сервисов ${services} закрыто:`, event.code, event.reason);
if (onClose) onClose(event);
};
this.connections.set(`group:${services}`, ws);
return ws;
}
// Закрытие соединения
disconnect(identifier) {
const ws = this.connections.get(identifier);
if (ws) {
ws.close();
this.connections.delete(identifier);
}
}
// Закрытие всех соединений
disconnectAll() {
for (const [identifier, ws] of this.connections) {
ws.close();
}
this.connections.clear();
}
}
// Использование
async function main() {
// Получение токена
const loginResponse = await fetch('http://localhost:9001/api/auth/login', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
username: 'admin',
password: 'your-password'
})
});
const {access_token} = await loginResponse.json();
// Создание WebSocket клиента
const wsClient = new LogBoardWebSocket('http://localhost:9001', access_token);
// Подключение к контейнеру
wsClient.connectToContainer('abc123def456', {
tail: 100,
onMessage: (data) => {
console.log('Логи контейнера:', data);
// Добавление в UI
appendToLogView(data);
},
onError: (error) => {
console.error('Ошибка:', error);
}
});
// Подключение к сервису
wsClient.connectToService('web', {
tail: 100,
onMessage: (data) => {
console.log('Логи сервиса:', data);
// Парсинг префикса для группировки
const match = data.match(/^\[([a-f0-9]+)\]\s+(.+)$/);
if (match) {
const [, containerId, logMessage] = match;
appendToServiceLogView(containerId, logMessage);
}
}
});
// Подключение к группе сервисов
wsClient.connectToServiceGroup('web,db,redis', {
tail: 100,
onMessage: (data) => {
console.log('Логи группы:', data);
// Парсинг префикса для группировки
const match = data.match(/^\[([a-f0-9]+)\s+(\w+)\]\s+(.+)$/);
if (match) {
const [, containerId, serviceName, logMessage] = match;
appendToGroupLogView(serviceName, containerId, logMessage);
}
}
});
}
// Функции для обновления UI
function appendToLogView(message) {
const logContainer = document.getElementById('log-container');
const logLine = document.createElement('div');
logLine.className = 'log-line';
logLine.textContent = message;
logContainer.appendChild(logLine);
logContainer.scrollTop = logContainer.scrollHeight;
}
function appendToServiceLogView(containerId, message) {
// Реализация для отображения логов сервиса
}
function appendToGroupLogView(serviceName, containerId, message) {
// Реализация для отображения логов группы сервисов
}
main().catch(console.error);
```
### Python
```python
import asyncio
import websockets
import json
import requests
class LogBoardWebSocket:
def __init__(self, base_url, token):
self.base_url = base_url.replace('http://', 'ws://')
self.token = token
self.connections = {}
async def connect_to_container(self, container_id, tail=100, callback=None):
"""Подключение к логам контейнера"""
uri = f"{self.base_url}/ws/logs/{container_id}?token={self.token}&tail={tail}"
try:
async with websockets.connect(uri) as websocket:
print(f"Подключение к контейнеру {container_id} установлено")
async for message in websocket:
if callback:
await callback(container_id, message)
else:
print(f"[{container_id}] {message}")
except websockets.exceptions.ConnectionClosed:
print(f"Соединение с контейнером {container_id} закрыто")
except Exception as e:
print(f"Ошибка подключения к контейнеру {container_id}: {e}")
async def connect_to_service(self, service_name, tail=100, project=None, callback=None):
"""Подключение к логам сервиса"""
uri = f"{self.base_url}/ws/fan/{service_name}?token={self.token}&tail={tail}"
if project:
uri += f"&project={project}"
try:
async with websockets.connect(uri) as websocket:
print(f"Подключение к сервису {service_name} установлено")
async for message in websocket:
if callback:
await callback(service_name, message)
else:
print(f"[{service_name}] {message}")
except websockets.exceptions.ConnectionClosed:
print(f"Соединение с сервисом {service_name} закрыто")
except Exception as e:
print(f"Ошибка подключения к сервису {service_name}: {e}")
async def connect_to_service_group(self, services, tail=100, project=None, callback=None):
"""Подключение к логам группы сервисов"""
uri = f"{self.base_url}/ws/fan_group?services={services}&token={self.token}&tail={tail}"
if project:
uri += f"&project={project}"
try:
async with websockets.connect(uri) as websocket:
print(f"Подключение к группе сервисов {services} установлено")
async for message in websocket:
if callback:
await callback(services, message)
else:
print(f"[{services}] {message}")
except websockets.exceptions.ConnectionClosed:
print(f"Соединение с группой сервисов {services} закрыто")
except Exception as e:
print(f"Ошибка подключения к группе сервисов {services}: {e}")
# Функции обратного вызова
async def container_log_callback(container_id, message):
"""Обработка логов контейнера"""
print(f"[{container_id}] {message}")
async def service_log_callback(service_name, message):
"""Обработка логов сервиса"""
print(f"[{service_name}] {message}")
async def group_log_callback(services, message):
"""Обработка логов группы сервисов"""
print(f"[{services}] {message}")
async def main():
# Получение токена
response = requests.post('http://localhost:9001/api/auth/login', json={
'username': 'admin',
'password': 'your-password'
})
token = response.json()['access_token']
# Создание WebSocket клиента
ws_client = LogBoardWebSocket('http://localhost:9001', token)
# Создание задач для одновременного подключения
tasks = [
ws_client.connect_to_container('abc123def456', tail=100, callback=container_log_callback),
ws_client.connect_to_service('web', tail=100, callback=service_log_callback),
ws_client.connect_to_service_group('web,db,redis', tail=100, callback=group_log_callback)
]
# Запуск всех подключений одновременно
await asyncio.gather(*tasks)
if __name__ == "__main__":
asyncio.run(main())
```
### Node.js
```javascript
const WebSocket = require('ws');
const axios = require('axios');
class LogBoardWebSocket {
constructor(baseUrl, token) {
this.baseUrl = baseUrl.replace('http://', 'ws://');
this.token = token;
this.connections = new Map();
}
// Подключение к логам контейнера
connectToContainer(containerId, options = {}) {
const { tail = 100, onMessage, onError, onClose } = options;
const url = `${this.baseUrl}/ws/logs/${containerId}?token=${this.token}&tail=${tail}`;
const ws = new WebSocket(url);
ws.on('open', () => {
console.log(`Подключение к контейнеру ${containerId} установлено`);
});
ws.on('message', (data) => {
const message = data.toString();
if (onMessage) onMessage(message);
else console.log(`[${containerId}] ${message}`);
});
ws.on('error', (error) => {
console.error(`Ошибка WebSocket для контейнера ${containerId}:`, error);
if (onError) onError(error);
});
ws.on('close', (code, reason) => {
console.log(`Соединение с контейнером ${containerId} закрыто:`, code, reason);
if (onClose) onClose(code, reason);
});
this.connections.set(containerId, ws);
return ws;
}
// Подключение к логам сервиса
connectToService(serviceName, options = {}) {
const { tail = 100, project, onMessage, onError, onClose } = options;
let url = `${this.baseUrl}/ws/fan/${serviceName}?token=${this.token}&tail=${tail}`;
if (project) url += `&project=${project}`;
const ws = new WebSocket(url);
ws.on('open', () => {
console.log(`Подключение к сервису ${serviceName} установлено`);
});
ws.on('message', (data) => {
const message = data.toString();
if (onMessage) onMessage(message);
else console.log(`[${serviceName}] ${message}`);
});
ws.on('error', (error) => {
console.error(`Ошибка WebSocket для сервиса ${serviceName}:`, error);
if (onError) onError(error);
});
ws.on('close', (code, reason) => {
console.log(`Соединение с сервисом ${serviceName} закрыто:`, code, reason);
if (onClose) onClose(code, reason);
});
this.connections.set(serviceName, ws);
return ws;
}
// Закрытие соединения
disconnect(identifier) {
const ws = this.connections.get(identifier);
if (ws) {
ws.close();
this.connections.delete(identifier);
}
}
// Закрытие всех соединений
disconnectAll() {
for (const [identifier, ws] of this.connections) {
ws.close();
}
this.connections.clear();
}
}
async function main() {
try {
// Получение токена
const loginResponse = await axios.post('http://localhost:9001/api/auth/login', {
username: 'admin',
password: 'your-password'
});
const token = loginResponse.data.access_token;
// Создание WebSocket клиента
const wsClient = new LogBoardWebSocket('http://localhost:9001', token);
// Подключение к контейнеру
wsClient.connectToContainer('abc123def456', {
tail: 100,
onMessage: (message) => {
console.log('Логи контейнера:', message);
}
});
// Подключение к сервису
wsClient.connectToService('web', {
tail: 100,
onMessage: (message) => {
console.log('Логи сервиса:', message);
}
});
// Обработка сигналов завершения
process.on('SIGINT', () => {
console.log('Закрытие соединений...');
wsClient.disconnectAll();
process.exit(0);
});
} catch (error) {
console.error('Ошибка:', error.message);
}
}
main();
```
## Обработка ошибок
### Коды ошибок WebSocket
| Код | Описание |
|-----|----------|
| 1000 | Нормальное закрытие |
| 1001 | Удаленная сторона закрыла соединение |
| 1002 | Ошибка протокола |
| 1003 | Неподдерживаемый тип данных |
| 1006 | Аномальное закрытие |
| 1011 | Внутренняя ошибка сервера |
### Обработка ошибок аутентификации
```javascript
ws.onmessage = function(event) {
if (event.data.startsWith('ERROR:')) {
const error = event.data.substring(6);
console.error('Ошибка аутентификации:', error);
if (error.includes('token required') || error.includes('invalid token')) {
// Переподключение с новым токеном
reconnectWithNewToken();
}
} else {
// Обработка обычных логов
console.log('Логи:', event.data);
}
};
```
### Автоматическое переподключение
```javascript
class ReconnectingWebSocket {
constructor(url, options = {}) {
this.url = url;
this.options = options;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = options.maxReconnectAttempts || 5;
this.reconnectInterval = options.reconnectInterval || 1000;
this.connect();
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log('WebSocket соединение установлено');
this.reconnectAttempts = 0;
};
this.ws.onclose = (event) => {
console.log('WebSocket соединение закрыто:', event.code, event.reason);
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
console.log(`Попытка переподключения ${this.reconnectAttempts}/${this.maxReconnectAttempts}`);
setTimeout(() => {
this.connect();
}, this.reconnectInterval * this.reconnectAttempts);
} else {
console.error('Превышено максимальное количество попыток переподключения');
}
};
this.ws.onerror = (error) => {
console.error('WebSocket ошибка:', error);
};
}
send(data) {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(data);
}
}
close() {
this.ws.close();
}
}
```
## Лучшие практики
### 1. Управление соединениями
```javascript
// Создание пула соединений
class WebSocketPool {
constructor() {
this.connections = new Map();
this.maxConnections = 10;
}
connect(identifier, url, options = {}) {
if (this.connections.size >= this.maxConnections) {
console.warn('Достигнут лимит соединений');
return null;
}
const ws = new WebSocket(url);
this.connections.set(identifier, ws);
ws.onclose = () => {
this.connections.delete(identifier);
};
return ws;
}
disconnect(identifier) {
const ws = this.connections.get(identifier);
if (ws) {
ws.close();
this.connections.delete(identifier);
}
}
}
```
### 2. Обработка больших объемов данных
```javascript
// Буферизация сообщений
class LogBuffer {
constructor(maxSize = 1000) {
this.buffer = [];
this.maxSize = maxSize;
}
add(message) {
this.buffer.push(message);
if (this.buffer.length > this.maxSize) {
this.buffer.shift(); // Удаляем старые сообщения
}
}
getMessages() {
return [...this.buffer];
}
clear() {
this.buffer = [];
}
}
// Использование
const logBuffer = new LogBuffer(1000);
ws.onmessage = function(event) {
logBuffer.add(event.data);
// Обновление UI каждые 100ms
if (!updateScheduled) {
updateScheduled = setTimeout(() => {
updateUI(logBuffer.getMessages());
updateScheduled = null;
}, 100);
}
};
```
### 3. Фильтрация логов
```javascript
// Фильтр по уровню логирования
class LogFilter {
constructor() {
this.filters = {
level: 'all', // debug, info, warn, error, all
service: null,
container: null,
text: null
};
}
setFilter(type, value) {
this.filters[type] = value;
}
shouldDisplay(message) {
// Фильтр по уровню
if (this.filters.level !== 'all') {
const levelMatch = message.toLowerCase().includes(`level=${this.filters.level}`);
if (!levelMatch) return false;
}
// Фильтр по сервису
if (this.filters.service) {
const serviceMatch = message.includes(`[${this.filters.service}]`);
if (!serviceMatch) return false;
}
// Фильтр по контейнеру
if (this.filters.container) {
const containerMatch = message.includes(`[${this.filters.container}]`);
if (!containerMatch) return false;
}
// Фильтр по тексту
if (this.filters.text) {
const textMatch = message.toLowerCase().includes(this.filters.text.toLowerCase());
if (!textMatch) return false;
}
return true;
}
}
// Использование
const logFilter = new LogFilter();
logFilter.setFilter('level', 'error');
ws.onmessage = function(event) {
if (logFilter.shouldDisplay(event.data)) {
console.log('Отфильтрованные логи:', event.data);
}
};
```
### 4. Мониторинг состояния соединений
```javascript
class ConnectionMonitor {
constructor() {
this.stats = {
totalMessages: 0,
totalBytes: 0,
connectionTime: null,
lastMessageTime: null,
errors: 0
};
}
onConnect() {
this.stats.connectionTime = new Date();
this.stats.totalMessages = 0;
this.stats.totalBytes = 0;
this.stats.errors = 0;
}
onMessage(message) {
this.stats.totalMessages++;
this.stats.totalBytes += message.length;
this.stats.lastMessageTime = new Date();
}
onError() {
this.stats.errors++;
}
getStats() {
return {
...this.stats,
uptime: this.stats.connectionTime ?
Date.now() - this.stats.connectionTime.getTime() : 0,
messagesPerSecond: this.stats.totalMessages /
(this.stats.uptime / 1000) || 0
};
}
}
```
## Ограничения и рекомендации
### Ограничения
- **Максимальное количество соединений:** 100 одновременных WebSocket соединений
- **Таймаут соединения:** 60 секунд бездействия
- **Размер сообщения:** до 1 MB на сообщение
- **Частота сообщений:** до 1000 сообщений в секунду
### Рекомендации
1. **Используйте переподключение** при разрыве соединений
2. **Ограничивайте количество соединений** для одного клиента
3. **Фильтруйте логи** на стороне клиента для снижения нагрузки
4. **Мониторьте состояние соединений** для диагностики проблем
5. **Используйте буферизацию** для больших объемов данных

View File

@ -783,34 +783,9 @@ a{color:var(--link)}
justify-content: center;
}
/* Help button: visible only when sidebar is collapsed */
.sidebar:not(.collapsed) .help-btn { display: none; }
.sidebar.collapsed .help-btn { display: flex; }
/* При свернутом сайдбаре фиксируем help-btn снизу по центру */
.sidebar.collapsed { position: relative; }
.sidebar.collapsed .help-btn {
position: absolute;
left: 50%;
bottom: 10px;
transform: translateX(-50%);
z-index: 2;
}
/* Компактные контролы в header: по умолчанию скрыты */
.header-compact-controls { display: none; align-items: center; gap: 6px; }
.help-btn:hover,
.logout-btn:hover {
background: var(--tab-active);
color: var(--fg);
border-color: var(--accent);
}
/* Tooltip для кнопки помощи */
.help-tooltip {
position: absolute;
@ -929,6 +904,28 @@ a{color:var(--link)}
text-decoration: underline;
}
.help-btn:hover {
background: #a78bfa;
color: #0b0d12;
border-color: #a78bfa;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(187, 154, 247, 0.5);
}
.logout-btn:hover {
background: var(--tab-active);
color: var(--fg);
border-color: var(--accent);
}
/* Специальный hover эффект для кнопки options с цветом accent */
.options-btn:hover {
background: var(--accent) !important; /* Цвет логотипа */
@ -1367,6 +1364,39 @@ a{color:var(--link)}
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
/* Стили для кнопки помощи в header */
.help-btn {
background: var(--accent);
color: #0b0d12;
border: 1px solid var(--accent);
border-radius: 6px;
padding: 6px 8px;
font-size: 11px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
min-width: 32px;
text-align: center;
box-shadow: 0 2px 6px rgba(187, 154, 247, 0.3);
}
.help-btn i {
font-size: 12px;
animation: help-pulse 2s ease-in-out infinite;
}
@keyframes help-pulse {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
}
/* Стили для счетчиков-кнопок */
.counter-btn {
display: inline-flex;
@ -2514,42 +2544,6 @@ footer{position:fixed;right:10px;bottom:10px;opacity:.6;font-size:11px}
<button class="options-btn" id="optionsBtn" title="Показать/скрыть настройки">
<i class="fas fa-cog"></i>
</button>
<button class="help-btn" id="helpBtn" title="Горячие клавиши">
<i class="fas fa-question-circle"></i>
</button>
<!-- Tooltip для кнопки помощи -->
<div class="help-tooltip" id="helpTooltip">
<div class="help-tooltip-header">
<div class="help-tooltip-logo">LB</div>
<div class="help-tooltip-title">LogBoard+</div>
</div>
<div class="help-tooltip-section">
<div class="help-tooltip-section-title">О проекте</div>
<div class="help-tooltip-content">
Веб-интерфейс для мониторинга логов Docker контейнеров в реальном времени с поддержкой мультивыбора и автоматического обновления.
</div>
</div>
<div class="help-tooltip-section">
<div class="help-tooltip-section-title">Горячие клавиши</div>
<div class="help-tooltip-hotkeys">
<div class="help-tooltip-hotkey">[</div>
<div class="help-tooltip-description">Предыдущий контейнер</div>
<div class="help-tooltip-hotkey">]</div>
<div class="help-tooltip-description">Следующий контейнер</div>
<div class="help-tooltip-hotkey">Ctrl+R</div>
<div class="help-tooltip-description">Обновить логи</div>
<div class="help-tooltip-hotkey">Ctrl+B</div>
<div class="help-tooltip-description">Свернуть sidebar</div>
</div>
</div>
<div class="help-tooltip-author">
Автор: <a href="https://devops.org.ru" target="_blank">Сергей Антропов</a>
</div>
</div>
<button class="logout-btn" id="logoutBtn" title="Выйти">
<i class="fas fa-sign-out-alt"></i>
</button>
@ -2732,6 +2726,46 @@ footer{position:fixed;right:10px;bottom:10px;opacity:.6;font-size:11px}
<button class="btn btn-small log-refresh-btn" title="Обновить логи и счетчики">
<i class="fas fa-sync-alt"></i> Refresh
</button>
<button class="help-btn" id="helpBtn" title="Справка">
<i class="fas fa-question-circle"></i>
</button>
<!-- Tooltip для кнопки помощи -->
<div class="help-tooltip" id="helpTooltip">
<div class="help-tooltip-header">
<div class="help-tooltip-logo">LB</div>
<div class="help-tooltip-title">LogBoard+</div>
</div>
<div class="help-tooltip-section">
<div class="help-tooltip-section-title">О проекте</div>
<div class="help-tooltip-content">
Веб-интерфейс для мониторинга логов Docker контейнеров в реальном времени с поддержкой мультивыбора и автоматического обновления.
</div>
</div>
<div class="help-tooltip-section">
<div class="help-tooltip-section-title">Горячие клавиши</div>
<div class="help-tooltip-hotkeys">
<div class="help-tooltip-hotkey">[</div>
<div class="help-tooltip-description">Предыдущий контейнер</div>
<div class="help-tooltip-hotkey">]</div>
<div class="help-tooltip-description">Следующий контейнер</div>
<div class="help-tooltip-hotkey">Ctrl+R</div>
<div class="help-tooltip-description">Обновить логи</div>
<div class="help-tooltip-hotkey">Ctrl+B</div>
<div class="help-tooltip-description">Свернуть sidebar</div>
<div class="help-tooltip-hotkey">Ctrl+F</div>
<div class="help-tooltip-description">Фокус на фильтр</div>
<div class="help-tooltip-hotkey">Ctrl+Shift+M</div>
<div class="help-tooltip-description">Мультивыбор контейнеров</div>
</div>
</div>
<div class="help-tooltip-author">
Автор: <a href="https://devops.org.ru" target="_blank">Сергей Антропов</a>
</div>
</div>
</div>
</div>
@ -4023,6 +4057,8 @@ function positionTooltip(event, tooltip) {
}, 10);
}
// Функции для help tooltip
function showHelpTooltip() {
const helpBtn = document.getElementById('helpBtn');
@ -6940,10 +6976,7 @@ if (els.sidebarToggle) {
els.sidebarToggle.onclick = toggleSidebar;
}
// Help button
if (els.helpBtn) {
els.helpBtn.onclick = toggleHotkeysModal;
}
// Modal close button
if (els.hotkeysModalClose) {
@ -7090,6 +7123,22 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
// Инициализируем стили логов при загрузке страницы
updateLogStyles();
// Применяем настройки wrap text при загрузке
applyWrapSettings();
// Дополнительная проверка для multi-view логов при загрузке
setTimeout(() => {
if (state.multiViewMode) {
console.log('Initialization: Force fixing multi-view styles');
forceFixMultiViewStyles();
}
}, 1000);
// Обработчик для кнопки помощи
if (els.helpBtn) {
const helpTooltip = document.getElementById('helpTooltip');
@ -7125,20 +7174,6 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
// Инициализируем стили логов при загрузке страницы
updateLogStyles();
// Применяем настройки wrap text при загрузке
applyWrapSettings();
// Дополнительная проверка для multi-view логов при загрузке
setTimeout(() => {
if (state.multiViewMode) {
console.log('Initialization: Force fixing multi-view styles');
forceFixMultiViewStyles();
}
}, 1000);
});
if (els.snapshotBtn) {
els.snapshotBtn.onclick = ()=>{