# 🌐 Удаленные клиенты LogBoard+

## Обзор

LogBoard+ поддерживает сбор логов с удаленных серверов через специальные клиенты. Это позволяет централизованно мониторить логи контейнеров с нескольких машин.

## Архитектура

```
┌─────────────────┐    HTTP API    ┌─────────────────┐
│   Удаленный     │ ──────────────► │   LogBoard+     │
│   сервер        │                │   Сервер        │
│                 │                │                 │
│ ┌─────────────┐ │                │ ┌─────────────┐ │
│ │   Клиент    │ │                │ │   Web UI    │ │
│ │ LogBoard+   │ │                │ │             │ │
│ └─────────────┘ │                │ └─────────────┘ │
└─────────────────┘                └─────────────────┘
```

## Компоненты

### 1. Серверная часть (LogBoard+ Server)

- **API эндпоинты**: Прием логов от удаленных клиентов
- **Хранение логов**: Сохранение в файловой системе
- **Web интерфейс**: Отображение локальных и удаленных контейнеров
- **Визуальное разделение**: Четкое различие между локальными и удаленными контейнерами

### 2. Клиентская часть (LogBoard+ Client)

- **Сбор логов**: Чтение логов Docker контейнеров
- **Отправка данных**: HTTP POST запросы на сервер
- **Автоматизация**: Docker Compose для простого развертывания

## Установка и настройка

### На сервере LogBoard+

1. Убедитесь, что сервер LogBoard+ запущен и доступен
2. Получите API ключ для аутентификации клиентов

### На удаленном сервере

1. Создайте директорию для клиента:
```bash
mkdir logboard-client
cd logboard-client
```

2. Создайте `docker-compose.yml`:
```yaml
version: '3.8'

services:
  logboard-client:
    build: .
    container_name: logboard-client
    environment:
      - LOGBOARD_SERVER_URL=http://your-logboard-server:9001
      - API_KEY=your-api-key
      - HOSTNAME=your-server-name
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./logs:/app/logs
    restart: unless-stopped
    user: 0:0
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  # Тестовый контейнер для демонстрации
  test-nginx:
    image: nginx:alpine
    container_name: test-nginx
    ports:
      - "8080:80"
    restart: unless-stopped
```

3. Создайте `Dockerfile`:
```dockerfile
FROM python:3.11-slim

RUN apt-get update && apt-get install -y \
    curl \
    && rm -rf /var/lib/apt/lists/*

RUN groupadd -r logboard && useradd -r -g logboard logboard

RUN mkdir -p /app /var/log

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY app/ ./app/

RUN mkdir -p /var/log && \
    chown -R logboard:logboard /app /var/log

USER logboard

CMD ["python", "app/main.py"]
```

4. Создайте `requirements.txt`:
```
aiohttp==3.9.1
docker==6.1.3
urllib3==2.1.0
requests==2.31.0
```

5. Создайте `app/main.py`:
```python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
LogBoard+ Client - Клиент для отправки логов на сервер LogBoard+
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""

import asyncio
import aiohttp
import docker
import os
import logging
from datetime import datetime

# Настройка логирования
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

class LogBoardClient:
    def __init__(self):
        self.server_url = os.getenv('LOGBOARD_SERVER_URL', 'http://localhost:9001')
        self.api_key = os.getenv('API_KEY', 'default-key')
        self.hostname = os.getenv('HOSTNAME', 'unknown')
        
        # Инициализация Docker клиента
        try:
            self.docker_client = docker.from_env()
            logger.info("Docker клиент инициализирован успешно")
        except Exception as e:
            logger.error(f"Ошибка инициализации Docker клиента: {e}")
            self.docker_client = None
    
    async def send_logs(self, container_name, logs_data):
        """Отправка логов на сервер LogBoard+"""
        try:
            async with aiohttp.ClientSession() as session:
                url = f"{self.server_url}/api/logs/remote"
                data = {
                    'container_name': container_name,
                    'hostname': self.hostname,
                    'logs': logs_data,
                    'api_key': self.api_key
                }
                
                async with session.post(url, json=data) as response:
                    if response.status == 200:
                        logger.info(f"Логи контейнера {container_name} отправлены успешно")
                    else:
                        logger.error(f"Ошибка отправки логов: {response.status}")
                        
        except Exception as e:
            logger.error(f"Ошибка при отправке логов: {e}")
    
    def get_container_logs(self, container_name, tail=100):
        """Получение логов контейнера"""
        try:
            if not self.docker_client:
                return []
            
            container = self.docker_client.containers.get(container_name)
            logs = container.logs(tail=tail, timestamps=True).decode('utf-8')
            return logs.split('\n')[:-1]  # Убираем пустую строку в конце
            
        except Exception as e:
            logger.error(f"Ошибка получения логов контейнера {container_name}: {e}")
            return []
    
    async def collect_and_send_logs(self):
        """Сбор и отправка логов всех контейнеров"""
        try:
            if not self.docker_client:
                logger.error("Docker клиент недоступен")
                return
            
            containers = self.docker_client.containers.list()
            logger.info(f"Найдено {len(containers)} контейнеров")
            
            for container in containers:
                try:
                    logs = self.get_container_logs(container.name)
                    if logs:
                        await self.send_logs(container.name, logs)
                except Exception as e:
                    logger.error(f"Ошибка обработки контейнера {container.name}: {e}")
                    
        except Exception as e:
            logger.error(f"Ошибка сбора логов: {e}")
    
    async def run(self):
        """Основной цикл работы клиента"""
        logger.info(f"LogBoard+ Client запущен для хоста: {self.hostname}")
        logger.info(f"Сервер: {self.server_url}")
        
        while True:
            try:
                await self.collect_and_send_logs()
                await asyncio.sleep(30)  # Пауза 30 секунд
            except Exception as e:
                logger.error(f"Критическая ошибка: {e}")
                await asyncio.sleep(60)  # Увеличенная пауза при ошибке

async def main():
    client = LogBoardClient()
    await client.run()

if __name__ == "__main__":
    asyncio.run(main())
```

6. Запустите клиент:
```bash
docker-compose up -d
```

## Использование

### Web интерфейс

После настройки клиентов, в веб-интерфейсе LogBoard+ вы увидите:

#### 📍 Локальные контейнеры
- Контейнеры с текущего сервера
- Обычное отображение без дополнительных индикаторов

#### 🌐 Удаленные контейнеры
- Контейнеры с удаленных серверов
- Визуальное разделение по хостам
- Индикаторы удаленного доступа (глобус 🌐)
- Время последнего обновления
- Статистика по хостам

### Функции интерфейса

#### 🔽 Сворачивание секций
- **Секции контейнеров**: Можно сворачивать/разворачивать секции "Локальные контейнеры" и "Удаленные контейнеры"
- **Секции хостов**: Каждый хост в удаленных контейнерах можно сворачивать отдельно
- **Кнопки управления**: Стрелки для сворачивания/разворачивания
- **Сохранение состояния**: Состояние сворачивания сохраняется между сессиями

#### ⚡ Периодическое обновление
- **Автоматическое обновление**: Список контейнеров обновляется каждые 30 секунд
- **Фильтрация остановленных**: Остановленные контейнеры автоматически скрываются из интерфейса
- **Обновление счетчиков**: Количество контейнеров в секциях обновляется в реальном времени
- **Логирование изменений**: В консоли браузера отображается информация об изменениях

#### 📱 Адаптивный интерфейс
- **Свернутый sidebar**: Миникарточки контейнеров с иконками статуса
- **Развернутый sidebar**: Полная информация о контейнерах с возможностью сворачивания секций
- **Мобильная поддержка**: Адаптивный дизайн для мобильных устройств

### API эндпоинты

#### Получение контейнеров
```http
GET /api/containers/services
Authorization: Bearer <token>
```

**Ответ:**
```json
[
  {
    "id": "container-id",
    "name": "container-name",
    "status": "running",
    "is_remote": false,
    "hostname": "localhost",
    "project": "project-name",
    "service": "service-name"
  },
  {
    "id": "remote-hostname-container",
    "name": "remote-container",
    "status": "running",
    "is_remote": true,
    "hostname": "remote-host",
    "last_modified": "2025-08-20T16:30:00",
    "size": 1024
  }
]
```

#### Получение логов удаленного контейнера
```http
GET /api/logs/{container_id}
Authorization: Bearer <token>
```

#### Получение статистики логов
```http
GET /api/logs/stats/{container_id}
Authorization: Bearer <token>
```

## Мониторинг и отладка

### Логи клиента
```bash
docker-compose logs -f logboard-client
```

### Проверка здоровья
```bash
curl http://localhost:8080/health
```

### Тестирование API
```bash
python3 test_interface.py
```

## Безопасность

- **API ключи**: Обязательная аутентификация клиентов
- **HTTPS**: Рекомендуется использовать HTTPS для передачи данных
- **Сетевая изоляция**: Клиенты должны иметь доступ только к необходимым портам

## Устранение неполадок

### Клиент не подключается
1. Проверьте URL сервера в переменной `LOGBOARD_SERVER_URL`
2. Убедитесь, что API ключ правильный
3. Проверьте сетевое подключение

### Логи не отображаются
1. Проверьте права доступа к Docker socket
2. Убедитесь, что контейнеры запущены
3. Проверьте логи клиента

### Интерфейс не обновляется
1. Откройте консоль браузера (F12)
2. Проверьте наличие ошибок JavaScript
3. Убедитесь, что WebSocket соединения работают

## Автор

**Сергей Антропов**  
🌐 Сайт: https://devops.org.ru  
📧 Email: contact@devops.org.ru

---

*Документация обновлена: 2025-08-20*

