f01db8d4b7
- Цель make docker|podman kubectl CLUSTER=… (KUBECTL_ARGS) — exec kubectl в kind-k8s-web - README: без kubectl на хосте; раздел про проверку API из контейнера - create_cluster/cluster_status: подсказки для UI, make kubectl и exec в контейнере - app/docs: api_routes.md и README.md про kubectl и API workloads - Прочее: переименование проекта, документация, UI документации (ранее в рабочем дереве)
97 lines
3.9 KiB
Python
97 lines
3.9 KiB
Python
"""Веб-интерфейс и REST API для управления локальными кластерами kind.
|
||
|
||
В контейнере uvicorn слушает порт 6000; на хост публикация по умолчанию 8080 (``KIND_K8S_WEB_PORT``), т.к. 6000 на хосте блокируется Chrome (ERR_UNSAFE_PORT).
|
||
|
||
Запуск в контейнере: ``python3 -m uvicorn main:app --host 0.0.0.0 --port 6000`` из каталога ``/opt/kind-k8s/app``
|
||
или через ``make docker up`` / ``make podman up``.
|
||
|
||
Автор: Сергей Антропов
|
||
Сайт: https://devops.org.ru
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
import os
|
||
from contextlib import asynccontextmanager
|
||
from pathlib import Path
|
||
|
||
from fastapi import FastAPI, Request
|
||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||
from fastapi.staticfiles import StaticFiles
|
||
from fastapi.templating import Jinja2Templates
|
||
|
||
from api.v1.router import api_router
|
||
from core.config import get_settings
|
||
|
||
_BASE = Path(__file__).resolve().parent
|
||
logger = logging.getLogger("kind_k8s.web")
|
||
|
||
|
||
def _configure_logging() -> None:
|
||
"""Единая настройка логов для uvicorn и модулей kind-k8s."""
|
||
if logging.root.handlers:
|
||
return
|
||
level = logging.DEBUG if os.environ.get("KIND_K8S_DEBUG", "").strip().lower() in ("1", "true", "yes", "да") else logging.INFO
|
||
logging.basicConfig(level=level, format="%(levelname)s %(name)s: %(message)s")
|
||
logger.info("Логирование инициализировано, уровень=%s", logging.getLevelName(level))
|
||
|
||
|
||
@asynccontextmanager
|
||
async def lifespan(app: FastAPI):
|
||
"""Старт и остановка приложения."""
|
||
_configure_logging()
|
||
settings = get_settings()
|
||
logger.info("Запуск FastAPI «%s»", settings.app_title)
|
||
yield
|
||
logger.info("Остановка FastAPI")
|
||
|
||
|
||
settings = get_settings()
|
||
app = FastAPI(title=settings.app_title, lifespan=lifespan)
|
||
app.include_router(api_router, prefix="/api/v1")
|
||
|
||
_templates_dir = _BASE / "templates"
|
||
_static_dir = _BASE / "static"
|
||
if _static_dir.is_dir():
|
||
app.mount("/static", StaticFiles(directory=str(_static_dir)), name="static")
|
||
|
||
templates = Jinja2Templates(directory=str(_templates_dir))
|
||
|
||
|
||
@app.get("/", response_class=HTMLResponse, summary="Веб-интерфейс")
|
||
async def dashboard(request: Request) -> HTMLResponse:
|
||
"""Главная страница с формой создания и таблицей кластеров."""
|
||
if not _templates_dir.is_dir():
|
||
return HTMLResponse(
|
||
content="<p>Шаблоны не найдены. Ожидается каталог app/templates/</p>",
|
||
status_code=500,
|
||
)
|
||
# Starlette ≥0.37: первым аргументом обязателен Request (иначе dict уйдёт в get_template → unhashable type).
|
||
return templates.TemplateResponse(
|
||
request,
|
||
"dashboard.html",
|
||
{"app_title": settings.app_title, "nav_active": "panel"},
|
||
)
|
||
|
||
|
||
@app.get("/ui", include_in_schema=False)
|
||
async def ui_redirect() -> RedirectResponse:
|
||
"""Удобный алиас на корень UI."""
|
||
return RedirectResponse(url="/", status_code=307)
|
||
|
||
|
||
@app.get("/documentation", response_class=HTMLResponse, summary="Документация (README)")
|
||
async def documentation_page(request: Request) -> HTMLResponse:
|
||
"""Оболочка страницы: Markdown подгружается с ``GET /api/v1/docs/readme``, рендер в браузере (marked + DOMPurify из ``/static/js/vendor/``)."""
|
||
if not _templates_dir.is_dir():
|
||
return HTMLResponse(
|
||
content="<p>Шаблоны не найдены.</p>",
|
||
status_code=500,
|
||
)
|
||
return templates.TemplateResponse(
|
||
request,
|
||
"documentation.html",
|
||
{"app_title": settings.app_title, "nav_active": "documentation"},
|
||
)
|