"""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)}
""" @router.get("/apps/{app_id}/logs", response_class=HTMLResponse) def app_logs_page( app_id: str, since: str = Query(""), tail: str = Query(""), q: str = Query(""), user=Depends(require_developer), ): app = get_app(app_id) if not app: raise HTTPException(status_code=404, detail="App not found") real_app_id = (app.get("id") or "").strip() app_url_id = quote(real_app_id, safe="") period = normalize_period(since) tail_value = normalize_tail(tail) query = (q or "").strip() log_audit_event( user, action="service.logs.view", target_type="service", target_id=real_app_id, metadata={"since": period, "tail": tail_value}, ) container_name = "" container_state: str | None = None error_detail = "" all_lines: list[str] = [] try: container_name, container_state = resolve_app_container(real_app_id) if container_state: all_lines = split_log_lines(get_container_logs(container_name, since=period, tail=tail_value)) except DockerLogsError as exc: error_detail = str(exc) visible_lines = filter_log_lines(all_lines, query) if error_detail: log_body = render_docker_error(container_name, error_detail) elif not container_state: log_body = ( '

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"""

Logy kontejneru

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)}

Výpis

Reset
{log_body}
""", user=user, )