"""Stránka "Logy" v detailu služby - diagnostické čtení Docker logů kontejneru. Účel: administrátor uvidí konkrétní exception (Kestrel / Uvicorn / Node.js) bez SSH přístupu na server. Portál logy nikam neukládá, jen je přečte z Dockeru a zobrazí. Container se odvozuje z ``app_id`` služby načtené z databáze AppFactory - z URL se bere pouze ID služby, nikdy název kontejneru. Vlastní volání dockeru řeší ``app.docker_logs``. """ import html from urllib.parse import quote from fastapi import APIRouter, Depends, HTTPException, Query from fastapi.responses import HTMLResponse from ..auth import require_developer from ..db.apps import get_app from ..db.audit import log_audit_event from ..docker_logs import ( ALLOWED_LOG_TAILS, LOG_PERIOD_LABELS, DockerLogsError, docker_daemon_available, filter_log_lines, get_container_logs, is_error_line, normalize_period, normalize_tail, resolve_app_container, split_log_lines, state_label, ) from ..templates.layout import page router = APIRouter() # Stav kontejneru -> třída pilulky (stejné barvy jako u ostatních stavů v portálu). STATE_PILL_CLASSES = { "running": "pill pill-success", "restarting": "pill pill-warning", "paused": "pill pill-warning", "created": "pill pill-muted", "removing": "pill pill-warning", "exited": "pill pill-danger", "dead": "pill pill-danger", } def render_state_pill(status: str | None) -> str: if not status: return 'Neexistuje' css = STATE_PILL_CLASSES.get(status, "pill pill-muted") return f'{html.escape(state_label(status))}' def render_period_options(selected: str) -> str: options = "" for value, label in LOG_PERIOD_LABELS: selected_attr = " selected" if value == selected else "" options += f'' return options def render_tail_options(selected: int) -> str: options = "" for value in ALLOWED_LOG_TAILS: selected_attr = " selected" if value == selected else "" options += f'' return options def render_log_lines(lines: list[str]) -> str: """Vykreslí řádky logu. Obsah se nemění, chybové řádky se jen vizuálně zvýrazní.""" if not lines: return '
Ve zvoleném období nejsou žádné řádky logu.
' rendered = [] for line in lines: css = "log-line log-line-error" if is_error_line(line) else "log-line" # Prázdný řádek by se bez pevné mezery smrskl na nulovou výšku. rendered.append(f'{html.escape(line) or " "}') return f'{"".join(rendered)}'
def render_docker_error(container_name: str, detail: str) -> str:
daemon = "dostupný" if docker_daemon_available() else "nedostupný"
return f"""
Logy se nepodařilo načíst.
| Docker daemon | {daemon} |
|---|---|
| Container | {html.escape(container_name) or "neznámý"} |
| Detail | {html.escape(detail)} |
Container není spuštěný nebo neexistuje.
' 'Logy lze číst jen dokud kontejner v Dockeru existuje. ' "Po smazání kontejneru (například při redeployi) Docker jeho logy zahodí.
" ) else: summary = f"Načteno {len(all_lines)} řádků." if query: summary = f"Zobrazeno {len(visible_lines)} z {len(all_lines)} načtených řádků (filtr: {html.escape(query)})." log_body = f'{summary}
{render_log_lines(visible_lines)}' return page( "Logy služby", f"""Diagnostické čtení Docker logů běžící služby (stdout i stderr dohromady). Portál logy neukládá, jen je čte přímo z Dockeru.
← Zpět na detail služby Úlohy služby
| Aplikace | {html.escape(real_app_id)} |
|---|---|
| Container | {html.escape(container_name) or "neurčeno"} |
| Stav | {render_state_pill(container_state)} |