Move excluded containers to separate sidebar section and improve UI

This commit is contained in:
Сергей Антропов
2025-08-16 16:31:15 +03:00
parent f3e1966f3e
commit 293e9c8cba
3 changed files with 682 additions and 70 deletions

184
app.py
View File

@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
import asyncio
import base64
import json
import os
import re
from typing import Optional, List, Dict
@@ -52,44 +53,95 @@ def verify_ws_token(token: str) -> bool:
return raw == f"{BASIC_USER}:{BASIC_PASS}"
# ---------- DOCKER HELPERS ----------
def load_excluded_containers() -> List[str]:
"""
Загружает список исключенных контейнеров из JSON файла
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
try:
with open("excluded_containers.json", "r", encoding="utf-8") as f:
data = json.load(f)
return data.get("excluded_containers", [])
except FileNotFoundError:
print("⚠️ Файл excluded_containers.json не найден, используем пустой список")
return []
except json.JSONDecodeError as e:
print(f"❌ Ошибка парсинга excluded_containers.json: {e}")
return []
except Exception as e:
print(f"❌ Ошибка загрузки excluded_containers.json: {e}")
return []
def save_excluded_containers(containers: List[str]) -> bool:
"""
Сохраняет список исключенных контейнеров в JSON файл
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
try:
data = {
"excluded_containers": containers,
"description": "Список контейнеров, которые генерируют слишком много логов и исключаются из отображения"
}
with open("excluded_containers.json", "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
return True
except Exception as e:
print(f"❌ Ошибка сохранения excluded_containers.json: {e}")
return False
def get_all_projects() -> List[str]:
"""
Получает список всех проектов Docker Compose
Получает список всех проектов Docker Compose с учетом исключенных контейнеров
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
projects = set()
excluded_containers = load_excluded_containers()
try:
containers = docker_client.containers.list(all=True)
# Словарь для подсчета контейнеров по проектам
project_containers = {}
standalone_containers = []
for c in containers:
try:
# Пропускаем исключенные контейнеры
if c.name in excluded_containers:
continue
labels = c.labels or {}
project = labels.get("com.docker.compose.project")
if project:
projects.add(project)
if project not in project_containers:
project_containers[project] = 0
project_containers[project] += 1
else:
standalone_containers.append(c.name)
except Exception:
continue
# Добавляем контейнеры без проекта как "standalone"
standalone_count = 0
for c in containers:
try:
labels = c.labels or {}
if not labels.get("com.docker.compose.project"):
standalone_count += 1
except Exception:
continue
# Добавляем проекты, у которых есть хотя бы один неисключенный контейнер
for project, count in project_containers.items():
if count > 0:
projects.add(project)
if standalone_count > 0:
# Добавляем standalone, если есть неисключенные контейнеры без проекта
if standalone_containers:
projects.add("standalone")
except Exception as e:
print(f"❌ Ошибка получения списка проектов: {e}")
return []
return sorted(list(projects))
result = sorted(list(projects))
print(f"📋 Доступные проекты (с учетом исключенных контейнеров): {result}")
return result
def list_containers(projects: Optional[List[str]] = None, include_stopped: bool = False) -> List[Dict]:
"""
@@ -97,21 +149,13 @@ def list_containers(projects: Optional[List[str]] = None, include_stopped: bool
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
# Список контейнеров, которые генерируют слишком много логов
excluded_containers = [
"buildx_buildkit_multiarch-builder0",
"buildx_buildkit_multiarch-builder1",
"buildx_buildkit_multiarch-builder2",
"buildx_buildkit_multiarch-builder3",
"buildx_buildkit_multiarch-builder4",
"buildx_buildkit_multiarch-builder5",
"buildx_buildkit_multiarch-builder6",
"buildx_buildkit_multiarch-builder7",
"buildx_buildkit_multiarch-builder8",
"buildx_buildkit_multiarch-builder9"
]
# Загружаем список исключенных контейнеров из JSON файла
excluded_containers = load_excluded_containers()
print(f"🚫 Список исключенных контейнеров: {excluded_containers}")
items = []
excluded_count = 0
try:
# Получаем список контейнеров с базовой обработкой ошибок
@@ -128,6 +172,8 @@ def list_containers(projects: Optional[List[str]] = None, include_stopped: bool
"service": c.name,
"project": None,
"health": None,
"ports": [],
"url": None,
}
# Безопасно получаем метки
@@ -147,6 +193,31 @@ def list_containers(projects: Optional[List[str]] = None, include_stopped: bool
except Exception:
pass # Оставляем "unknown"
# Безопасно получаем информацию о портах
try:
ports = c.ports or {}
if ports:
basic_info["ports"] = list(ports.keys())
# Пытаемся найти HTTP/HTTPS порт для создания URL
for port_mapping in ports.values():
if port_mapping:
for mapping in port_mapping:
if isinstance(mapping, dict) and mapping.get("HostPort"):
host_port = mapping["HostPort"]
# Проверяем, что это HTTP порт (80, 443, 8080, 3000, etc.)
host_port_int = int(host_port)
if (host_port_int in [80, 443] or
(3000 <= host_port_int <= 4000) or
(8000 <= host_port_int <= 9000)):
protocol = "https" if host_port == "443" else "http"
basic_info["url"] = f"{protocol}://localhost:{host_port}"
break
if basic_info["url"]:
break
except Exception:
pass # Оставляем пустые значения
# Фильтрация по проектам
if projects:
# Если проект не указан, считаем его standalone
@@ -156,6 +227,7 @@ def list_containers(projects: Optional[List[str]] = None, include_stopped: bool
# Фильтрация исключенных контейнеров
if basic_info["name"] in excluded_containers:
excluded_count += 1
print(f"⚠️ Пропускаем исключенный контейнер: {basic_info['name']}")
continue
@@ -173,6 +245,31 @@ def list_containers(projects: Optional[List[str]] = None, include_stopped: bool
# Сортируем по проекту, сервису и имени
items.sort(key=lambda x: (x.get("project") or "", x.get("service") or "", x.get("name") or ""))
# Подсчитываем статистику по проектам
project_stats = {}
for item in items:
project = item.get("project") or "standalone"
if project not in project_stats:
project_stats[project] = {"visible": 0, "excluded": 0}
project_stats[project]["visible"] += 1
# Подсчитываем исключенные контейнеры по проектам
for c in containers:
try:
if c.name in excluded_containers:
labels = c.labels or {}
project = labels.get("com.docker.compose.project") or "standalone"
if project not in project_stats:
project_stats[project] = {"visible": 0, "excluded": 0}
project_stats[project]["excluded"] += 1
except Exception:
continue
print(f"📊 Статистика: найдено {len(items)} контейнеров, исключено {excluded_count} контейнеров")
for project, stats in project_stats.items():
print(f" 📦 {project}: {stats['visible']} видимых, {stats['excluded']} исключенных")
return items
# ---------- HTML ----------
@@ -246,6 +343,41 @@ def api_logs_stats(container_id: str, _: HTTPBasicCredentials = Depends(check_ba
print(f"Error getting log stats for {container_id}: {e}")
return JSONResponse({"error": str(e)}, status_code=500)
@app.get("/api/excluded-containers")
def api_get_excluded_containers(_: HTTPBasicCredentials = Depends(check_basic)):
"""
Получить список исключенных контейнеров
"""
return JSONResponse(
content={"excluded_containers": load_excluded_containers()},
headers={
"Cache-Control": "no-cache, no-store, must-revalidate",
"Pragma": "no-cache",
"Expires": "0"
}
)
@app.post("/api/excluded-containers")
def api_update_excluded_containers(
containers: List[str] = Body(...),
_: HTTPBasicCredentials = Depends(check_basic)
):
"""
Обновить список исключенных контейнеров
"""
success = save_excluded_containers(containers)
if success:
return JSONResponse(
content={"status": "success", "message": "Список исключенных контейнеров обновлен"},
headers={
"Cache-Control": "no-cache, no-store, must-revalidate",
"Pragma": "no-cache",
"Expires": "0"
}
)
else:
raise HTTPException(status_code=500, detail="Ошибка сохранения списка")
@app.get("/api/projects")
def api_projects(_: HTTPBasicCredentials = Depends(check_basic)):
"""Получить список всех проектов Docker Compose"""