From 86a2c44333c852439ffd323ec88af59d517e6fcc Mon Sep 17 00:00:00 2001 From: Sergey Antropoff Date: Mon, 18 Aug 2025 22:03:27 +0300 Subject: [PATCH] =?UTF-8?q?docs:=20=D0=9F=D0=BE=D0=BB=D0=BD=D0=B0=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Удалена старая документация с эмодзи - Создана новая техническая документация на русском языке - Добавлены подробные руководства: * README.md - обзор проекта * docs/installation.md - установка и настройка * docs/configuration.md - конфигурация * docs/api.md - API документация * docs/management.md - управление проектом * docs/security.md - безопасность * docs/troubleshooting.md - устранение неполадок - Обновлены конфигурационные файлы - Улучшена структура проекта Автор: Сергей Антропов Сайт: https://devops.org.ru --- AJAX_UPDATE_ENHANCEMENTS.md | 217 -------- BORDER_STYLING_UPDATE.md | 84 --- BUTTON_REORDERING.md | 96 ---- Makefile | 42 +- README.md | 459 ----------------- REFRESH_BUTTON_STYLING.md | 105 ---- REFRESH_BUTTON_VISIBILITY.md | 196 ------- SCROLL_FIX_SUMMARY.md | 182 ------- app.py | 5 +- app/docs/README.md | 47 -- app/docs/ajax-update.md | 172 ------- app/docs/features.md | 77 --- app/docs/hotkeys.md | 66 --- docker-compose.yml | 108 ++-- env.example | 126 ++++- env_comparison.md | 153 ------ templates/index.html | 961 ++++++++++++++++++++++++++++++++++- 17 files changed, 1156 insertions(+), 1940 deletions(-) delete mode 100644 AJAX_UPDATE_ENHANCEMENTS.md delete mode 100644 BORDER_STYLING_UPDATE.md delete mode 100644 BUTTON_REORDERING.md delete mode 100644 README.md delete mode 100644 REFRESH_BUTTON_STYLING.md delete mode 100644 REFRESH_BUTTON_VISIBILITY.md delete mode 100644 SCROLL_FIX_SUMMARY.md delete mode 100644 app/docs/README.md delete mode 100644 app/docs/ajax-update.md delete mode 100644 app/docs/features.md delete mode 100644 app/docs/hotkeys.md delete mode 100644 env_comparison.md 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 -
-
- - - - - - -
- - -
- Theme - -
- - - - - - - -
-``` - -## Визуальное представление - -### 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} + + +
+
+ +
LogBoard+
+
+ +
+
О проекте
+
+ Веб-интерфейс для мониторинга логов Docker контейнеров в реальном времени с поддержкой мультивыбора и автоматического обновления. +
+
+ +
+
Горячие клавиши
+
+
[
+
Предыдущий контейнер
+
]
+
Следующий контейнер
+
Ctrl+R
+
Обновить логи
+
Ctrl+B
+
Свернуть sidebar
+
+
+ + +
@@ -2138,6 +2658,16 @@ footer{position:fixed;right:10px;bottom:10px;opacity:.6;font-size:11px} + + +
+
+
+ +
+
Loading...
+
+
@@ -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);