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
|
Сайт: 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 = []
|
items = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -140,6 +154,11 @@ def list_containers(projects: Optional[List[str]] = None, include_stopped: bool
|
|||||||
if container_project not in projects:
|
if container_project not in projects:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Фильтрация исключенных контейнеров
|
||||||
|
if basic_info["name"] in excluded_containers:
|
||||||
|
print(f"⚠️ Пропускаем исключенный контейнер: {basic_info['name']}")
|
||||||
|
continue
|
||||||
|
|
||||||
# Добавляем контейнер в список
|
# Добавляем контейнер в список
|
||||||
items.append(basic_info)
|
items.append(basic_info)
|
||||||
|
|
||||||
@ -167,16 +186,77 @@ def load_index_html(token: str) -> str:
|
|||||||
@app.get("/", response_class=HTMLResponse)
|
@app.get("/", response_class=HTMLResponse)
|
||||||
def index(creds: HTTPBasicCredentials = Depends(check_basic)):
|
def index(creds: HTTPBasicCredentials = Depends(check_basic)):
|
||||||
token = token_from_creds(creds)
|
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)
|
@app.get("/healthz", response_class=PlainTextResponse)
|
||||||
def healthz():
|
def healthz():
|
||||||
return "ok"
|
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")
|
@app.get("/api/projects")
|
||||||
def api_projects(_: HTTPBasicCredentials = Depends(check_basic)):
|
def api_projects(_: HTTPBasicCredentials = Depends(check_basic)):
|
||||||
"""Получить список всех проектов Docker Compose"""
|
"""Получить список всех проектов 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")
|
@app.get("/api/services")
|
||||||
def api_services(projects: Optional[str] = Query(None), include_stopped: bool = Query(False),
|
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:
|
elif DEFAULT_PROJECT:
|
||||||
project_list = [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")
|
@app.post("/api/snapshot")
|
||||||
def api_snapshot(
|
def api_snapshot(
|
||||||
@ -252,24 +339,30 @@ async def ws_logs(ws: WebSocket, container_id: str, tail: int = DEFAULT_TAIL, to
|
|||||||
|
|
||||||
# Получаем логи (только последние строки, без follow)
|
# Получаем логи (только последние строки, без follow)
|
||||||
try:
|
try:
|
||||||
|
print(f"Getting logs for container {container.name} (ID: {container.id[:12]})")
|
||||||
logs = container.logs(tail=tail).decode(errors="ignore")
|
logs = container.logs(tail=tail).decode(errors="ignore")
|
||||||
if logs:
|
if logs:
|
||||||
await ws.send_text(logs)
|
await ws.send_text(logs)
|
||||||
else:
|
else:
|
||||||
await ws.send_text("No logs available")
|
await ws.send_text("No logs available")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
print(f"Error getting logs for {container.name}: {e}")
|
||||||
await ws.send_text(f"ERROR getting logs: {e}")
|
await ws.send_text(f"ERROR getting logs: {e}")
|
||||||
|
|
||||||
|
# Простое WebSocket соединение - только отправляем логи один раз
|
||||||
|
print(f"WebSocket connection established for {container.name}")
|
||||||
|
|
||||||
except WebSocketDisconnect:
|
except WebSocketDisconnect:
|
||||||
print("WebSocket client disconnected")
|
print(f"WebSocket client disconnected for container {container.name}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"WebSocket error: {e}")
|
print(f"WebSocket error for {container.name}: {e}")
|
||||||
try:
|
try:
|
||||||
await ws.send_text(f"ERROR: {e}")
|
await ws.send_text(f"ERROR: {e}")
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
|
print(f"Closing WebSocket connection for container {container.name}")
|
||||||
await ws.close()
|
await ws.close()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user