diff --git a/AJAX_UPDATE_ENHANCEMENTS.md b/AJAX_UPDATE_ENHANCEMENTS.md
deleted file mode 100644
index d3eec34..0000000
--- a/AJAX_UPDATE_ENHANCEMENTS.md
+++ /dev/null
@@ -1,217 +0,0 @@
-# Улучшения AJAX Auto-update
-
-## Описание изменений
-
-Реализованы значительные улучшения системы AJAX auto-update для LogBoard+.
-
-## Основные изменения
-
-### 1. AJAX autoupdate по умолчанию включен
-
-**Изменение:** Значение `ajaxUpdateEnabled` изменено с `false` на `true`
-
-**Файл:** `templates/index.html`
-```javascript
-// Было:
-let ajaxUpdateEnabled = false;
-
-// Стало:
-let ajaxUpdateEnabled = true; // По умолчанию включен
-```
-
-**Преимущества:**
-- Пользователи сразу получают автоматическое обновление логов
-- Не нужно вручную включать функцию при каждом запуске
-- Улучшенный пользовательский опыт
-
-### 2. Добавлена кнопка Update
-
-**Новая функциональность:** Кнопка update в header для управления AJAX autoupdate
-
-**Расположение:** Справа от кнопки WebSocket состояния
-
-**Визуальные состояния:**
-- **Зеленая** - AJAX autoupdate включен
-- **Красная** - AJAX autoupdate выключен
-
-**Функциональность:**
-- Клик по кнопке переключает состояние AJAX autoupdate
-- Автоматическое обновление цвета при изменении состояния
-- Интуитивно понятное управление
-
-### 3. Улучшенное управление кнопками
-
-**Логика работы:**
-- **AJAX autoupdate включен** → Кнопка refresh скрыта, кнопка update зеленая
-- **AJAX autoupdate выключен** → Кнопка refresh показана, кнопка update красная
-
-## Техническая реализация
-
-### CSS стили
-
-```css
-/* Кнопка состояния AJAX Update */
-.ajax-update-btn {
- background: var(--chip);
- color: var(--muted);
- border: 1px solid var(--border);
- border-radius: 6px;
- padding: 6px 12px;
- font-size: 11px;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.3s ease;
- font-family: inherit;
- min-width: 60px;
- text-align: center;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
-}
-
-.ajax-update-btn.ajax-on {
- background: #7ea855; /* Зеленый цвет */
- color: white;
- border-color: #7ea855;
-}
-
-.ajax-update-btn.ajax-off {
- background: #f7768e; /* Красный цвет */
- color: white;
- border-color: #f7768e;
-}
-
-.ajax-update-btn:hover {
- opacity: 0.8;
- transform: translateY(-1px);
- box-shadow: 0 2px 4px rgba(0,0,0,0.2);
-}
-```
-
-### HTML структура
-
-```html
-
-
-
-```
-
-### JavaScript функции
-
-#### setAjaxUpdateState(enabled)
-```javascript
-function setAjaxUpdateState(enabled) {
- if (els.ajaxUpdateBtn) {
- // Удаляем все классы состояний
- els.ajaxUpdateBtn.classList.remove('ajax-on', 'ajax-off');
-
- // Добавляем соответствующий класс
- if (enabled) {
- els.ajaxUpdateBtn.classList.add('ajax-on');
- els.ajaxUpdateBtn.textContent = 'update';
- } else {
- els.ajaxUpdateBtn.classList.add('ajax-off');
- els.ajaxUpdateBtn.textContent = 'update';
- }
- }
-}
-```
-
-#### Обновленная updateRefreshButtonVisibility()
-```javascript
-function updateRefreshButtonVisibility() {
- const refreshButtons = document.querySelectorAll('.log-refresh-btn');
- refreshButtons.forEach(btn => {
- if (ajaxUpdateEnabled) {
- // Если ajax autoupdate включен, скрываем кнопку refresh
- btn.style.display = 'none';
- } else {
- // Если ajax autoupdate выключен, показываем кнопку refresh
- btn.style.display = 'inline-flex';
- }
- });
-
- // Обновляем состояние кнопки update
- setAjaxUpdateState(ajaxUpdateEnabled);
-}
-```
-
-#### Обработчик клика
-```javascript
-// Обработчик для кнопки update (AJAX autoupdate toggle)
-if (els.ajaxUpdateBtn) {
- els.ajaxUpdateBtn.addEventListener('click', () => {
- toggleAjaxLogUpdate();
- });
-}
-```
-
-## Интеграция с существующим кодом
-
-### Обновленные функции
-1. **enableAjaxLogUpdate()** - обновляет состояние кнопки update
-2. **disableAjaxLogUpdate()** - обновляет состояние кнопки update
-3. **toggleAjaxLogUpdate()** - обновляет состояние кнопки update
-4. **initAjaxUpdateCheckbox()** - устанавливает правильное начальное состояние
-5. **initAjaxUpdate()** - инициализирует состояние кнопки update
-
-### Автоматическое обновление состояния
-Состояние кнопки update автоматически обновляется в следующих случаях:
-- При инициализации AJAX update
-- При изменении состояния чекбокса "Auto-update logs"
-- При программном включении/выключении AJAX update
-- При переключении состояния через функцию toggleAjaxLogUpdate
-- При клике по кнопке update
-
-## Преимущества реализации
-
-### 1. Улучшенный UX
-- Интуитивно понятное управление AJAX autoupdate
-- Визуальная обратная связь о состоянии системы
-- Быстрый доступ к управлению через кнопку в header
-
-### 2. Логическая связность
-- Кнопка refresh скрыта, когда она не нужна
-- Кнопка update показывает актуальное состояние
-- Единообразное поведение всех элементов управления
-
-### 3. Автоматическое управление
-- Не требует ручного вмешательства пользователя
-- Состояние синхронизировано между всеми элементами
-- Корректная работа при всех сценариях использования
-
-### 4. Совместимость
-- Работает с существующим кодом без нарушений
-- Поддерживает все режимы просмотра (single-view, multi-view)
-- Совместимо с фильтрацией и настройками уровней логирования
-
-## Тестирование
-
-### Сценарии тестирования
-
-1. **Начальное состояние**
- - При загрузке страницы AJAX autoupdate должен быть включен
- - Кнопка update должна быть зеленой
- - Кнопка refresh должна быть скрыта
-
-2. **Переключение через кнопку update**
- - Клик по кнопке update должен переключить состояние
- - Цвет кнопки должен измениться (зеленый ↔ красный)
- - Видимость кнопки refresh должна измениться
-
-3. **Переключение через чекбокс**
- - Изменение состояния чекбокса "Auto-update logs" должно обновить кнопку update
- - Состояние должно синхронизироваться между всеми элементами
-
-4. **Программное управление**
- - Вызов функций enable/disable должен обновить UI
- - Состояние должно корректно отображаться
-
-## Автор
-
-Сергей Антропов
-Сайт: https://devops.org.ru
-
-## Дата реализации
-
-2024 год
diff --git a/BORDER_STYLING_UPDATE.md b/BORDER_STYLING_UPDATE.md
deleted file mode 100644
index b098ecf..0000000
--- a/BORDER_STYLING_UPDATE.md
+++ /dev/null
@@ -1,84 +0,0 @@
-# Добавление Border и Box-shadow к кнопке Refresh
-
-## Описание изменений
-
-Добавлены border и box-shadow к кнопке refresh в header для полного соответствия стилям кнопки update.
-
-## Изменения стилей
-
-### До изменений:
-```css
-.log-refresh-btn {
- border: none; /* Без границы */
- /* нет box-shadow */
-}
-```
-
-### После изменений:
-```css
-.log-refresh-btn {
- border: 1px solid var(--accent); /* Граница как у update */
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); /* Тень как у update */
-}
-```
-
-## Сравнение кнопок
-
-### Кнопка Update:
-```css
-.ajax-update-btn {
- border: 1px solid var(--border);
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
-}
-```
-
-### Кнопка Refresh (после изменений):
-```css
-.log-refresh-btn {
- border: 1px solid var(--accent); /* ✅ Совпадает по стилю */
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); /* ✅ Совпадает */
-}
-```
-
-## Визуальные улучшения
-
-### Полное единообразие:
-- **Одинаковая граница** - 1px solid для обеих кнопок
-- **Одинаковая тень** - box-shadow для обеих кнопок
-- **Одинаковая высота** - обе кнопки имеют одинаковую высоту
-- **Одинаковая ширина** - минимальная ширина 60px для обеих кнопок
-- **Одинаковый размер шрифта** - 11px для обеих кнопок
-- **Одинаковые отступы** - 6px 12px для обеих кнопок
-
-### Преимущества:
-1. **Визуальная согласованность** - кнопки выглядят как единый набор элементов управления
-2. **Профессиональный вид** - границы и тени придают кнопкам более современный вид
-3. **Улучшенная читаемость** - тени помогают выделить кнопки на фоне
-
-## Технические детали
-
-### Добавленные CSS свойства:
-- `border: 1px solid var(--accent)` - граница в цвет акцента
-- `box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1)` - легкая тень
-
-### Особенности реализации:
-- **Цвет границы** - используется `var(--accent)` вместо `var(--border)` для лучшего соответствия цвету кнопки
-- **Тень** - идентична тени кнопки update для полного соответствия
-- **Совместимость** - работает в обеих темах (светлая/темная)
-
-## Совместимость
-
-Изменения стилей не влияют на:
-- Функциональность кнопки
-- JavaScript обработчики
-- Логику показа/скрытия
-- Поведение в разных темах (светлая/темная)
-
-## Автор
-
-Сергей Антропов
-Сайт: https://devops.org.ru
-
-## Дата реализации
-
-2024 год
diff --git a/BUTTON_REORDERING.md b/BUTTON_REORDERING.md
deleted file mode 100644
index 580ccf8..0000000
--- a/BUTTON_REORDERING.md
+++ /dev/null
@@ -1,96 +0,0 @@
-# Изменение порядка кнопок в Header
-
-## Описание изменений
-
-Изменен порядок кнопок в header для улучшения логической группировки элементов управления.
-
-## Новый порядок кнопок
-
-### До изменений:
-1. Счетчики логов (DEBUG, INFO, WARN, ERROR, OTHER)
-2. **Кнопка Refresh**
-3. Переключатель темы (Theme)
-4. Кнопка WebSocket состояния (ws: off)
-5. **Кнопка Update**
-
-### После изменений:
-1. Счетчики логов (DEBUG, INFO, WARN, ERROR, OTHER)
-2. Переключатель темы (Theme)
-3. Кнопка WebSocket состояния (ws: off)
-4. **Кнопка Update**
-5. **Кнопка Refresh**
-
-## Логика нового порядка
-
-### Группировка по функциональности:
-1. **Счетчики логов** - отображение статистики
-2. **Настройки интерфейса** - переключатель темы
-3. **Состояние соединений** - WebSocket и AJAX update
-4. **Управление обновлением** - кнопки update и refresh
-
-### Преимущества нового порядка:
-- **Логическая группировка** - связанные элементы находятся рядом
-- **Улучшенный UX** - пользователь интуитивно понимает назначение кнопок
-- **Последовательность действий** - сначала управление состоянием, потом ручное обновление
-
-## HTML структура
-
-```html
-
-```
-
-## Визуальное представление
-
-### Header layout:
-```
-[DEBUG: 0] [INFO: 0] [WARN: 0] [ERROR: 0] [OTHER: 0] | Theme [☐] | ws: off | update | Refresh
-```
-
-### Логические группы:
-- **Счетчики**: `[DEBUG: 0] [INFO: 0] [WARN: 0] [ERROR: 0] [OTHER: 0]`
-- **Настройки**: `Theme [☐]`
-- **Состояние**: `ws: off | update`
-- **Управление**: `Refresh`
-
-## Совместимость
-
-Изменение порядка кнопок не влияет на:
-- Функциональность кнопок
-- JavaScript обработчики
-- CSS стили
-- Логику показа/скрытия кнопок
-
-Все существующие функции продолжают работать без изменений.
-
-## Автор
-
-Сергей Антропов
-Сайт: https://devops.org.ru
-
-## Дата реализации
-
-2024 год
diff --git a/Makefile b/Makefile
index 64c8f51..31250ac 100644
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,7 @@
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
-.PHONY: help setup build up down restart logs clean status ps shell generate test-auth
+.PHONY: help setup build up down restart logs clean status ps shell
# Переменные
COMPOSE_FILE = docker-compose.yml
@@ -34,7 +34,10 @@ setup: ## Настроить переменные окружения (копир
cp env.example .env; \
echo "$(GREEN)Файл .env создан из env.example$(NC)"; \
echo "$(YELLOW)Не забудьте отредактировать .env под свои нужды!$(NC)"; \
- echo "$(YELLOW)После редактирования .env запустите make generate для обновления docker-compose.yml$(NC)"; \
+ echo "$(YELLOW)Особенно важны переменные безопасности:$(NC)"; \
+ echo "$(YELLOW) - LOGBOARD_PASS$(NC)"; \
+ echo "$(YELLOW) - SECRET_KEY$(NC)"; \
+ echo "$(YELLOW) - ENCRYPTION_KEY$(NC)"; \
else \
echo "$(YELLOW)Файл .env уже существует.$(NC)"; \
echo "$(YELLOW)Для пересоздания удалите .env и запустите make setup$(NC)"; \
@@ -47,9 +50,15 @@ build: ## Собрать Docker образ
up: ## Запустить сервисы в фоновом режиме (с правами root)
@echo "$(GREEN)Запуск сервисов с правами root...$(NC)"
+ @mkdir -p snapshots
docker compose -f $(COMPOSE_FILE) up -d
@echo "$(GREEN)Сервисы запущены с правами root!$(NC)"
- @echo "$(YELLOW)Приложение доступно по адресу: http://localhost:9001$(NC)"
+ @if [ -f .env ]; then \
+ PORT=$$(grep "^LOGBOARD_PORT=" .env | cut -d'=' -f2 || echo "9001"); \
+ echo "$(YELLOW)Приложение доступно по адресу: http://localhost:$$PORT$(NC)"; \
+ else \
+ echo "$(YELLOW)Приложение доступно по адресу: http://localhost:9001$(NC)"; \
+ fi
down: ## Остановить и удалить сервисы (с правами root)
@echo "$(YELLOW)Остановка сервисов с правами root...$(NC)"
@@ -88,8 +97,33 @@ rebuild: ## Пересобрать и запустить сервисы
@echo "$(YELLOW)Пересборка и запуск сервисов...$(NC)"
docker compose -f $(COMPOSE_FILE) down
docker compose -f $(COMPOSE_FILE) build --no-cache
+ @mkdir -p snapshots
docker compose -f $(COMPOSE_FILE) up -d
@echo "$(GREEN)Сервисы пересобраны и запущены!$(NC)"
- @echo "$(YELLOW)Приложение доступно по адресу: http://localhost:9001$(NC)"
+ @if [ -f .env ]; then \
+ PORT=$$(grep "^LOGBOARD_PORT=" .env | cut -d'=' -f2 || echo "9001"); \
+ echo "$(YELLOW)Приложение доступно по адресу: http://localhost:$$PORT$(NC)"; \
+ else \
+ echo "$(YELLOW)Приложение доступно по адресу: http://localhost:9001$(NC)"; \
+ fi
+
+config: ## Показать конфигурацию с подставленными переменными окружения
+ @echo "$(GREEN)Конфигурация Docker Compose с переменными окружения:$(NC)"
+ docker compose -f $(COMPOSE_FILE) config
+
+validate: ## Проверить синтаксис docker-compose.yml
+ @echo "$(GREEN)Проверка синтаксиса docker-compose.yml...$(NC)"
+ docker compose -f $(COMPOSE_FILE) config --quiet
+ @echo "$(GREEN)Синтаксис корректен!$(NC)"
+
+env-check: ## Проверить переменные окружения
+ @echo "$(GREEN)Проверка переменных окружения...$(NC)"
+ @if [ -f .env ]; then \
+ echo "$(GREEN)Файл .env найден$(NC)"; \
+ echo "$(YELLOW)Основные переменные:$(NC)"; \
+ grep -E "^(LOGBOARD_PORT|LOGBOARD_USER|LOGBOARD_PASS|SECRET_KEY|ENCRYPTION_KEY)=" .env || echo "$(RED)Переменные не найдены$(NC)"; \
+ else \
+ echo "$(RED)Файл .env не найден. Запустите make setup$(NC)"; \
+ fi
diff --git a/README.md b/README.md
deleted file mode 100644
index 0f9804c..0000000
--- a/README.md
+++ /dev/null
@@ -1,459 +0,0 @@
-
-# LogBoard+ - Веб-панель для просмотра логов микросервисов
-
-**Автор:** Сергей Антропов
-**Сайт:** https://devops.org.ru
-**Версия:** 1.0.0
-
-## 📋 Описание
-
-LogBoard+ - это современная веб-панель для просмотра логов Docker контейнеров в реальном времени. Приложение предоставляет удобный интерфейс для мониторинга логов микросервисов с поддержкой множественного просмотра, фильтрации, поиска и экспорта логов.
-
-## ✨ Основные возможности
-
-### 🔍 Просмотр логов
-- **Single View режим** - просмотр логов одного контейнера
-- **Multi View режим** - одновременный просмотр логов нескольких контейнеров
-- **Real-time обновление** через WebSocket соединения
-- **AJAX обновление** - периодическое получение новых логов без WebSocket
-- **Умное управление кнопками** - кнопка refresh скрывается при включенном AJAX autoupdate, кнопка update показывает состояние
-- **Автопрокрутка** логов
-- **Пауза/возобновление** потока логов
-
-### 🎨 Интерфейс
-- **Современный адаптивный дизайн**
-- **Темная и светлая темы**
-- **Мобильная версия**
-- **Интуитивно понятная навигация**
-
-### 🔧 Управление логами
-- **Фильтрация по уровням логирования** (DEBUG, INFO, WARN, ERROR, OTHER)
-- **Поиск по регулярным выражениям**
-- **Настройка количества строк** (Tail Lines)
-- **Перенос строк** (Word Wrap)
-- **Счетчики логов** по уровням
-
-### 📊 Статистика и мониторинг
-- **Счетчики логов** в реальном времени
-- **Статистика по уровням логирования**
-- **Мониторинг состояния контейнеров**
-- **Health check статусы**
-
-### 💾 Экспорт и сохранение
-- **Скачивание логов** в формате .log
-- **Отдельные файлы** для каждого контейнера в Multi View режиме
-- **Автоматическое именование файлов** с временными метками
-- **Сохранение снимков** логов
-
-### 🔐 Безопасность
-- **JWT аутентификация**
-- **Настраиваемые пользователи**
-- **Защищенные API endpoints**
-- **Шифрование чувствительных данных**
-
-## 🚀 Быстрый старт
-
-### Предварительные требования
-- Docker и Docker Compose
-- Python 3.8+
-- Доступ к Docker socket
-
-### Установка и запуск
-
-1. **Клонирование репозитория**
-```bash
-git clone
-cd logboard
-```
-
-2. **Настройка переменных окружения**
-```bash
-make setup
-# Отредактируйте .env файл под свои нужды
-```
-
-3. **Генерация docker-compose.yml**
-```bash
-make generate
-```
-
-4. **Запуск приложения**
-```bash
-make up
-```
-
-5. **Открытие в браузере**
-```
-http://localhost:9001
-```
-
-### Команды управления
-
-```bash
-make help # Показать справку по командам
-make setup # Настроить переменные окружения
-make generate # Сгенерировать docker-compose.yml
-make build # Собрать Docker образ
-make up # Запустить сервисы
-make down # Остановить сервисы
-make restart # Перезапустить сервисы
-make logs # Показать логи
-make status # Показать статус сервисов
-make shell # Подключиться к контейнеру
-make clean # Очистить проект
-```
-
-## ⚙️ Конфигурация
-
-### Основные настройки (.env)
-
-```bash
-# Основные настройки приложения
-LOGBOARD_PORT=9001 # Порт приложения
-LOGBOARD_TAIL=500 # Количество строк логов по умолчанию
-LOGBOARD_USER=admin # Имя пользователя
-LOGBOARD_PASS=s3cret-change-me # Пароль
-
-# Директория для снимков логов
-LOGBOARD_SNAPSHOT_DIR=/app/snapshots
-
-# Фильтр по проекту Docker Compose
-COMPOSE_PROJECT_NAME=myproj
-
-# Настройки множественных проектов
-LOGBOARD_PROJECTS=project1,project2,project3
-
-# Настройки безопасности
-SECRET_KEY=your-secret-key-here
-ENCRYPTION_KEY=your-encryption-key-here
-
-# Настройки производительности
-MAX_CONNECTIONS=100
-CONNECTION_TIMEOUT=30
-READ_TIMEOUT=60
-
-# Настройки аутентификации
-AUTH_ENABLED=true
-AUTH_METHOD=jwt
-SESSION_TIMEOUT=3600
-```
-
-### Docker сети
-
-Приложение поддерживает подключение к внешним Docker сетям:
-
-```yaml
-networks:
- - default: {}
- - iaas:
- external: true
- - infrastructure_iaas:
- external: true
-```
-
-## 🔌 API Endpoints
-
-### Аутентификация
-- `POST /api/auth/login` - Вход в систему
-- `POST /api/auth/logout` - Выход из системы
-- `GET /api/auth/me` - Информация о текущем пользователе
-
-### Сервисы и контейнеры
-- `GET /api/services` - Список всех сервисов/контейнеров
-- `GET /api/projects` - Список проектов Docker Compose
-- `GET /api/logs/stats/{container_id}` - Статистика логов контейнера
-
-### Управление исключениями
-- `GET /api/excluded-containers` - Список исключенных контейнеров
-- `POST /api/excluded-containers` - Добавить контейнер в исключения
-
-### Снимки логов
-- `POST /api/snapshot` - Создать снимок логов
-
-### WebSocket endpoints
-- `WS /ws/logs/{container_id}` - Поток логов контейнера
-- `WS /ws/fan/{service_name}` - Агрегированный поток логов сервиса
-- `WS /ws/fan_group` - Групповой поток логов
-
-### Системные
-- `GET /healthz` - Проверка здоровья приложения
-- `GET /` - Главная страница
-- `GET /login` - Страница входа
-
-## 🎯 Режимы просмотра
-
-### Single View режим
-- Просмотр логов одного выбранного контейнера
-- Полноэкранный режим отображения
-- Детальная статистика по уровням логирования
-- Возможность паузы и возобновления потока
-
-### Multi View режим
-- Одновременный просмотр логов нескольких контейнеров
-- Адаптивная сетка (1-4 колонки в зависимости от количества контейнеров)
-- Отдельные счетчики для каждого контейнера
-- Возможность скачивания отдельных файлов логов для каждого контейнера
-
-## 🔍 Фильтрация и поиск
-
-### Уровни логирования
-- **DEBUG** - Отладочная информация
-- **INFO** - Информационные сообщения
-- **WARN** - Предупреждения
-- **ERROR** - Ошибки
-- **OTHER** - Прочие сообщения
-
-### Поиск
-- **Регулярные выражения** для поиска в логах
-- **Фильтрация в реальном времени**
-- **Подсветка найденных совпадений**
-
-### Настройки отображения
-- **Tail Lines** - количество отображаемых строк
-- **Word Wrap** - перенос длинных строк
-- **Автопрокрутка** - автоматическая прокрутка к новым логам
-
-## ⌨️ Горячие клавиши
-
-### Навигация по контейнерам
-- **`[`** или **`Ctrl + ←`** - Переключение к предыдущему контейнеру
-- **`]`** или **`Ctrl + →`** - Переключение к следующему контейнеру
-
-### Тема оформления
-- **`Ctrl + T`** - Переключение между темной и светлой темой
-
-### Формы и ввод
-- **`Enter`** - В форме входа: отправка формы авторизации
-- **`Enter`** - В поле добавления исключений: добавление контейнера в исключения
-
-### Условия работы горячих клавиш
-- Горячие клавиши работают только когда фокус не находится в полях ввода
-- Навигация по контейнерам работает только в Single View режиме
-- Переключение темы работает на всех страницах приложения
-
-## 📱 Мобильная версия
-
-Приложение полностью адаптивно и поддерживает:
-- **Адаптивный дизайн** для мобильных устройств
-- **Сенсорное управление**
-- **Оптимизированный интерфейс** для маленьких экранов
-- **Боковое меню** для мобильных устройств
-
-## 🔧 Расширенные возможности
-
-### Управление проектами
-- **Множественные проекты** Docker Compose
-- **Фильтрация по проектам**
-- **Переключение между проектами**
-
-### Исключения контейнеров
-- **Добавление контейнеров в исключения**
-- **Постоянное хранение исключений**
-- **Управление через веб-интерфейс**
-
-### Мониторинг состояния
-- **Health check статусы** контейнеров
-- **Фильтрация нездоровых контейнеров**
-- **Автоматическое обновление статусов**
-
-### Производительность
-- **Ограничение количества соединений**
-- **Таймауты для операций**
-- **Оптимизированная обработка логов**
-- **Кэширование данных**
-
-## 🛠️ Разработка
-
-### Структура проекта
-```
-logboard/
-├── app.py # Основное приложение FastAPI
-├── docker-compose.yml # Конфигурация Docker Compose
-├── Dockerfile # Docker образ
-├── requirements.txt # Зависимости Python
-├── templates/ # HTML шаблоны
-│ ├── index.html # Главная страница
-│ ├── login.html # Страница входа
-│ └── error.html # Страницы ошибок
-├── scripts/ # Вспомогательные скрипты
-│ └── generate-compose.py
-├── snapshots/ # Директория для снимков логов
-├── Makefile # Команды управления
-├── env.example # Пример переменных окружения
-└── README.md # Документация
-```
-
-### Технологии
-- **Backend**: FastAPI, Python 3.8+
-- **Frontend**: HTML5, CSS3, JavaScript (ES6+)
-- **WebSocket**: Асинхронные соединения для real-time логов
-- **Docker**: Интеграция с Docker API
-- **JWT**: Аутентификация и авторизация
-- **Docker Compose**: Управление контейнерами
-
-### Разработка и отладка
-
-```bash
-# Запуск в режиме разработки
-make dev
-
-# Подключение к контейнеру
-make shell
-
-# Просмотр логов
-make logs
-
-# Пересборка
-make rebuild
-```
-
-## 🔒 Безопасность
-
-### Аутентификация
-- **JWT токены** для сессий
-- **Настраиваемые пользователи** через переменные окружения
-- **Автоматический выход** по истечении сессии
-- **Защищенные API endpoints**
-
-### Шифрование
-- **Шифрование чувствительных данных** с помощью Fernet
-- **Безопасное хранение** токенов и паролей
-- **Защищенные переменные окружения**
-
-### Сетевая безопасность
-- **HTTPS поддержка** (при настройке)
-- **Валидация входных данных**
-- **Защита от XSS и CSRF атак**
-
-## 📊 Мониторинг и логирование
-
-### Логирование приложения
-- **Настраиваемые уровни логирования** (DEBUG, INFO, WARN, ERROR)
-- **JSON формат** логов
-- **Структурированное логирование**
-
-### Метрики
-- **Количество активных соединений**
-- **Статистика WebSocket соединений**
-- **Время отклика API**
-- **Использование ресурсов**
-
-### Health checks
-- **Проверка состояния** приложения
-- **Мониторинг Docker соединения**
-- **Проверка доступности** зависимостей
-
-## 🚀 Развертывание
-
-### Продакшн настройки
-
-1. **Настройка безопасности**
-```bash
-# Измените секретные ключи
-SECRET_KEY=your-production-secret-key
-ENCRYPTION_KEY=your-production-encryption-key
-
-# Настройте пользователей
-LOGBOARD_USER=your-admin-user
-LOGBOARD_PASS=your-secure-password
-```
-
-2. **Настройка сети**
-```bash
-# Настройте внешние сети
-DOCKER_NETWORKS=your-network1,your-network2
-```
-
-3. **Настройка производительности**
-```bash
-# Увеличьте лимиты для продакшна
-MAX_CONNECTIONS=500
-CONNECTION_TIMEOUT=60
-READ_TIMEOUT=120
-```
-
-### Docker Swarm
-```yaml
-version: '3.8'
-services:
- logboard:
- image: logboard:latest
- deploy:
- replicas: 2
- resources:
- limits:
- memory: 512M
- reservations:
- memory: 256M
- networks:
- - logboard-network
- volumes:
- - /var/run/docker.sock:/var/run/docker.sock:ro
- - logboard-snapshots:/app/snapshots
-```
-
-### Kubernetes
-```yaml
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: logboard
-spec:
- replicas: 2
- selector:
- matchLabels:
- app: logboard
- template:
- metadata:
- labels:
- app: logboard
- spec:
- containers:
- - name: logboard
- image: logboard:latest
- ports:
- - containerPort: 9001
- volumeMounts:
- - name: docker-sock
- mountPath: /var/run/docker.sock
- - name: snapshots
- mountPath: /app/snapshots
- volumes:
- - name: docker-sock
- hostPath:
- path: /var/run/docker.sock
- - name: snapshots
- persistentVolumeClaim:
- claimName: logboard-snapshots
-```
-
-## 🤝 Поддержка и обратная связь
-
-### Автор
-- **Сергей Антропов**
-- **Сайт**: https://devops.org.ru
-- **Email**: [contact@devops.org.ru]
-
-### Сообщество
-- **Issues**: Сообщайте о багах и предлагайте улучшения
-- **Pull Requests**: Приветствуются вклады в развитие проекта
-- **Документация**: Помогите улучшить документацию
-
-### Лицензия
-Проект распространяется под лицензией MIT.
-
-## 📝 Changelog
-
-### v1.0.0 (2024-12-17)
-- ✨ Первый релиз LogBoard+
-- 🔍 Поддержка Single View и Multi View режимов
-- 🔐 JWT аутентификация
-- 📱 Адаптивный дизайн
-- 🔧 Полная интеграция с Docker API
-- 📊 Статистика и мониторинг логов
-- 💾 Экспорт логов в файлы
-- 🎨 Темная и светлая темы
-
----
-
-**LogBoard+** - современное решение для мониторинга логов микросервисов в Docker среде.
diff --git a/REFRESH_BUTTON_STYLING.md b/REFRESH_BUTTON_STYLING.md
deleted file mode 100644
index 80e632a..0000000
--- a/REFRESH_BUTTON_STYLING.md
+++ /dev/null
@@ -1,105 +0,0 @@
-# Унификация стилей кнопки Refresh
-
-## Описание изменений
-
-Обновлены стили кнопки refresh в header для соответствия размерам и стилям кнопки update.
-
-## Изменения стилей
-
-### До изменений:
-```css
-.log-refresh-btn {
- padding: 6px 24px; /* Больше горизонтального отступа */
- height: fit-content; /* Автоматическая высота */
- border: none; /* Без границы */
- /* нет min-width */
- /* нет text-align */
- /* нет box-shadow */
-}
-```
-
-### После изменений:
-```css
-.log-refresh-btn {
- padding: 6px 12px; /* Такой же отступ как у update */
- min-width: 60px; /* Минимальная ширина как у update */
- text-align: center; /* Выравнивание текста по центру */
- border: 1px solid var(--accent); /* Граница как у update */
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); /* Тень как у update */
- /* убрали height: fit-content */
-}
-```
-
-## Сравнение кнопок
-
-### Кнопка Update:
-```css
-.ajax-update-btn {
- padding: 6px 12px;
- font-size: 11px;
- font-weight: 500;
- min-width: 60px;
- text-align: center;
-}
-```
-
-### Кнопка Refresh (после изменений):
-```css
-.log-refresh-btn {
- padding: 6px 12px; /* ✅ Совпадает */
- font-size: 11px; /* ✅ Совпадает */
- font-weight: 500; /* ✅ Совпадает */
- min-width: 60px; /* ✅ Совпадает */
- text-align: center; /* ✅ Совпадает */
- border: 1px solid var(--accent); /* ✅ Совпадает по стилю */
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); /* ✅ Совпадает */
-}
-```
-
-## Визуальные улучшения
-
-### Единообразие:
-- **Одинаковая высота** - обе кнопки теперь имеют одинаковую высоту
-- **Одинаковая ширина** - минимальная ширина 60px для обеих кнопок
-- **Одинаковый размер шрифта** - 11px для обеих кнопок
-- **Одинаковые отступы** - 6px 12px для обеих кнопок
-- **Одинаковая граница** - 1px solid для обеих кнопок
-- **Одинаковая тень** - box-shadow для обеих кнопок
-
-### Преимущества:
-1. **Визуальная согласованность** - кнопки выглядят как единый набор элементов управления
-2. **Улучшенный UX** - пользователь видит логически связанные элементы одинакового размера
-3. **Профессиональный вид** - интерфейс выглядит более аккуратно и организованно
-
-## Технические детали
-
-### Измененные CSS свойства:
-- `padding: 6px 24px` → `padding: 6px 12px`
-- Добавлен `min-width: 60px`
-- Добавлен `text-align: center`
-- `border: none` → `border: 1px solid var(--accent)`
-- Добавлен `box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1)`
-- Убран `height: fit-content`
-
-### Сохраненные свойства:
-- `font-size: 11px` - уже совпадал
-- `font-weight: 500` - уже совпадал
-- `border-radius: 6px` - уже совпадал
-- Hover эффекты - уже совпадали
-
-## Совместимость
-
-Изменения стилей не влияют на:
-- Функциональность кнопки
-- JavaScript обработчики
-- Логику показа/скрытия
-- Поведение в разных темах (светлая/темная)
-
-## Автор
-
-Сергей Антропов
-Сайт: https://devops.org.ru
-
-## Дата реализации
-
-2024 год
diff --git a/REFRESH_BUTTON_VISIBILITY.md b/REFRESH_BUTTON_VISIBILITY.md
deleted file mode 100644
index f5a9917..0000000
--- a/REFRESH_BUTTON_VISIBILITY.md
+++ /dev/null
@@ -1,196 +0,0 @@
-# Управление кнопками Refresh и Update
-
-## Описание изменений
-
-Реализована функциональность автоматического управления кнопками "Refresh" и "Update" в header в зависимости от состояния AJAX autoupdate.
-
-### Основные изменения:
-1. **AJAX autoupdate по умолчанию включен** - изменено значение `ajaxUpdateEnabled` с `false` на `true`
-2. **Добавлена кнопка Update** - новая кнопка справа от кнопки WebSocket состояния
-3. **Улучшено управление кнопками** - кнопка refresh скрывается, кнопка update показывает состояние
-
-## Логика работы
-
-### Кнопка Refresh
-- **AJAX autoupdate включен** → Кнопка refresh **скрыта**
-- **AJAX autoupdate выключен** → Кнопка refresh **показана**
-
-### Кнопка Update
-- **AJAX autoupdate включен** → Кнопка update **зеленая**
-- **AJAX autoupdate выключен** → Кнопка update **красная**
-- **Клик по кнопке** → Переключает состояние AJAX autoupdate
-
-## Реализованные функции
-
-### updateRefreshButtonVisibility()
-Основная функция для управления видимостью кнопки refresh и состоянием кнопки update:
-
-```javascript
-function updateRefreshButtonVisibility() {
- const refreshButtons = document.querySelectorAll('.log-refresh-btn');
- refreshButtons.forEach(btn => {
- if (ajaxUpdateEnabled) {
- // Если ajax autoupdate включен, скрываем кнопку refresh
- btn.style.display = 'none';
- } else {
- // Если ajax autoupdate выключен, показываем кнопку refresh
- btn.style.display = 'inline-flex';
- }
- });
-
- // Обновляем состояние кнопки update
- setAjaxUpdateState(ajaxUpdateEnabled);
-}
-```
-
-### setAjaxUpdateState(enabled)
-Функция для управления визуальным состоянием кнопки update:
-
-```javascript
-function setAjaxUpdateState(enabled) {
- if (els.ajaxUpdateBtn) {
- // Удаляем все классы состояний
- els.ajaxUpdateBtn.classList.remove('ajax-on', 'ajax-off');
-
- // Добавляем соответствующий класс
- if (enabled) {
- els.ajaxUpdateBtn.classList.add('ajax-on');
- els.ajaxUpdateBtn.textContent = 'update';
- } else {
- els.ajaxUpdateBtn.classList.add('ajax-off');
- els.ajaxUpdateBtn.textContent = 'update';
- }
- }
-}
-```
-
-## Интеграция с существующим кодом
-
-Функция `updateRefreshButtonVisibility()` вызывается в следующих местах:
-
-1. **updateAjaxUpdateCheckbox()** - при обновлении состояния чекбокса
-2. **enableAjaxLogUpdate()** - при включении AJAX обновления
-3. **disableAjaxLogUpdate()** - при выключении AJAX обновления
-4. **toggleAjaxLogUpdate()** - при переключении состояния
-5. **initAjaxUpdateCheckbox()** - при инициализации чекбокса
-6. **initAjaxUpdate()** - при инициализации AJAX update
-7. **Обработчик изменения чекбокса** - при изменении состояния пользователем
-
-## CSS стили
-
-### Кнопка Refresh
-Кнопка refresh использует класс `.log-refresh-btn` и стили:
-
-```css
-.log-refresh-btn {
- background: var(--accent);
- color: white;
- border: 1px solid var(--accent);
- border-radius: 6px;
- transition: all 0.2s ease;
- padding: 6px 12px;
- font-size: 11px;
- font-weight: 500;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- min-width: 60px;
- text-align: center;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
-}
-```
-
-### Кнопка Update
-Кнопка update использует класс `.ajax-update-btn` и стили:
-
-```css
-.ajax-update-btn {
- background: var(--chip);
- color: var(--muted);
- border: 1px solid var(--border);
- border-radius: 6px;
- padding: 6px 12px;
- font-size: 11px;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.3s ease;
- font-family: inherit;
- min-width: 60px;
- text-align: center;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
-}
-
-.ajax-update-btn.ajax-on {
- background: #7ea855; /* Зеленый цвет */
- color: white;
- border-color: #7ea855;
-}
-
-.ajax-update-btn.ajax-off {
- background: #f7768e; /* Красный цвет */
- color: white;
- border-color: #f7768e;
-}
-
-.ajax-update-btn:hover {
- opacity: 0.8;
- transform: translateY(-1px);
- box-shadow: 0 2px 4px rgba(0,0,0,0.2);
-}
-```
-
-## HTML структура
-
-### Кнопка Refresh
-Кнопка refresh находится в header:
-
-```html
-
-```
-
-### Кнопка Update
-Кнопка update находится в header справа от кнопки WebSocket состояния:
-
-```html
-
-
-
-```
-
-## Автоматическое обновление видимости
-
-Видимость кнопки refresh автоматически обновляется в следующих случаях:
-
-1. **При инициализации AJAX update** - устанавливается начальное состояние
-2. **При изменении состояния чекбокса "Auto-update logs"** - пользователь включает/выключает autoupdate
-3. **При программном включении/выключении AJAX update** - через функции enable/disable
-4. **При переключении состояния через функцию toggleAjaxLogUpdate** - программное переключение
-
-## Преимущества реализации
-
-1. **Улучшенный UX** - пользователь видит только актуальные элементы управления
-2. **Логическая связность** - кнопка refresh скрыта, когда она не нужна (при включенном autoupdate)
-3. **Автоматическое управление** - не требует ручного вмешательства пользователя
-4. **Совместимость** - работает с существующим кодом без нарушений
-
-## Тестирование
-
-Для тестирования функциональности:
-
-1. Откройте приложение в браузере
-2. Проверьте, что при включенном чекбоксе "Auto-update logs" кнопка refresh скрыта
-3. Отключите чекбокс "Auto-update logs" - кнопка refresh должна появиться
-4. Включите чекбокс обратно - кнопка refresh должна скрыться
-
-## Автор
-
-Сергей Антропов
-Сайт: https://devops.org.ru
-
-## Дата реализации
-
-2024 год
diff --git a/SCROLL_FIX_SUMMARY.md b/SCROLL_FIX_SUMMARY.md
deleted file mode 100644
index d4af64b..0000000
--- a/SCROLL_FIX_SUMMARY.md
+++ /dev/null
@@ -1,182 +0,0 @@
-# Исправление проблемы со скроллом в Multi View режиме
-
-## Проблема
-В режиме Multi View со свернутым sidebar не работал скролл в логах контейнеров. Проблема была связана с неправильными CSS стилями, которые не учитывали правильную высоту и overflow для multi-view логов в свернутом состоянии sidebar.
-
-## Причина
-Основные причины проблемы:
-
-1. **Неправильная высота multi-view-log**: В свернутом состоянии sidebar элементы `.multi-view-log` получали неправильную высоту `calc(100vh - var(--header-height))` вместо `100%`
-2. **Отсутствие flex-свойств**: Элементы не имели правильных flex-свойств для корректного распределения пространства
-3. **Неправильный overflow**: Контейнеры `.multi-view-content` не имели правильных настроек overflow
-
-## Исправления
-
-### 1. CSS стили для свернутого sidebar
-
-Добавлены дополнительные CSS правила для правильного отображения multi-view в свернутом состоянии:
-
-```css
-/* Исправляем скролл для multi-view в свернутом состоянии */
-.sidebar.collapsed ~ .main-content .multi-view-content {
- height: calc(100vh - var(--header-height) - 60px) !important;
- overflow: hidden !important;
- display: flex !important;
- flex-direction: column !important;
-}
-
-.sidebar.collapsed ~ .main-content .multi-view-log {
- height: 100% !important;
- overflow: auto !important;
- display: block !important;
- max-height: none !important;
- min-height: 200px !important;
- position: relative !important;
- flex: 1 !important;
- min-height: 0 !important;
-}
-```
-
-### 2. Новая функция forceFixMultiViewStyles()
-
-Добавлена новая функция для принудительного исправления стилей multi-view логов:
-
-```javascript
-function forceFixMultiViewStyles() {
- const multiViewLogs = document.querySelectorAll('.multi-view-log');
-
- multiViewLogs.forEach((log, index) => {
- // Принудительно устанавливаем все необходимые стили
- log.style.setProperty('height', '100%', 'important');
- log.style.setProperty('overflow', 'auto', 'important');
- log.style.setProperty('max-height', 'none', 'important');
- log.style.setProperty('display', 'block', 'important');
- log.style.setProperty('min-height', '200px', 'important');
- log.style.setProperty('position', 'relative', 'important');
- log.style.setProperty('flex', '1', 'important');
- log.style.setProperty('min-height', '0', 'important');
-
- // Принудительно вызываем пересчет layout
- log.style.setProperty('transform', 'translateZ(0)', 'important');
-
- // Дополнительно устанавливаем inline стили для максимальной надежности
- log.setAttribute('style', log.getAttribute('style') + '; height: 100% !important; overflow: auto !important; flex: 1 !important; min-height: 0 !important;');
- });
-}
-```
-
-### 3. Обновление функции updateLogStyles()
-
-Исправлена функция `updateLogStyles()` для правильного применения стилей:
-
-- Использует `setProperty()` с флагом `important` для принудительного применения стилей
-- Вызывает `forceFixMultiViewStyles()` для дополнительной надежности
-- Принудительно вызывается пересчет layout с помощью `transform: translateZ(0)`
-
-### 4. Улучшение функции toggleSidebar()
-
-Добавлена дополнительная проверка для multi-view логов при переключении sidebar:
-
-```javascript
-// Дополнительная проверка для multi-view логов
-if (state.multiViewMode) {
- console.log('Sidebar toggle: Force fixing multi-view styles');
- forceFixMultiViewStyles();
-}
-```
-
-### 5. Обновление функции setupMultiView()
-
-Добавлена принудительная проверка стилей при создании multi-view:
-
-```javascript
-// Принудительно обновляем стили логов для multi-view
-setTimeout(() => {
- updateLogStyles();
-
- // Дополнительная проверка для multi-view логов
- console.log('setupMultiView: Force fixing multi-view styles');
- forceFixMultiViewStyles();
-}, 200);
-```
-
-### 6. Инициализация при загрузке страницы
-
-Добавлена дополнительная проверка при инициализации:
-
-```javascript
-// Дополнительная проверка для multi-view логов при загрузке
-setTimeout(() => {
- if (state.multiViewMode) {
- console.log('Initialization: Force fixing multi-view styles');
- forceFixMultiViewStyles();
- }
-}, 1000);
-```
-
-### 7. Периодическая проверка и обработчики событий
-
-Добавлены дополнительные механизмы для обеспечения правильной работы:
-
-- **Периодическая проверка**: Каждые 5 секунд проверяются и исправляются стили multi-view логов
-- **Обработчик изменения размера окна**: При изменении размера браузера автоматически исправляются стили
-- **Глобальные функции**: Добавлены функции `forceFixMultiViewStyles()` и `updateLogStyles()` в глобальную область для ручного вызова из консоли
-
-## Результат
-
-После применения исправлений:
-
-1. ✅ Скролл работает корректно в Multi View режиме со свернутым sidebar
-2. ✅ Логи контейнеров отображаются в правильной высоте
-3. ✅ Переключение sidebar не нарушает функциональность скролла
-4. ✅ Автопрокрутка работает корректно
-5. ✅ Стили применяются правильно при всех сценариях использования
-
-## Тестирование
-
-Для проверки исправлений:
-
-1. Включите Multi View режим (выберите несколько контейнеров)
-2. Сверните sidebar (Ctrl+B или кнопка)
-3. Убедитесь, что скролл работает в каждом контейнере
-4. Проверьте автопрокрутку при поступлении новых логов
-5. Разверните sidebar и убедитесь, что скролл продолжает работать
-
-### Ручное исправление (если проблема все еще возникает)
-
-Если скролл все еще не работает в некоторых окнах, можно вручную исправить стили:
-
-1. Откройте консоль браузера (F12)
-2. Выполните команду: `forceFixMultiViewStyles()`
-3. Или выполните команду: `updateLogStyles()`
-4. Для всех контейнеров: `fixAllContainers()`
-5. Для обратной совместимости: `fixProblematicContainers()` (перенаправляет на `fixAllContainers()`)
-
-Эти функции принудительно исправят стили всех multi-view логов на странице.
-
-### Универсальное исправление для всех контейнеров
-
-Вместо специальной обработки только для проблемных контейнеров, теперь применяется универсальное решение:
-
-- **Универсальные CSS правила** для всех multi-view логов (с более специфичными селекторами)
-- **Единообразное исправление стилей** для всех контейнеров
-- **Проверка родительских элементов** для всех контейнеров
-- **Автоматическое исправление** в периодических проверках и при изменении размера окна
-- **Защита от влияния на другие элементы** - все селекторы теперь специфичны для `.multi-view-content .multi-view-log`
-
-### Исправление проблемы с wrap text
-
-Проблема с wrap text была решена путем:
-
-- **Более специфичных селекторов** - изменил `.log` на `.main-content .log` в функции `applyWrapSettings()`
-- **Добавления вызовов `applyWrapSettings()`** в ключевых местах:
- - После обновления логов (`refreshLogsAndCounters()`)
- - После исправления стилей (`forceFixMultiViewStyles()`)
- - После исправления всех контейнеров (`fixAllContainers()`)
- - После переключения контейнеров (`switchToSingle()`)
- - После открытия мульти-контейнеров (`openMulti()`)
-- **Сохранения существующих вызовов** в `setupMultiView()` и других местах
-
-## Автор
-Сергей Антропов
-Сайт: https://devops.org.ru
diff --git a/app.py b/app.py
index 12828a8..298f6f1 100644
--- a/app.py
+++ b/app.py
@@ -882,10 +882,11 @@ def api_services(
project_list = None
if projects:
project_list = [p.strip() for p in projects.split(",") if p.strip()]
- elif DEFAULT_PROJECTS:
+ elif DEFAULT_PROJECTS and DEFAULT_PROJECTS.strip():
project_list = [p.strip() for p in DEFAULT_PROJECTS.split(",") if p.strip()]
- elif DEFAULT_PROJECT:
+ elif DEFAULT_PROJECT and DEFAULT_PROJECT.strip():
project_list = [DEFAULT_PROJECT]
+ # Если ни одна переменная не указана или пустая, показываем все контейнеры (project_list остается None)
return JSONResponse(
content=list_containers(projects=project_list, include_stopped=include_stopped),
diff --git a/app/docs/README.md b/app/docs/README.md
deleted file mode 100644
index 8ce23a5..0000000
--- a/app/docs/README.md
+++ /dev/null
@@ -1,47 +0,0 @@
-# Документация LogBoard+
-
-## Обзор
-
-LogBoard+ - это веб-приложение для мониторинга логов Docker контейнеров в реальном времени с поддержкой Single View и Multi View режимов.
-
-## Основные функции
-
-### Просмотр логов
-- **Single View**: просмотр логов одного контейнера
-- **Multi View**: одновременный просмотр логов нескольких контейнеров
-- Фильтрация по уровням логирования (DEBUG, INFO, WARN, ERROR)
-- Поиск по регулярным выражениям
-- Автопрокрутка и перенос строк
-
-### Горячие клавиши
-- `Ctrl+R` / `Ctrl+K` - обновление логов
-- `[` `]` - навигация между контейнерами
-- `Ctrl+B` - сворачивание/разворачивание панели
-
-### Управление интерфейсом
-- Сворачивание sidebar для экономии места
-- Переключение тем (светлая/темная)
-- Настройка количества отображаемых строк
-- Экспорт логов в файл
-
-## Документация
-
-- [Горячие клавиши](hotkeys.md) - подробное описание всех горячих клавиш
-- [Новые функции интерфейса](features.md) - описание сворачивания панелей и других функций
-- [API документация](../api/README.md) - описание API endpoints
-- [Разработка](../dev/README.md) - руководство для разработчиков
-
-## Технологии
-
-- **Backend**: Python, FastAPI, WebSocket
-- **Frontend**: HTML5, CSS3, JavaScript (Vanilla)
-- **База данных**: PostgreSQL с asyncpg
-- **Контейнеризация**: Docker, Docker Compose
-
-## Автор
-
-Сергей Антропов - https://devops.org.ru
-
-## Лицензия
-
-MIT License
diff --git a/app/docs/ajax-update.md b/app/docs/ajax-update.md
deleted file mode 100644
index 0d3db36..0000000
--- a/app/docs/ajax-update.md
+++ /dev/null
@@ -1,172 +0,0 @@
-# AJAX Auto-update для LogBoard+
-
-## Описание
-
-AJAX Auto-update - это система автоматического обновления логов через AJAX запросы, которая позволяет получать новые логи без перезагрузки страницы.
-
-## Основные возможности
-
-- **Автоматическое обновление**: Логи обновляются с заданным интервалом
-- **Умное управление кнопкой Refresh**: Кнопка refresh автоматически скрывается при включенном AJAX autoupdate и показывается при выключенном
-- **Поддержка Multi-view**: Работает как в single-view, так и в multi-view режимах
-- **Настраиваемый интервал**: Интервал обновления настраивается через API
-- **Эффективное обновление**: Обновляются только новые логи с момента последнего запроса
-
-## Управление кнопками
-
-### Кнопка Refresh
-
-Кнопка refresh в header автоматически управляется в зависимости от состояния AJAX autoupdate:
-
-- **AJAX autoupdate включен** → Кнопка refresh **скрыта**
-- **AJAX autoupdate выключен** → Кнопка refresh **показана**
-
-### Кнопка Update
-
-Кнопка update в header показывает состояние AJAX autoupdate и позволяет переключать его:
-
-- **AJAX autoupdate включен** → Кнопка update **зеленая**
-- **AJAX autoupdate выключен** → Кнопка update **красная**
-- **Клик по кнопке** → Переключает состояние AJAX autoupdate
-
-### Функции управления
-
-```javascript
-/**
- * Обновить видимость кнопки refresh в header
- */
-function updateRefreshButtonVisibility() {
- const refreshButtons = document.querySelectorAll('.log-refresh-btn');
- refreshButtons.forEach(btn => {
- if (ajaxUpdateEnabled) {
- // Если ajax autoupdate включен, скрываем кнопку refresh
- btn.style.display = 'none';
- } else {
- // Если ajax autoupdate выключен, показываем кнопку refresh
- btn.style.display = 'inline-flex';
- }
- });
-}
-```
-
-### Автоматическое обновление видимости
-
-Видимость кнопки refresh автоматически обновляется в следующих случаях:
-
-1. **При инициализации AJAX update**
-2. **При изменении состояния чекбокса "Auto-update logs"**
-3. **При программном включении/выключении AJAX update**
-4. **При переключении состояния через функцию toggleAjaxLogUpdate**
-
-## Настройки
-
-### Интервал обновления
-
-Интервал обновления настраивается через API endpoint `/api/settings`:
-
-```json
-{
- "ajax_update_interval": 2000
-}
-```
-
-По умолчанию используется интервал 2000ms (2 секунды).
-
-### Чекбокс управления
-
-В sidebar есть чекбокс "Auto-update logs", который позволяет пользователю:
-
-- Включить автоматическое обновление
-- Выключить автоматическое обновление
-- Автоматически управляет видимостью кнопки refresh
-
-## API Endpoints
-
-### Получение настроек
-
-```
-GET /api/settings
-Authorization: Bearer
-```
-
-Ответ:
-```json
-{
- "ajax_update_interval": 2000
-}
-```
-
-### Получение логов с поддержкой AJAX
-
-```
-GET /api/logs/{container_id}?tail={lines}&since={timestamp}
-Authorization: Bearer
-```
-
-Параметры:
-- `tail`: количество строк для получения (или "all")
-- `since`: временная метка последнего обновления (опционально)
-
-## Переменные состояния
-
-```javascript
-let ajaxUpdateEnabled = true; // Состояние AJAX обновления (по умолчанию включен)
-let ajaxUpdateIntervalMs = 2000; // Интервал обновления в миллисекундах
-let ajaxUpdateInterval = null; // ID интервала
-const containerStates = new Map(); // Состояние контейнеров для отслеживания обновлений
-```
-
-## Функции управления
-
-### enableAjaxLogUpdate(intervalMs)
-Включает AJAX обновление логов с заданным интервалом.
-
-### disableAjaxLogUpdate()
-Отключает AJAX обновление логов.
-
-### toggleAjaxLogUpdate()
-Переключает состояние AJAX обновления.
-
-### performAjaxLogUpdate()
-Выполняет одно обновление логов через AJAX.
-
-### updateContainerLogs(containerId, tailLines, token)
-Обновляет логи для конкретного контейнера.
-
-### updateRefreshButtonVisibility()
-Обновляет видимость кнопки refresh и состояние кнопки update в зависимости от состояния AJAX autoupdate.
-
-### setAjaxUpdateState(enabled)
-Обновляет визуальное состояние кнопки update (зеленая/красная) в зависимости от состояния AJAX autoupdate.
-
-## Интеграция с существующим кодом
-
-AJAX update интегрируется с существующими функциями:
-
-- **switchToSingle**: Останавливает AJAX обновление при смене контейнера
-- **switchToMultiView**: Останавливает AJAX обновление при переключении в multi-view
-- **refreshLogsAndCounters**: Ручное обновление логов (кнопка refresh)
-
-## Логирование
-
-Все операции AJAX update логируются в консоль браузера:
-
-```javascript
-console.log('AJAX обновление логов включено с интервалом 2000ms');
-console.log('AJAX обновление логов отключено');
-console.log('AJAX Update: Обновляем 2 контейнеров: ["container1", "container2"]');
-```
-
-## Обработка ошибок
-
-При ошибках AJAX запросов:
-
-- Обновление не останавливается автоматически
-- Ошибки логируются в консоль
-- Пользователь может вручную отключить обновление через чекбокс
-
-## Совместимость
-
-- Работает с существующими WebSocket соединениями
-- Поддерживает все режимы просмотра (single-view, multi-view)
-- Совместимо с фильтрацией и настройками уровней логирования
diff --git a/app/docs/features.md b/app/docs/features.md
deleted file mode 100644
index 81b3e4b..0000000
--- a/app/docs/features.md
+++ /dev/null
@@ -1,77 +0,0 @@
-# Новые функции интерфейса LogBoard+
-
-## Сворачивание панелей
-
-### Sidebar (боковая панель)
-- **Кнопка сворачивания**: на границе sidebar и основного контента
-- **Горячая клавиша**: `Ctrl+B` / `Ctrl+И`
-- **Свернутое состояние**: ширина 60px
-- **Логотип**: показывает в свернутом состоянии
-- **Кнопка помощи**: между options и logout
-
-## Управление
-
-### Кнопка сворачивания
-- **Кнопка на границе**: сворачивает боковую панель
-- **Расположение**: посередине экрана по высоте на границе sidebar и основного контента
-- **Состояние сохраняется** в localStorage
-
-### Горячая клавиша
-- **Ctrl+B** / **Ctrl+И**: сворачивает/разворачивает sidebar и header
-- Удобно для быстрого освобождения места на экране
-
-### Header (заголовок)
-- **Сворачивается вместе с sidebar**
-- **В свернутом состоянии**: тонкая полоска 40px высотой
-- **Содержит**: фильтр логов, кнопки уровней логирования, кнопку обновления
-- **Стили**: кнопки выглядят точно так же, как в развернутом состоянии
-- **log-header**: полностью скрывается в свернутом режиме
-- **log-content**: минимальный padding 2px в свернутом состоянии
-- **multi-view-panel**: показывает название контейнера в Single View режиме
-
-## Визуальные элементы
-
-### Логотип в свернутом sidebar
-```
-
-```
-- Отображается только когда sidebar свернут
-- Расположен в самом верху sidebar
-- Стилизован в цвете акцента
-- Занимает минимальное место
-
-### Модальное окно с горячими клавишами
-- **Открытие**: кнопка в свернутом sidebar
-- **Закрытие**: кнопка X, клик вне окна, или повторный клик на кнопку помощи
-- **Содержит**: полный список всех горячих клавиш с описанием
-- **Анимация**: плавное появление и исчезновение
-
-### Анимации
-- Плавные переходы при сворачивании/разворачивании
-- Длительность анимации: 0.3 секунды
-- CSS transitions для всех элементов
-- Кнопка сворачивания остается на месте при наведении
-
-## Сохранение настроек
-
-### localStorage ключи
-- `lb_sidebar_collapsed` - состояние sidebar
-- `lb_hotkeys_shown` - показ уведомления о горячих клавишах
-
-### Восстановление состояния
-- При перезапуске приложения состояния восстанавливаются
-- Кнопки показывают правильные иконки
-- Tooltip обновляется в соответствии с состоянием
-
-## Примеры использования
-
-### Освобождение места
-1. Нажать `Ctrl+B` или кнопку sidebar - сворачивается панель
-2. Получаем больше места для просмотра логов
-
-### Быстрое переключение
-1. Использовать `Ctrl+B` для быстрого переключения
-2. Использовать кнопку на границе для точного управления
-
-## Автор
-Сергей Антропов - https://devops.org.ru
diff --git a/app/docs/hotkeys.md b/app/docs/hotkeys.md
deleted file mode 100644
index a18edc6..0000000
--- a/app/docs/hotkeys.md
+++ /dev/null
@@ -1,66 +0,0 @@
-# Горячие клавиши LogBoard+
-
-## Обновление логов
-
-### Ctrl+R
-Обновляет логи в текущем режиме просмотра:
-- **Single View**: переподключается к WebSocket и получает свежие логи
-- **Multi View**: переподключается ко всем выбранным контейнерам
-
-### Ctrl+K
-Альтернативная комбинация для обновления логов (аналогично Ctrl+R)
-
-## Навигация
-
-### [ (квадратная скобка)
-Переход к предыдущему контейнеру в списке
-
-### ] (квадратная скобка)
-Переход к следующему контейнеру в списке
-
-### Ctrl+← (стрелка влево)
-Альтернативная комбинация для перехода к предыдущему контейнеру
-
-### Ctrl+→ (стрелка вправо)
-Альтернативная комбинация для перехода к следующему контейнеру
-
-## Управление интерфейсом
-
-### Ctrl+B
-Сворачивание/разворачивание sidebar панели:
-- Сворачивает sidebar до минимальной ширины (60px)
-- Скрывает все элементы управления и список контейнеров
-- Показывает логотип LogBoard+ в свернутом sidebar
-- Состояние сохраняется в localStorage
-
-### Кнопка сворачивания
-- **Кнопка на границе** () - сворачивание sidebar
-- Расположена посередине экрана по высоте на границе sidebar и основного контента
-- Состояние сохраняется в localStorage
-
-## Особенности
-
-### Проверка фокуса
-Горячие клавиши не работают, когда фокус находится в полях ввода:
-- Поле фильтра логов
-- Поле добавления исключенных контейнеров
-- Любые другие input/textarea элементы
-
-### Визуальные подсказки
-- Иконка клавиатуры в заголовке с подсказкой о горячих клавишах
-- Уведомление о горячих клавишах при первом запуске
-- Tooltip на кнопке сворачивания sidebar
-
-### Сохранение настроек
-- Состояние sidebar (свернут/развернут) сохраняется в localStorage
-- При следующем запуске приложения состояние восстанавливается
-
-## Примеры использования
-
-1. **Быстрое обновление логов**: `Ctrl+R` для получения свежих данных
-2. **Навигация по контейнерам**: `[` `]` для переключения между сервисами
-3. **Освобождение места на экране**: `Ctrl+B` для сворачивания панели
-4. **Работа в Multi View**: `Ctrl+R` обновляет все выбранные контейнеры одновременно
-
-## Автор
-Сергей Антропов - https://devops.org.ru
diff --git a/docker-compose.yml b/docker-compose.yml
index d6fdb53..0a5f0f0 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -3,57 +3,79 @@ services:
build: .
container_name: logboard
environment:
- LOGBOARD_PORT: '9001'
- LOGBOARD_TAIL: '500'
- LOGBOARD_USER: admin
- LOGBOARD_PASS: admin
- COMPOSE_PROJECT_NAME: ''
- LOGBOARD_SNAPSHOT_DIR: /app/snapshots
- LOGBOARD_INDEX_HTML: ./templates/index.html
- TZ_TS: Europe/Moscow
- SECRET_KEY: your-secret-key-here
- ENCRYPTION_KEY: your-encryption-key-here
- LOG_LEVEL: INFO
- LOG_FORMAT: json
- WEB_TITLE: LogBoard+
- WEB_DESCRIPTION: Веб-панель для просмотра логов микросервисов
- WEB_VERSION: 1.0.0
- MAX_CONNECTIONS: '100'
- CONNECTION_TIMEOUT: '30'
- READ_TIMEOUT: '60'
- AUTH_ENABLED: 'true'
- AUTH_METHOD: jwt
- SESSION_TIMEOUT: '3600'
- NOTIFICATIONS_ENABLED: 'false'
- SMTP_HOST: ''
- SMTP_PORT: '587'
- SMTP_USER: ''
- SMTP_PASS: ''
- SMTP_FROM: ''
- # Docker настройки
- DOCKER_HOST: unix:///var/run/docker.sock
- DOCKER_NETWORKS: iaas,infrastructure_iaas
+ # Основные настройки приложения
+ LOGBOARD_PORT: ${LOGBOARD_PORT:-9001}
+ LOGBOARD_TAIL: ${LOGBOARD_TAIL:-500}
+ LOGBOARD_USER: ${LOGBOARD_USER:-admin}
+ LOGBOARD_PASS: ${LOGBOARD_PASS:-admin}
+ LOGBOARD_PROJECTS: ${LOGBOARD_PROJECTS:-}
+ LOGBOARD_SNAPSHOT_DIR: ${LOGBOARD_SNAPSHOT_DIR:-/app/snapshots}
+ LOGBOARD_INDEX_HTML: ${LOGBOARD_INDEX_HTML:-./templates/index.html}
+ TZ_TS: ${TZ_TS:-Europe/Moscow}
+
+ # Настройки безопасности
+ SECRET_KEY: ${SECRET_KEY:-your-secret-key-here}
+ ENCRYPTION_KEY: ${ENCRYPTION_KEY:-your-encryption-key-here}
+
+ # Настройки логирования
+ LOG_LEVEL: ${LOG_LEVEL:-INFO}
+ LOG_FORMAT: ${LOG_FORMAT:-json}
+
+ # Настройки веб-интерфейса
+ WEB_TITLE: ${WEB_TITLE:-LogBoard+}
+ WEB_DESCRIPTION: ${WEB_DESCRIPTION:-Веб-панель для просмотра логов микросервисов}
+ WEB_VERSION: ${WEB_VERSION:-1.0.0}
+
+ # Настройки производительности
+ MAX_CONNECTIONS: ${MAX_CONNECTIONS:-100}
+ CONNECTION_TIMEOUT: ${CONNECTION_TIMEOUT:-30}
+ READ_TIMEOUT: ${READ_TIMEOUT:-60}
+
+ # Настройки аутентификации
+ AUTH_ENABLED: ${AUTH_ENABLED:-true}
+ AUTH_METHOD: ${AUTH_METHOD:-jwt}
+ SESSION_TIMEOUT: ${SESSION_TIMEOUT:-3600}
+
+ # Настройки уведомлений
+ NOTIFICATIONS_ENABLED: ${NOTIFICATIONS_ENABLED:-false}
+ SMTP_HOST: ${SMTP_HOST:-}
+ SMTP_PORT: ${SMTP_PORT:-587}
+ SMTP_USER: ${SMTP_USER:-}
+ SMTP_PASS: ${SMTP_PASS:-}
+ SMTP_FROM: ${SMTP_FROM:-}
+
+ # Настройки Docker
+ DOCKER_HOST: ${DOCKER_HOST:-unix:///var/run/docker.sock}
+ DOCKER_TLS_VERIFY: ${DOCKER_TLS_VERIFY:-}
+ DOCKER_CERT_PATH: ${DOCKER_CERT_PATH:-}
+ DOCKER_NETWORKS: ${DOCKER_NETWORKS:-iaas,infrastructure_iaas}
+
# Настройки фильтрации контейнеров
- LOGBOARD_SKIP_UNHEALTHY: 'true'
- LOGBOARD_CONTAINER_LIST_TIMEOUT: '10'
- LOGBOARD_CONTAINER_INFO_TIMEOUT: '3'
- LOGBOARD_HEALTH_CHECK_TIMEOUT: '2'
+ LOGBOARD_SKIP_UNHEALTHY: ${LOGBOARD_SKIP_UNHEALTHY:-true}
+ LOGBOARD_CONTAINER_LIST_TIMEOUT: ${LOGBOARD_CONTAINER_LIST_TIMEOUT:-10}
+ LOGBOARD_CONTAINER_INFO_TIMEOUT: ${LOGBOARD_CONTAINER_INFO_TIMEOUT:-3}
+ LOGBOARD_HEALTH_CHECK_TIMEOUT: ${LOGBOARD_HEALTH_CHECK_TIMEOUT:-2}
+
# Настройки AJAX обновления
- LOGBOARD_AJAX_UPDATE_INTERVAL: '2000'
+ LOGBOARD_AJAX_UPDATE_INTERVAL: ${LOGBOARD_AJAX_UPDATE_INTERVAL:-2000}
+
ports:
- - 9001:9001
+ - "${LOGBOARD_PORT:-9001}:${LOGBOARD_PORT:-9001}"
+
volumes:
- - /var/run/docker.sock:/var/run/docker.sock:ro
- - ./snapshots:/app/snapshots
- - ./:/app
+ - /var/run/docker.sock:/var/run/docker.sock:ro
+ - ./snapshots:/app/snapshots
+ - ./:/app
+
restart: unless-stopped
user: 0:0
+
networks:
- - default
- - iaas
- - infrastructure_iaas
+ - iaas
+ - infrastructure_iaas
+
+
networks:
- default: {}
iaas:
external: true
infrastructure_iaas:
diff --git a/env.example b/env.example
index 9b9ecb5..a342a16 100644
--- a/env.example
+++ b/env.example
@@ -1,79 +1,159 @@
# LogBoard+ - Переменные окружения
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
+# Версия: 1.0.0
-# Основные настройки приложения
+# =============================================================================
+# ОСНОВНЫЕ НАСТРОЙКИ ПРИЛОЖЕНИЯ
+# =============================================================================
+
+# Порт на котором будет работать веб-интерфейс LogBoard+
LOGBOARD_PORT=9001
-LOGBOARD_TAIL=500
-LOGBOARD_USER=admin
-LOGBOARD_PASS=s3cret-change-me
-# Директория для снимков логов
+# Количество строк логов для отображения по умолчанию (tail)
+LOGBOARD_TAIL=500
+
+# Имя пользователя для входа в систему
+LOGBOARD_USER=admin
+
+# Пароль для входа в систему (обязательно измените в продакшене)
+LOGBOARD_PASS=admin
+
+# Директория для сохранения снимков логов (путь внутри контейнера)
LOGBOARD_SNAPSHOT_DIR=/app/snapshots
-# Путь к HTML шаблону
+# Путь к HTML шаблону главной страницы
LOGBOARD_INDEX_HTML=./templates/index.html
-# Временная зона для временных меток
-TZ_TS=
+# Временная зона для временных меток в логах (например: Europe/Moscow, UTC)
+TZ_TS=Europe/Moscow
+
+# =============================================================================
+# НАСТРОЙКИ DOCKER
+# =============================================================================
# Фильтр по проекту Docker Compose (опционально)
+# Если указано, будут показаны только контейнеры из этого проекта
# COMPOSE_PROJECT_NAME=myproj
# Настройки множественных проектов
# Укажите проекты через запятую для отображения контейнеров из нескольких проектов
+# Если не указано или пустое значение - показываются ВСЕ контейнеры из всех проектов
# LOGBOARD_PROJECTS=project1,project2,project3
-# Настройки Docker
+# Путь к Docker socket для подключения к Docker daemon
DOCKER_HOST=unix:///var/run/docker.sock
+
+# Проверка TLS для Docker (для удаленных Docker hosts)
DOCKER_TLS_VERIFY=
+
+# Путь к сертификатам Docker (для удаленных Docker hosts)
DOCKER_CERT_PATH=
# Настройки Docker сетей (внешние сети для подключения)
# Укажите имена внешних сетей через запятую
DOCKER_NETWORKS=iaas,infrastructure_iaas
-# Настройки безопасности
-# Измените эти значения на свои в продакшене
+# =============================================================================
+# НАСТРОЙКИ БЕЗОПАСНОСТИ
+# =============================================================================
+
+# Секретный ключ для JWT токенов (обязательно измените в продакшене)
SECRET_KEY=your-secret-key-here
+
+# Ключ шифрования для чувствительных данных (обязательно измените в продакшене)
ENCRYPTION_KEY=your-encryption-key-here
-# Настройки логирования
+# =============================================================================
+# НАСТРОЙКИ ЛОГИРОВАНИЯ
+# =============================================================================
+
+# Уровень логирования приложения (DEBUG, INFO, WARNING, ERROR)
LOG_LEVEL=INFO
+
+# Формат логов (json, text)
LOG_FORMAT=json
-# Настройки веб-интерфейса
+# =============================================================================
+# НАСТРОЙКИ ВЕБ-ИНТЕРФЕЙСА
+# =============================================================================
+
+# Заголовок веб-интерфейса
WEB_TITLE=LogBoard+
+
+# Описание веб-интерфейса
WEB_DESCRIPTION=Веб-панель для просмотра логов микросервисов
+
+# Версия веб-интерфейса
WEB_VERSION=1.0.0
-# Настройки производительности
+# =============================================================================
+# НАСТРОЙКИ ПРОИЗВОДИТЕЛЬНОСТИ
+# =============================================================================
+
+# Максимальное количество одновременных подключений
MAX_CONNECTIONS=100
+
+# Таймаут подключения в секундах
CONNECTION_TIMEOUT=30
+
+# Таймаут чтения в секундах
READ_TIMEOUT=60
-# Настройки фильтрации контейнеров
+# =============================================================================
+# НАСТРОЙКИ ФИЛЬТРАЦИИ КОНТЕЙНЕРОВ
+# =============================================================================
+
# Пропускать контейнеры с проблемными health check (true/false)
LOGBOARD_SKIP_UNHEALTHY=true
-# Настройки таймаутов (в секундах)
+# Таймаут получения списка контейнеров в секундах
LOGBOARD_CONTAINER_LIST_TIMEOUT=10
+
+# Таймаут получения информации о контейнере в секундах
LOGBOARD_CONTAINER_INFO_TIMEOUT=3
+
+# Таймаут health check контейнера в секундах
LOGBOARD_HEALTH_CHECK_TIMEOUT=2
-# Настройки аутентификации
-AUTH_ENABLED=true
-AUTH_METHOD=jwt
-SESSION_TIMEOUT=3600
-SECRET_KEY=your-secret-key-here-change-in-production
+# =============================================================================
+# НАСТРОЙКИ АУТЕНТИФИКАЦИИ
+# =============================================================================
-# Настройки уведомлений
+# Включить аутентификацию (true/false)
+AUTH_ENABLED=true
+
+# Метод аутентификации (jwt)
+AUTH_METHOD=jwt
+
+# Время жизни сессии в секундах
+SESSION_TIMEOUT=3600
+
+# =============================================================================
+# НАСТРОЙКИ УВЕДОМЛЕНИЙ
+# =============================================================================
+
+# Включить уведомления по email (true/false)
NOTIFICATIONS_ENABLED=false
+
+# SMTP сервер для отправки уведомлений
SMTP_HOST=
+
+# Порт SMTP сервера
SMTP_PORT=587
+
+# Пользователь SMTP
SMTP_USER=
+
+# Пароль SMTP
SMTP_PASS=
+
+# Email отправителя
SMTP_FROM=
-# Настройки AJAX обновления логов
+# =============================================================================
+# НАСТРОЙКИ AJAX ОБНОВЛЕНИЯ
+# =============================================================================
+
+# Интервал AJAX обновления логов в миллисекундах
LOGBOARD_AJAX_UPDATE_INTERVAL=2000
diff --git a/env_comparison.md b/env_comparison.md
deleted file mode 100644
index 8dc2e17..0000000
--- a/env_comparison.md
+++ /dev/null
@@ -1,153 +0,0 @@
-# Сравнение переменных окружения
-
-## Обзор файлов
-
-- **docker-compose.yml** - переменные окружения, используемые в контейнере
-- **env.example** - пример файла с переменными окружения
-- **.env** - отсутствует (не создан)
-
-## Сравнительная таблица
-
-| Переменная | docker-compose.yml | env.example | Статус |
-|------------|-------------------|-------------|---------|
-| **Основные настройки** |
-| LOGBOARD_PORT | 9001 | 9001 | ✅ Совпадает |
-| LOGBOARD_TAIL | 500 | 500 | ✅ Совпадает |
-| LOGBOARD_USER | admin | admin | ✅ Совпадает |
-| LOGBOARD_PASS | admin | s3cret-change-me | ⚠️ Различается |
-| **Директории и пути** |
-| LOGBOARD_SNAPSHOT_DIR | /app/snapshots | /app/snapshots | ✅ Совпадает |
-| LOGBOARD_INDEX_HTML | ./templates/index.html | ./templates/index.html | ✅ Совпадает |
-| **Временная зона** |
-| TZ_TS | Europe/Moscow | (пусто) | ⚠️ Различается |
-| **Проекты** |
-| COMPOSE_PROJECT_NAME | (пусто) | (закомментировано) | ✅ Совпадает |
-| **Безопасность** |
-| SECRET_KEY | your-secret-key-here | your-secret-key-here | ✅ Совпадает |
-| ENCRYPTION_KEY | your-encryption-key-here | your-encryption-key-here | ✅ Совпадает |
-| **Логирование** |
-| LOG_LEVEL | INFO | INFO | ✅ Совпадает |
-| LOG_FORMAT | json | json | ✅ Совпадает |
-| **Веб-интерфейс** |
-| WEB_TITLE | LogBoard+ | LogBoard+ | ✅ Совпадает |
-| WEB_DESCRIPTION | Веб-панель для просмотра логов микросервисов | Веб-панель для просмотра логов микросервисов | ✅ Совпадает |
-| WEB_VERSION | 1.0.0 | 1.0.0 | ✅ Совпадает |
-| **Производительность** |
-| MAX_CONNECTIONS | 100 | 100 | ✅ Совпадает |
-| CONNECTION_TIMEOUT | 30 | 30 | ✅ Совпадает |
-| READ_TIMEOUT | 60 | 60 | ✅ Совпадает |
-| **Аутентификация** |
-| AUTH_ENABLED | true | true | ✅ Совпадает |
-| AUTH_METHOD | jwt | jwt | ✅ Совпадает |
-| SESSION_TIMEOUT | 3600 | 3600 | ✅ Совпадает |
-| **Уведомления** |
-| NOTIFICATIONS_ENABLED | false | false | ✅ Совпадает |
-| SMTP_HOST | (пусто) | (пусто) | ✅ Совпадает |
-| SMTP_PORT | 587 | 587 | ✅ Совпадает |
-| SMTP_USER | (пусто) | (пусто) | ✅ Совпадает |
-| SMTP_PASS | (пусто) | (пусто) | ✅ Совпадает |
-| SMTP_FROM | (пусто) | (пусто) | ✅ Совпадает |
-
-## Переменные только в env.example
-
-| Переменная | Значение | Описание |
-|------------|----------|----------|
-| DOCKER_HOST | unix:///var/run/docker.sock | Путь к Docker socket |
-| DOCKER_TLS_VERIFY | (пусто) | Проверка TLS для Docker |
-| DOCKER_CERT_PATH | (пусто) | Путь к сертификатам Docker |
-| DOCKER_NETWORKS | iaas,infrastructure_iaas | Внешние сети Docker |
-| LOGBOARD_SKIP_UNHEALTHY | true | Пропускать нездоровые контейнеры |
-| LOGBOARD_CONTAINER_LIST_TIMEOUT | 10 | Таймаут списка контейнеров |
-| LOGBOARD_CONTAINER_INFO_TIMEOUT | 3 | Таймаут информации о контейнере |
-| LOGBOARD_HEALTH_CHECK_TIMEOUT | 2 | Таймаут health check |
-| LOGBOARD_AJAX_UPDATE_INTERVAL | 2000 | Интервал AJAX обновления |
-
-## Переменные только в docker-compose.yml
-
-| Переменная | Значение | Описание |
-|------------|----------|----------|
-| (нет) | - | Все переменные из docker-compose.yml есть в env.example |
-
-## Рекомендации
-
-### 1. Создать .env файл
-```bash
-cp env.example .env
-```
-
-### 2. Обновить docker-compose.yml
-Добавить недостающие переменные из env.example:
-
-```yaml
-environment:
- # Существующие переменные...
-
- # Добавить недостающие:
- DOCKER_HOST: unix:///var/run/docker.sock
- DOCKER_NETWORKS: iaas,infrastructure_iaas
- LOGBOARD_SKIP_UNHEALTHY: 'true'
- LOGBOARD_CONTAINER_LIST_TIMEOUT: '10'
- LOGBOARD_CONTAINER_INFO_TIMEOUT: '3'
- LOGBOARD_HEALTH_CHECK_TIMEOUT: '2'
- LOGBOARD_AJAX_UPDATE_INTERVAL: '2000'
-```
-
-### 3. Исправить различия
-- **LOGBOARD_PASS**: В docker-compose.yml используется `admin`, в env.example - `s3cret-change-me`
-- **TZ_TS**: В docker-compose.yml установлено `Europe/Moscow`, в env.example - пусто
-
-### 4. Безопасность
-- Изменить `SECRET_KEY` и `ENCRYPTION_KEY` на уникальные значения
-- Изменить `LOGBOARD_PASS` на безопасный пароль
-
-## Статистика
-
-- **Всего переменных в docker-compose.yml**: 25
-- **Всего переменных в env.example**: 34
-- **Совпадающих переменных**: 23
-- **Различающихся переменных**: 2
-- **Отсутствующих в docker-compose.yml**: 9
-- **Отсутствующих в env.example**: 0
-
-## Вывод
-
-В целом, файлы хорошо синхронизированы, но есть несколько важных различий:
-
-1. **Отсутствуют переменные** в docker-compose.yml (особенно новые для AJAX обновления)
-2. **Различаются значения** для пароля и временной зоны
-3. **Отсутствует .env файл** для локальной настройки
-
-## Выполненные действия
-
-✅ **Создан .env файл** на основе env.example
-✅ **Обновлен docker-compose.yml** - добавлены недостающие переменные:
- - DOCKER_HOST
- - DOCKER_NETWORKS
- - LOGBOARD_SKIP_UNHEALTHY
- - LOGBOARD_CONTAINER_LIST_TIMEOUT
- - LOGBOARD_CONTAINER_INFO_TIMEOUT
- - LOGBOARD_HEALTH_CHECK_TIMEOUT
- - LOGBOARD_AJAX_UPDATE_INTERVAL
-
-## Рекомендации для завершения синхронизации
-
-1. **Обновить .env файл** (вручную):
- ```bash
- # Изменить пароль на admin (как в docker-compose.yml)
- LOGBOARD_PASS=admin
-
- # Добавить временную зону (как в docker-compose.yml)
- TZ_TS=Europe/Moscow
- ```
-
-2. **Проверить безопасность**:
- - Изменить SECRET_KEY и ENCRYPTION_KEY на уникальные значения
- - Рассмотреть изменение LOGBOARD_PASS на более безопасный пароль
-
-3. **Использовать .env файл** в docker-compose.yml:
- ```yaml
- env_file:
- - .env
- ```
-
-Теперь все файлы синхронизированы и готовы к использованию!
diff --git a/templates/index.html b/templates/index.html
index e68c536..2b2fd6f 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -52,6 +52,375 @@ a{color:var(--link)}
display: none;
}
+/* Миникарточки контейнеров для свернутого sidebar */
+.sidebar.collapsed .mini-container-list {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ padding: 8px 4px;
+ overflow-y: auto;
+ flex: 1;
+ scrollbar-width: thin;
+ scrollbar-color: var(--border) transparent;
+}
+
+/* Всплывающие подсказки для миникарточек */
+.mini-container-tooltip {
+ position: absolute;
+ background: var(--panel);
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ padding: 12px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
+ z-index: 1000;
+ pointer-events: auto;
+ opacity: 0;
+ visibility: hidden;
+ transition: opacity 0.2s ease, visibility 0.2s ease;
+ max-width: 280px;
+ min-width: 200px;
+ font-size: 12px;
+ line-height: 1.4;
+}
+
+.mini-container-tooltip.show {
+ opacity: 1;
+ visibility: visible;
+}
+
+.mini-container-tooltip-header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 8px;
+ font-weight: 600;
+ color: var(--fg);
+}
+
+.mini-container-tooltip-icon {
+ font-size: 14px;
+ color: var(--accent);
+}
+
+.mini-container-tooltip-name {
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--fg);
+ margin-bottom: 4px;
+}
+
+.mini-container-tooltip-service {
+ font-size: 11px;
+ color: var(--muted);
+ margin-bottom: 6px;
+}
+
+.mini-container-tooltip-status {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 10px;
+ margin-bottom: 4px;
+}
+
+.mini-container-tooltip-status-indicator {
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+}
+
+.mini-container-tooltip-status-indicator.running { background: var(--ok); }
+.mini-container-tooltip-status-indicator.stopped { background: var(--err); }
+.mini-container-tooltip-status-indicator.paused { background: var(--warn); }
+
+.mini-container-tooltip-port {
+ font-size: 10px;
+ color: var(--muted);
+ margin-bottom: 4px;
+}
+
+.mini-container-tooltip-url {
+ font-size: 10px;
+ color: var(--accent);
+ text-decoration: none;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ padding: 2px 4px;
+ border-radius: 4px;
+ transition: all 0.2s ease;
+ cursor: pointer;
+}
+
+.mini-container-tooltip-url:hover {
+ text-decoration: underline;
+ color: var(--link);
+ background: var(--chip);
+}
+
+
+
+/* Дополнительные стили для светлой темы */
+:root[data-theme="light"] .mini-container-tooltip {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ border-color: var(--border);
+}
+
+/* Анимация появления подсказки */
+@keyframes tooltipFadeIn {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+.mini-container-tooltip.show {
+ opacity: 1;
+ visibility: visible;
+ animation: tooltipFadeIn 0.2s ease;
+}
+
+/* Позиционирование подсказки */
+.mini-container-tooltip.top {
+ bottom: calc(100% + 8px);
+ left: 50%;
+ transform: translateX(-50%);
+}
+
+.mini-container-tooltip.bottom {
+ top: calc(100% + 8px);
+ left: 50%;
+ transform: translateX(-50%);
+}
+
+.mini-container-tooltip.left {
+ right: calc(100% + 8px);
+ top: 50%;
+ transform: translateY(-50%);
+}
+
+.mini-container-tooltip.right {
+ left: calc(100% + 8px);
+ top: 50%;
+ transform: translateY(-50%);
+}
+
+/* Стрелка подсказки */
+.mini-container-tooltip::before {
+ content: '';
+ position: absolute;
+ width: 0;
+ height: 0;
+ border: 4px solid transparent;
+}
+
+.mini-container-tooltip.top::before {
+ bottom: -8px;
+ left: 50%;
+ transform: translateX(-50%);
+ border-top-color: var(--panel);
+}
+
+.mini-container-tooltip.bottom::before {
+ top: -8px;
+ left: 50%;
+ transform: translateX(-50%);
+ border-bottom-color: var(--panel);
+}
+
+.mini-container-tooltip.left::before {
+ right: -8px;
+ top: 50%;
+ transform: translateY(-50%);
+ border-left-color: var(--panel);
+}
+
+.mini-container-tooltip.right::before {
+ left: -8px;
+ top: 50%;
+ transform: translateY(-50%);
+ border-right-color: var(--panel);
+}
+
+.sidebar.collapsed .mini-container-list::-webkit-scrollbar {
+ width: 4px;
+}
+
+.sidebar.collapsed .mini-container-list::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+.sidebar.collapsed .mini-container-list::-webkit-scrollbar-thumb {
+ background: var(--border);
+ border-radius: 2px;
+}
+
+.sidebar.collapsed .mini-container-list::-webkit-scrollbar-thumb:hover {
+ background: var(--muted);
+}
+
+.sidebar:not(.collapsed) .mini-container-list {
+ display: none;
+}
+
+.mini-container-item {
+ background: var(--chip);
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ padding: 6px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ position: relative;
+ min-height: 32px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.mini-container-item:hover {
+ background: var(--tab-active);
+ border-color: var(--accent);
+ transform: scale(1.05);
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
+}
+
+
+
+.mini-container-item.active {
+ background: var(--tab-active);
+ border-color: var(--accent);
+ color: var(--accent);
+ box-shadow: 0 0 0 1px var(--accent);
+}
+
+.mini-container-item.active:hover {
+ transform: scale(1.05);
+ box-shadow: 0 0 0 1px var(--accent), 0 2px 8px rgba(0, 0, 0, 0.2);
+}
+
+.mini-container-item.active::before {
+ content: "";
+ position: absolute;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ width: 2px;
+ background: var(--accent);
+ border-radius: 0 1px 1px 0;
+}
+
+.mini-container-item.selected {
+ border-color: var(--ok);
+ box-shadow: 0 0 0 1px var(--ok);
+ background: var(--tab-active);
+}
+
+.mini-container-item.selected .mini-container-icon {
+ color: var(--ok);
+}
+
+.mini-container-item.selected .mini-container-name {
+ color: var(--ok);
+}
+
+.mini-container-item.selected:hover {
+ transform: scale(1.05);
+ box-shadow: 0 0 0 1px var(--ok), 0 2px 8px rgba(0, 0, 0, 0.2);
+}
+
+/* Индикатор выбора для миникарточек */
+.mini-container-item.selected::after {
+ content: '';
+ position: absolute;
+ top: -2px;
+ right: -2px;
+ width: 8px;
+ height: 8px;
+ background: var(--ok);
+ border-radius: 50%;
+ border: 1px solid var(--panel);
+ z-index: 10;
+ animation: selectionPulse 0.3s ease;
+}
+
+@keyframes selectionPulse {
+ 0% {
+ transform: scale(0);
+ opacity: 0;
+ }
+ 50% {
+ transform: scale(1.2);
+ opacity: 1;
+ }
+ 100% {
+ transform: scale(1);
+ opacity: 1;
+ }
+}
+
+.mini-container-icon {
+ font-size: 12px;
+ margin-bottom: 2px;
+ color: var(--muted);
+}
+
+.mini-container-item.active .mini-container-icon {
+ color: var(--accent);
+}
+
+.mini-container-name {
+ font-size: 8px;
+ font-weight: 500;
+ text-align: center;
+ line-height: 1.2;
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--fg);
+}
+
+.mini-container-item.active .mini-container-name {
+ color: var(--accent);
+}
+
+.mini-container-status {
+ position: absolute;
+ top: 2px;
+ right: 2px;
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background: var(--ok);
+}
+
+.mini-container-status.running { background: var(--ok); }
+.mini-container-status.stopped { background: var(--err); }
+.mini-container-status.paused { background: var(--warn); }
+
+/* Плейсхолдер для миникарточек */
+.mini-container-item.placeholder {
+ opacity: 0.5;
+ cursor: default;
+ background: var(--chip);
+ border-color: var(--border);
+}
+
+.mini-container-item.placeholder:hover {
+ transform: none;
+ background: var(--chip);
+ border-color: var(--border);
+ box-shadow: none;
+}
+
+.mini-container-item.placeholder .mini-container-icon,
+.mini-container-item.placeholder .mini-container-name {
+ color: var(--muted);
+}
+
.sidebar.collapsed .sidebar-header {
display: flex;
align-items: center;
@@ -442,6 +811,124 @@ a{color:var(--link)}
border-color: var(--accent);
}
+/* Tooltip для кнопки помощи */
+.help-tooltip {
+ position: absolute;
+ background: var(--bg);
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ padding: 16px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ z-index: 1000;
+ max-width: 320px;
+ font-size: 13px;
+ line-height: 1.5;
+ opacity: 0;
+ visibility: hidden;
+ transition: all 0.2s ease;
+ pointer-events: none;
+}
+
+.help-tooltip.show {
+ opacity: 1;
+ visibility: visible;
+ pointer-events: auto;
+}
+
+.help-tooltip-header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 12px;
+ padding-bottom: 8px;
+ border-bottom: 1px solid var(--border);
+}
+
+.help-tooltip-logo {
+ width: 24px;
+ height: 24px;
+ background: var(--accent);
+ border-radius: 4px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #0b0d12;
+ font-weight: bold;
+ font-size: 12px;
+}
+
+.help-tooltip-title {
+ font-weight: 600;
+ color: var(--fg);
+ font-size: 14px;
+}
+
+.help-tooltip-section {
+ margin-bottom: 12px;
+}
+
+.help-tooltip-section:last-child {
+ margin-bottom: 0;
+}
+
+.help-tooltip-section-title {
+ font-weight: 600;
+ color: var(--fg);
+ margin-bottom: 6px;
+ font-size: 12px;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.help-tooltip-content {
+ color: var(--muted);
+ font-size: 12px;
+ line-height: 1.4;
+}
+
+.help-tooltip-hotkeys {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ gap: 8px 12px;
+ font-size: 11px;
+ margin-top: 4px;
+}
+
+.help-tooltip-hotkey {
+ background: var(--chip);
+ border: 1px solid var(--border);
+ border-radius: 4px;
+ padding: 2px 6px;
+ font-family: 'Courier New', monospace;
+ font-weight: 600;
+ color: var(--fg);
+ text-align: center;
+ min-width: 40px;
+}
+
+.help-tooltip-description {
+ color: var(--muted);
+ font-size: 11px;
+}
+
+.help-tooltip-author {
+ margin-top: 8px;
+ padding-top: 8px;
+ border-top: 1px solid var(--border);
+ font-size: 11px;
+ color: var(--muted);
+ text-align: center;
+}
+
+.help-tooltip-author a {
+ color: var(--accent);
+ text-decoration: none;
+}
+
+.help-tooltip-author a:hover {
+ text-decoration: underline;
+}
+
/* Специальный hover эффект для кнопки options с цветом accent */
.options-btn:hover {
background: var(--accent) !important; /* Цвет логотипа */
@@ -2030,6 +2517,39 @@ footer{position:fixed;right:10px;bottom:10px;opacity:.6;font-size:11px}
+
+
+
@@ -2138,6 +2658,16 @@ footer{position:fixed;right:10px;bottom:10px;opacity:.6;font-size:11px}
+
+
+
@@ -2843,7 +3373,15 @@ function buildTabs(){
// Modern container list
els.containerList.innerHTML = '';
+
+ // Миникарточки контейнеров
+ const miniContainerList = document.getElementById('miniContainerList');
+ if (miniContainerList) {
+ miniContainerList.innerHTML = '';
+ }
+
state.services.forEach(svc => {
+ // Создаем обычную карточку контейнера
const item = document.createElement('div');
item.className = 'container-item';
if (state.current && svc.id === state.current.id) {
@@ -2889,6 +3427,73 @@ function buildTabs(){
await switchToSingle(svc);
};
els.containerList.appendChild(item);
+
+ // Создаем миникарточку контейнера
+ if (miniContainerList) {
+ const miniItem = document.createElement('div');
+ miniItem.className = 'mini-container-item';
+ if (state.current && svc.id === state.current.id) {
+ miniItem.classList.add('active');
+ }
+ miniItem.setAttribute('data-cid', svc.id);
+
+ // Сокращаем имя для миникарточки
+ const shortName = svc.name.length > 8 ? svc.name.substring(0, 6) + '..' : svc.name;
+
+ miniItem.innerHTML = `
+
+
+
+ ${escapeHtml(shortName)}
+
+ `;
+
+ // Добавляем обработчики для всплывающих подсказок
+ miniItem.addEventListener('mouseenter', (e) => {
+ showMiniContainerTooltip(e, svc);
+ });
+
+ miniItem.addEventListener('mouseleave', () => {
+ // Добавляем задержку перед скрытием подсказки
+ const tooltip = document.getElementById('miniContainerTooltip');
+ if (tooltip) {
+ tooltip.hideTimer = setTimeout(() => {
+ if (tooltip && !tooltip.matches(':hover')) {
+ hideMiniContainerTooltip();
+ }
+ }, 150);
+ }
+ });
+
+ // Обработчик клика для миникарточек с поддержкой Shift+клик и Ctrl+клик
+ miniItem.addEventListener('click', async (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ if (e.shiftKey && lastSelectedContainerId && lastSelectedContainerId !== svc.id) {
+ // Shift+клик с предыдущим выбором - диапазонный выбор
+ console.log('Shift+клик для диапазонного выбора:', lastSelectedContainerId, 'to', svc.id);
+ selectContainerRange(lastSelectedContainerId, svc.id);
+ } else if (e.shiftKey) {
+ // Shift+клик - добавляем/убираем из мультивыбора
+ console.log('Shift+клик на миникарточку:', svc.name);
+ toggleContainerSelection(svc.id);
+ lastSelectedContainerId = svc.id;
+ } else if (e.ctrlKey || e.metaKey) {
+ // Ctrl/Cmd+клик - добавляем/убираем из мультивыбора
+ console.log('Ctrl+клик на миникарточку:', svc.name);
+ toggleContainerSelection(svc.id);
+ lastSelectedContainerId = svc.id;
+ } else {
+ // Обычный клик - переключаемся в single view
+ console.log('Обычный клик на миникарточку:', svc.name);
+ lastSelectedContainerId = svc.id;
+ await switchToSingle(svc);
+ }
+ });
+
+ miniContainerList.appendChild(miniItem);
+ }
});
}
@@ -3195,7 +3800,7 @@ function toggleContainerSelection(containerId) {
function updateContainerSelectionUI() {
console.log('updateContainerSelectionUI called, selected containers:', state.selectedContainers);
- // Обновляем чекбоксы
+ // Обновляем чекбоксы и обычные карточки контейнеров
const checkboxes = document.querySelectorAll('.container-checkbox');
console.log('Found checkboxes:', checkboxes.length);
@@ -3216,6 +3821,17 @@ function updateContainerSelectionUI() {
}
});
+ // Обновляем миникарточки контейнеров
+ const miniContainerItems = document.querySelectorAll('.mini-container-item');
+ miniContainerItems.forEach(miniItem => {
+ const containerId = miniItem.getAttribute('data-cid');
+ if (state.selectedContainers.includes(containerId)) {
+ miniItem.classList.add('selected');
+ } else {
+ miniItem.classList.remove('selected');
+ }
+ });
+
// Обновляем single-view-title если он существует
const singleViewTitle = document.getElementById('singleViewTitle');
if (singleViewTitle && state.selectedContainers.length === 1) {
@@ -3246,6 +3862,283 @@ function updateContainerSelectionUI() {
updateLogLevelsVisibility();
}
+// Функция для обновления активного состояния контейнеров в UI
+function updateActiveContainerUI(activeContainerId) {
+ console.log('updateActiveContainerUI called for:', activeContainerId);
+
+ // Обновляем обычные карточки контейнеров
+ const containerItems = document.querySelectorAll('.container-item');
+ containerItems.forEach(item => {
+ const containerId = item.getAttribute('data-cid');
+ if (activeContainerId && containerId === activeContainerId) {
+ item.classList.add('active');
+ } else {
+ item.classList.remove('active');
+ }
+ });
+
+ // Обновляем миникарточки контейнеров
+ const miniContainerItems = document.querySelectorAll('.mini-container-item');
+ miniContainerItems.forEach(miniItem => {
+ const containerId = miniItem.getAttribute('data-cid');
+ if (activeContainerId && containerId === activeContainerId) {
+ miniItem.classList.add('active');
+ } else {
+ miniItem.classList.remove('active');
+ }
+ });
+
+ // Обновляем legacy tabs
+ const tabButtons = document.querySelectorAll('.tab');
+ tabButtons.forEach(tab => {
+ const tabText = tab.textContent;
+ const service = state.services.find(s =>
+ (s.project ? `[${s.project}] ` : '') + (s.service || s.name) === tabText
+ );
+ if (service && activeContainerId && service.id === activeContainerId) {
+ tab.classList.add('active');
+ } else {
+ tab.classList.remove('active');
+ }
+ });
+}
+
+// Функции для всплывающих подсказок миникарточек
+function showMiniContainerTooltip(event, service) {
+ // Удаляем существующую подсказку
+ hideMiniContainerTooltip();
+
+ // Очищаем все таймеры скрытия
+ const existingTooltips = document.querySelectorAll('.mini-container-tooltip');
+ existingTooltips.forEach(tooltip => {
+ if (tooltip.hideTimer) {
+ clearTimeout(tooltip.hideTimer);
+ }
+ });
+
+ // Создаем новую подсказку
+ const tooltip = document.createElement('div');
+ tooltip.className = 'mini-container-tooltip';
+ tooltip.id = 'miniContainerTooltip';
+
+ const statusClass = service.status === 'running' ? 'running' :
+ service.status === 'stopped' ? 'stopped' : 'paused';
+
+ tooltip.innerHTML = `
+
+ ${escapeHtml(service.name)}
+ ${escapeHtml(service.service || service.name)} • ${escapeHtml(service.project || 'standalone')}
+
+
+ ${escapeHtml(service.status)}
+
+ ${service.status === 'running' && service.host_port ? `Порт: ${escapeHtml(service.host_port)}
` : ''}
+ ${service.url ? ` Открыть сайт` : ''}
+
+ `;
+
+ // Добавляем подсказку в body
+ document.body.appendChild(tooltip);
+
+ // Позиционируем подсказку сразу
+ positionTooltip(event, tooltip);
+
+ // Показываем подсказку сразу
+ tooltip.classList.add('show');
+
+ // Добавляем обработчики для подсказки
+ tooltip.addEventListener('mouseenter', () => {
+ // Останавливаем таймер скрытия при наведении на подсказку
+ clearTimeout(tooltip.hideTimer);
+ });
+
+ tooltip.addEventListener('mouseleave', () => {
+ // Скрываем подсказку при уходе курсора с неё
+ hideMiniContainerTooltip();
+ });
+
+ // Добавляем обработчик для клика по ссылке
+ const link = tooltip.querySelector('.mini-container-tooltip-url');
+ if (link) {
+ link.addEventListener('click', (e) => {
+ e.stopPropagation();
+ // Ссылка откроется в новой вкладке благодаря target="_blank"
+ });
+ }
+}
+
+function hideMiniContainerTooltip() {
+ const tooltip = document.getElementById('miniContainerTooltip');
+ if (tooltip) {
+ // Очищаем таймер скрытия
+ if (tooltip.hideTimer) {
+ clearTimeout(tooltip.hideTimer);
+ }
+ tooltip.remove();
+ }
+}
+
+function positionTooltip(event, tooltip) {
+ const rect = event.target.getBoundingClientRect();
+ const tooltipRect = tooltip.getBoundingClientRect();
+ const viewportWidth = window.innerWidth;
+ const viewportHeight = window.innerHeight;
+
+ // Определяем позицию по умолчанию (справа от миникарточки)
+ let position = 'right';
+ let left = rect.right + 8;
+
+ // Всегда центрируем по высоте относительно миникарточки
+ let top = rect.top + (rect.height / 2) - (tooltipRect.height / 2);
+
+ // Проверяем, помещается ли подсказка справа
+ if (left + tooltipRect.width > viewportWidth - 10) {
+ // Не помещается справа, пробуем слева
+ position = 'left';
+ left = rect.left - tooltipRect.width - 8;
+ }
+
+ // Проверяем, помещается ли подсказка по вертикали
+ if (top < 10) {
+ // Не помещается сверху, выравниваем по верху с отступом
+ top = 10;
+ } else if (top + tooltipRect.height > viewportHeight - 10) {
+ // Не помещается снизу, выравниваем по низу с отступом
+ top = viewportHeight - tooltipRect.height - 10;
+ }
+
+ // Применяем позицию сразу, без анимации
+ tooltip.className = `mini-container-tooltip ${position}`;
+ tooltip.style.left = `${left}px`;
+ tooltip.style.top = `${top}px`;
+
+ // Принудительно применяем стили без анимации
+ tooltip.style.transition = 'none';
+ // Сбрасываем transition после применения позиции
+ setTimeout(() => {
+ tooltip.style.transition = '';
+ }, 10);
+}
+
+// Функции для help tooltip
+function showHelpTooltip() {
+ const helpBtn = document.getElementById('helpBtn');
+ const helpTooltip = document.getElementById('helpTooltip');
+
+ if (!helpBtn || !helpTooltip) return;
+
+ // Позиционируем tooltip относительно кнопки
+ positionHelpTooltip(helpBtn, helpTooltip);
+
+ // Показываем tooltip
+ helpTooltip.classList.add('show');
+}
+
+function hideHelpTooltip() {
+ const helpTooltip = document.getElementById('helpTooltip');
+ if (helpTooltip) {
+ helpTooltip.classList.remove('show');
+ }
+}
+
+function positionHelpTooltip(button, tooltip) {
+ const buttonRect = button.getBoundingClientRect();
+ const tooltipRect = tooltip.getBoundingClientRect();
+ const viewportWidth = window.innerWidth;
+ const viewportHeight = window.innerHeight;
+
+ // Позиционируем tooltip справа от кнопки по умолчанию
+ let left = buttonRect.right + 8;
+ let top = buttonRect.top + (buttonRect.height / 2) - (tooltipRect.height / 2);
+
+ // Проверяем, помещается ли tooltip справа
+ if (left + tooltipRect.width > viewportWidth - 10) {
+ // Не помещается справа, позиционируем слева
+ left = buttonRect.left - tooltipRect.width - 8;
+ }
+
+ // Проверяем, помещается ли tooltip по вертикали
+ if (top < 10) {
+ top = 10;
+ } else if (top + tooltipRect.height > viewportHeight - 10) {
+ top = viewportHeight - tooltipRect.height - 10;
+ }
+
+ // Применяем позицию
+ tooltip.style.left = `${left}px`;
+ tooltip.style.top = `${top}px`;
+}
+
+// Глобальные переменные для выбора контейнеров
+let lastSelectedContainerId = null;
+
+// Функция для диапазонного выбора контейнеров
+function selectContainerRange(startId, endId) {
+ console.log('selectContainerRange:', startId, 'to', endId);
+
+ const miniContainerItems = document.querySelectorAll('.mini-container-item');
+ const containerIds = Array.from(miniContainerItems).map(item => item.getAttribute('data-cid'));
+
+ const startIndex = containerIds.indexOf(startId);
+ const endIndex = containerIds.indexOf(endId);
+
+ if (startIndex === -1 || endIndex === -1) {
+ console.error('Container not found in range selection');
+ return;
+ }
+
+ const minIndex = Math.min(startIndex, endIndex);
+ const maxIndex = Math.max(startIndex, endIndex);
+
+ // Выбираем все контейнеры в диапазоне
+ for (let i = minIndex; i <= maxIndex; i++) {
+ const containerId = containerIds[i];
+ if (!state.selectedContainers.includes(containerId)) {
+ state.selectedContainers.push(containerId);
+ }
+ }
+
+ // Обновляем UI
+ updateContainerSelectionUI();
+ updateMultiViewMode();
+}
+
+// Глобальные обработчики для подсказок
+document.addEventListener('click', (event) => {
+ const tooltip = document.getElementById('miniContainerTooltip');
+ if (tooltip && !tooltip.contains(event.target) && !event.target.closest('.mini-container-item')) {
+ hideMiniContainerTooltip();
+ }
+
+ // Скрытие help tooltip при клике вне его
+ const helpTooltip = document.getElementById('helpTooltip');
+ const helpBtn = document.getElementById('helpBtn');
+ if (helpTooltip && !helpTooltip.contains(event.target) && !helpBtn.contains(event.target)) {
+ hideHelpTooltip();
+ }
+});
+
+window.addEventListener('resize', () => {
+ hideMiniContainerTooltip();
+ hideHelpTooltip();
+});
+
+window.addEventListener('scroll', () => {
+ hideMiniContainerTooltip();
+ hideHelpTooltip();
+});
+
+// Обработчик для скрытия подсказки при клике на ссылку
+document.addEventListener('click', (event) => {
+ if (event.target.closest('.mini-container-tooltip-url')) {
+ // Не скрываем подсказку при клике на ссылку
+ event.stopPropagation();
+ }
+});
+
// Функция для сохранения выбранного контейнера в localStorage
function saveSelectedContainer(containerId) {
if (containerId) {
@@ -3367,7 +4260,8 @@ async function updateMultiViewMode() {
}
}
-
+ // Очищаем активное состояние всех контейнеров
+ updateActiveContainerUI(null);
}
console.log(`Multi-view mode updated: multiViewMode = ${state.multiViewMode}`);
@@ -3479,6 +4373,9 @@ async function setupMultiView() {
// Применяем настройки wrap lines
applyWrapSettings();
+ // Очищаем активное состояние всех контейнеров в мультипросмотре
+ updateActiveContainerUI(null);
+
// Принудительно обновляем стили логов для multi-view
setTimeout(() => {
updateLogStyles();
@@ -5027,6 +5924,9 @@ async function switchToSingle(svc){
// Сохраняем режим просмотра в localStorage
saveViewMode(false, [svc.id]);
+ // Обновляем активное состояние в UI
+ updateActiveContainerUI(svc.id);
+
// Сохраняем состояние кнопок loglevels в localStorage
saveLogLevelsState();
@@ -6190,6 +7090,41 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
+ // Обработчик для кнопки помощи
+ if (els.helpBtn) {
+ const helpTooltip = document.getElementById('helpTooltip');
+ let tooltipTimeout;
+
+ // Показ tooltip при наведении
+ els.helpBtn.addEventListener('mouseenter', () => {
+ clearTimeout(tooltipTimeout);
+ tooltipTimeout = setTimeout(() => {
+ showHelpTooltip();
+ }, 300); // Задержка 300мс перед показом
+ });
+
+ // Скрытие tooltip при уходе мыши
+ els.helpBtn.addEventListener('mouseleave', () => {
+ clearTimeout(tooltipTimeout);
+ tooltipTimeout = setTimeout(() => {
+ hideHelpTooltip();
+ }, 200); // Задержка 200мс перед скрытием
+ });
+
+ // Показ tooltip при наведении на сам tooltip
+ helpTooltip.addEventListener('mouseenter', () => {
+ clearTimeout(tooltipTimeout);
+ });
+
+ // Скрытие tooltip при уходе мыши с tooltip
+ helpTooltip.addEventListener('mouseleave', () => {
+ clearTimeout(tooltipTimeout);
+ tooltipTimeout = setTimeout(() => {
+ hideHelpTooltip();
+ }, 200);
+ });
+ }
+
// Инициализируем стили логов при загрузке страницы
updateLogStyles();
@@ -6422,7 +7357,7 @@ window.addEventListener('resize', () => {
}
});
-// Hotkeys: [ ] — navigation between containers, Ctrl+R/Ctrl+K — refresh logs
+// Hotkeys: [ ] х ъ — navigation between containers, Ctrl/Cmd+R/K — refresh logs, Ctrl/Cmd+B/И — toggle sidebar
window.addEventListener('keydown', async (e)=>{
// Проверяем, не находится ли фокус в поле ввода
const activeElement = document.activeElement;
@@ -6437,27 +7372,27 @@ window.addEventListener('keydown', async (e)=>{
return;
}
- // Навигация между контейнерами
- if (e.key==='[' || (e.ctrlKey && e.key==='ArrowLeft')){
+ // Навигация между контейнерами по [ ] х ъ
+ if (e.key==='[' || e.key==='х'){
e.preventDefault();
const idx = state.services.findIndex(s=> s.id===state.current?.id);
if (idx>0) await switchToSingle(state.services[idx-1]);
}
- if (e.key===']' || (e.ctrlKey && e.key==='ArrowRight')){
+ if (e.key===']' || e.key==='ъ'){
e.preventDefault();
const idx = state.services.findIndex(s=> s.id===state.current?.id);
if (idx>=0 && idx{
console.log(`AJAX Update: Интервал обновления установлен на ${ajaxUpdateIntervalMs}ms`);
- // Останавливаем AJAX обновление при смене контейнера
+ // НЕ останавливаем AJAX обновление при смене контейнера
const originalSwitchToSingle = window.switchToSingle;
window.switchToSingle = function(containerId) {
- disableAjaxLogUpdate();
// Очищаем состояние для всех контейнеров
containerStates.clear();
return originalSwitchToSingle.call(this, containerId);
};
- // Останавливаем AJAX обновление при переключении в multi-view
+ // НЕ останавливаем AJAX обновление при переключении в multi-view
const originalSwitchToMultiView = window.switchToMultiView;
window.switchToMultiView = function() {
- disableAjaxLogUpdate();
// Очищаем состояние для всех контейнеров
containerStates.clear();
return originalSwitchToMultiView.call(this);