Веб-UI: логи kind create, старт/стоп кластеров, документация README
- Потоковые логи в job_store и UI; kind create через Popen с построчным выводом
- POST /clusters/{name}/start|stop; create по сохранённому kind-config.yaml
- Страница /documentation: GET /api/v1/docs/readme, marked+DOMPurify из static/vendor
- Иконки действий, плавающие подсказки, модалка подтверждения вместо confirm
- Makefile: make docker|podman rebuild; compose: монтирование README.md
- Dockerfile: COPY README.md; readme_doc: несколько путей к README
Автор: Сергей Антропов — https://devops.org.ru
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
"""Чтение README.md для API ``GET /api/v1/docs/readme`` и страницы «Документация».
|
||||
|
||||
Разметка Markdown преобразуется в браузере: ``/static/js/vendor/marked.min.js`` и
|
||||
``purify.min.js`` (файлы входят в репозиторий, без CDN).
|
||||
|
||||
Путь к файлу: ``KIND_K8S_README_PATH`` или ``README.md`` в корне рядом с ``app/``;
|
||||
в Docker-образе — ``/opt/kind-k8s/README.md``.
|
||||
|
||||
Автор: Сергей Антропов
|
||||
Сайт: https://devops.org.ru
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger("kind_k8s.readme_doc")
|
||||
|
||||
# app/core/readme_doc.py: parents[2] = корень репозитория (рядом с app/) или /opt/kind-k8s в образе
|
||||
_LIB_FILE = Path(__file__).resolve()
|
||||
|
||||
|
||||
def _candidates_without_env() -> list[Path]:
|
||||
"""
|
||||
Возможные пути к README без KIND_K8S_README_PATH.
|
||||
|
||||
Порядок: родитель каталога app/ (типично репозиторий), затем фиксированный путь образа.
|
||||
В compose рекомендуется монтировать ./README.md → /opt/kind-k8s/README.md (см. docker-compose.yml).
|
||||
"""
|
||||
out: list[Path] = []
|
||||
seen: set[Path] = set()
|
||||
try:
|
||||
repo_readme = (_LIB_FILE.parents[2] / "README.md").resolve()
|
||||
if repo_readme not in seen:
|
||||
seen.add(repo_readme)
|
||||
out.append(repo_readme)
|
||||
except (IndexError, OSError):
|
||||
pass
|
||||
fixed = Path("/opt/kind-k8s/README.md")
|
||||
try:
|
||||
fixed_r = fixed.resolve()
|
||||
if fixed_r not in seen:
|
||||
seen.add(fixed_r)
|
||||
out.append(fixed_r)
|
||||
except OSError:
|
||||
out.append(fixed)
|
||||
return out
|
||||
|
||||
|
||||
def get_readme_path() -> Path | None:
|
||||
"""Первый существующий путь к README или ``None``."""
|
||||
raw = (os.environ.get("KIND_K8S_README_PATH") or "").strip()
|
||||
if raw:
|
||||
p = Path(raw).expanduser().resolve()
|
||||
return p if p.is_file() else None
|
||||
for p in _candidates_without_env():
|
||||
if p.is_file():
|
||||
return p
|
||||
return None
|
||||
|
||||
|
||||
def read_readme_text() -> str:
|
||||
"""Прочитать README как UTF-8; ``FileNotFoundError`` если файла нет."""
|
||||
raw = (os.environ.get("KIND_K8S_README_PATH") or "").strip()
|
||||
if raw:
|
||||
p = Path(raw).expanduser().resolve()
|
||||
if not p.is_file():
|
||||
logger.warning("KIND_K8S_README_PATH: файл не найден: %s", p)
|
||||
raise FileNotFoundError(str(p))
|
||||
text = p.read_text(encoding="utf-8")
|
||||
logger.debug("README из KIND_K8S_README_PATH, %s символов", len(text))
|
||||
return text
|
||||
|
||||
for p in _candidates_without_env():
|
||||
if p.is_file():
|
||||
text = p.read_text(encoding="utf-8")
|
||||
logger.info("README прочитан: %s (%s символов)", p, len(text))
|
||||
return text
|
||||
|
||||
logger.warning(
|
||||
"README.md не найден. Проверены пути: %s. "
|
||||
"В Docker Compose добавьте монтирование ./README.md:/opt/kind-k8s/README.md "
|
||||
"или пересоберите образ (COPY README.md в Dockerfile).",
|
||||
[str(x) for x in _candidates_without_env()],
|
||||
)
|
||||
raise FileNotFoundError("README.md")
|
||||
Reference in New Issue
Block a user