# 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 контейнеров. API разработан для интеграции с системами мониторинга и автоматизации, а также для создания собственных клиентов.

### Применение API

- **Интеграция с CI/CD** - автоматический мониторинг логов в пайплайнах
- **Собственные дашборды** - создание кастомных интерфейсов мониторинга
- **Автоматизация** - скрипты для анализа логов и алертинга
- **Локальная разработка** - интеграция с IDE и инструментами разработки

### Базовый 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/containers/services

Получение списка всех контейнеров.

**Параметры запроса:**

| Параметр | Тип | Описание | По умолчанию |
|----------|-----|----------|--------------|
| `projects` | string | Фильтр по проектам (через запятую) | Все проекты |
| `include_stopped` | boolean | Включить остановленные контейнеры | false |

**Пример запроса:**

```bash
curl -X GET "http://localhost:9001/api/containers/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/containers/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/containers/excluded

Получение списка исключенных контейнеров.

**Ответ:**

```json
{
  "excluded_containers": [
    "noisy-container-1",
    "noisy-container-2"
  ]
}
```

#### POST /api/containers/excluded

Обновление списка исключенных контейнеров.

**Запрос:**

```json
[
  "noisy-container-1",
  "noisy-container-2",
  "another-noisy-container"
]
```

**Ответ:**

```json
{
  "status": "success",
  "message": "Список исключенных контейнеров обновлен"
}
```

#### POST /api/logs/snapshot

Создание снимка логов.

**Запрос:**

```json
{
  "container_id": "abc123def456",
  "service": "web",
  "content": "Логи контейнера..."
}
```

**Ответ:**

```json
{
  "file": "web-20240115-103015.log",
  "url": "/snapshots/web-20240115-103015.log"
}
```

#### GET /api/websocket/status

Получение статуса WebSocket соединений.

**Заголовки:**
```
Authorization: Bearer <token>
```

**Ответ:**

```json
{
  "status": "available",
  "message": "WebSocket соединения доступны",
  "containers_count": 5,
  "timestamp": "2024-01-15T10:30:15.123456"
}
```

**Возможные статусы:**
- `available` - WebSocket соединения доступны, есть активные контейнеры
- `no_containers` - Нет доступных контейнеров для подключения
- `error` - Ошибка проверки статуса

**Примечание:** Когда сервер возвращает статус `available`, клиент всегда показывает `ws: on`, независимо от наличия активных клиентских соединений.

### Системные

#### GET /healthz

Health check endpoint.

**Ответ:**

```
ok
```

## WebSocket API

### Подключение

Все WebSocket endpoints требуют JWT токен в параметре `token`.

### ws://host:port/api/websocket/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/api/websocket/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/api/websocket/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/api/websocket/fan/${serviceName}?token=${token}&tail=100`);

ws.onmessage = function(event) {
  console.log('Логи сервиса:', event.data);
};
```

### ws://host:port/api/websocket/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/api/websocket/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/containers/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/containers/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://', '')}/api/websocket/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/containers/services" \
  -H "Authorization: Bearer $TOKEN" \
  | jq '.[] | {name: .name, status: .status, project: .project}'

# Получение логов первого контейнера
CONTAINER_ID=$(curl -s -X GET "$BASE_URL/api/containers/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 на запрос
