UI improvements: removed pause from Options, updated Tail Lines gradation, renamed Snapshot to Download logs and made it full-width
This commit is contained in:
parent
351b2ac041
commit
3fcaa8ad5d
103
app.py
103
app.py
@ -97,6 +97,20 @@ 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"
|
||||
]
|
||||
|
||||
items = []
|
||||
|
||||
try:
|
||||
@ -140,6 +154,11 @@ def list_containers(projects: Optional[List[str]] = None, include_stopped: bool
|
||||
if container_project not in projects:
|
||||
continue
|
||||
|
||||
# Фильтрация исключенных контейнеров
|
||||
if basic_info["name"] in excluded_containers:
|
||||
print(f"⚠️ Пропускаем исключенный контейнер: {basic_info['name']}")
|
||||
continue
|
||||
|
||||
# Добавляем контейнер в список
|
||||
items.append(basic_info)
|
||||
|
||||
@ -167,16 +186,77 @@ def load_index_html(token: str) -> str:
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
def index(creds: HTTPBasicCredentials = Depends(check_basic)):
|
||||
token = token_from_creds(creds)
|
||||
return HTMLResponse(load_index_html(token))
|
||||
return HTMLResponse(
|
||||
content=load_index_html(token),
|
||||
headers={
|
||||
"Cache-Control": "no-cache, no-store, must-revalidate",
|
||||
"Pragma": "no-cache",
|
||||
"Expires": "0"
|
||||
}
|
||||
)
|
||||
|
||||
@app.get("/healthz", response_class=PlainTextResponse)
|
||||
def healthz():
|
||||
return "ok"
|
||||
|
||||
@app.get("/api/logs/stats/{container_id}")
|
||||
def api_logs_stats(container_id: str, _: HTTPBasicCredentials = Depends(check_basic)):
|
||||
"""Получить статистику логов контейнера"""
|
||||
try:
|
||||
# Ищем контейнер
|
||||
container = None
|
||||
for c in docker_client.containers.list(all=True):
|
||||
if c.id.startswith(container_id):
|
||||
container = c
|
||||
break
|
||||
|
||||
if container is None:
|
||||
return JSONResponse({"error": "Container not found"}, status_code=404)
|
||||
|
||||
# Получаем логи
|
||||
logs = container.logs(tail=1000).decode(errors="ignore")
|
||||
|
||||
# Подсчитываем статистику
|
||||
stats = {"debug": 0, "info": 0, "warn": 0, "error": 0}
|
||||
|
||||
for line in logs.split('\n'):
|
||||
if not line.strip():
|
||||
continue
|
||||
|
||||
line_lower = line.lower()
|
||||
if 'level=debug' in line_lower or 'debug' in line_lower:
|
||||
stats["debug"] += 1
|
||||
elif 'level=info' in line_lower or 'info' in line_lower:
|
||||
stats["info"] += 1
|
||||
elif 'level=warning' in line_lower or 'level=warn' in line_lower or 'warning' in line_lower or 'warn' in line_lower:
|
||||
stats["warn"] += 1
|
||||
elif 'level=error' in line_lower or 'error' in line_lower:
|
||||
stats["error"] += 1
|
||||
|
||||
return JSONResponse(
|
||||
content=stats,
|
||||
headers={
|
||||
"Cache-Control": "no-cache, no-store, must-revalidate",
|
||||
"Pragma": "no-cache",
|
||||
"Expires": "0"
|
||||
}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting log stats for {container_id}: {e}")
|
||||
return JSONResponse({"error": str(e)}, status_code=500)
|
||||
|
||||
@app.get("/api/projects")
|
||||
def api_projects(_: HTTPBasicCredentials = Depends(check_basic)):
|
||||
"""Получить список всех проектов Docker Compose"""
|
||||
return JSONResponse(get_all_projects())
|
||||
return JSONResponse(
|
||||
content=get_all_projects(),
|
||||
headers={
|
||||
"Cache-Control": "no-cache, no-store, must-revalidate",
|
||||
"Pragma": "no-cache",
|
||||
"Expires": "0"
|
||||
}
|
||||
)
|
||||
|
||||
@app.get("/api/services")
|
||||
def api_services(projects: Optional[str] = Query(None), include_stopped: bool = Query(False),
|
||||
@ -193,7 +273,14 @@ def api_services(projects: Optional[str] = Query(None), include_stopped: bool =
|
||||
elif DEFAULT_PROJECT:
|
||||
project_list = [DEFAULT_PROJECT]
|
||||
|
||||
return JSONResponse(list_containers(projects=project_list, include_stopped=include_stopped))
|
||||
return JSONResponse(
|
||||
content=list_containers(projects=project_list, include_stopped=include_stopped),
|
||||
headers={
|
||||
"Cache-Control": "no-cache, no-store, must-revalidate",
|
||||
"Pragma": "no-cache",
|
||||
"Expires": "0"
|
||||
}
|
||||
)
|
||||
|
||||
@app.post("/api/snapshot")
|
||||
def api_snapshot(
|
||||
@ -252,24 +339,30 @@ async def ws_logs(ws: WebSocket, container_id: str, tail: int = DEFAULT_TAIL, to
|
||||
|
||||
# Получаем логи (только последние строки, без follow)
|
||||
try:
|
||||
print(f"Getting logs for container {container.name} (ID: {container.id[:12]})")
|
||||
logs = container.logs(tail=tail).decode(errors="ignore")
|
||||
if logs:
|
||||
await ws.send_text(logs)
|
||||
else:
|
||||
await ws.send_text("No logs available")
|
||||
except Exception as e:
|
||||
print(f"Error getting logs for {container.name}: {e}")
|
||||
await ws.send_text(f"ERROR getting logs: {e}")
|
||||
|
||||
# Простое WebSocket соединение - только отправляем логи один раз
|
||||
print(f"WebSocket connection established for {container.name}")
|
||||
|
||||
except WebSocketDisconnect:
|
||||
print("WebSocket client disconnected")
|
||||
print(f"WebSocket client disconnected for container {container.name}")
|
||||
except Exception as e:
|
||||
print(f"WebSocket error: {e}")
|
||||
print(f"WebSocket error for {container.name}: {e}")
|
||||
try:
|
||||
await ws.send_text(f"ERROR: {e}")
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
try:
|
||||
print(f"Closing WebSocket connection for container {container.name}")
|
||||
await ws.close()
|
||||
except:
|
||||
pass
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user