Веб-UI FastAPI, REST API v1, интерактивный setup без env.example

- Дашборд (Jinja2 + static), управление кластерами kind, задания и kubeconfig.
- API: health, stats, clusters CRUD, versions, jobs; документация app/docs/api_routes.md.
- Docker Compose: том app, uvicorn reload, KIND_K8S_PATCH_KUBECONFIG по умолчанию 1.
- setup_env_interactive.py: список переменных в скрипте, удалён env.example.
- Makefile: явный префикс docker/podman; прочие правки CLI и ядра кластеров.
This commit is contained in:
Sergey Antropoff
2026-04-04 05:39:53 +03:00
parent ae961ef5fe
commit e46a62cfdb
31 changed files with 2507 additions and 393 deletions
+80
View File
@@ -0,0 +1,80 @@
"""Веб-интерфейс и REST API для управления локальными кластерами kind (порт по умолчанию 6000).
Запуск в контейнере: ``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,
)
return templates.TemplateResponse(
"dashboard.html",
{
"request": request,
"app_title": settings.app_title,
},
)
@app.get("/ui", include_in_schema=False)
async def ui_redirect() -> RedirectResponse:
"""Удобный алиас на корень UI."""
return RedirectResponse(url="/", status_code=307)