Веб-UI: темы, навигация API, статистика и доработки API

- Шапка: логотип Kubernetes, ссылка на главную, выпадающее меню API (Swagger/ReDoc/Health), переключатель светлой/тёмной темы (localStorage).
- Светлая тема в синей гамме; выравнивание кнопки темы в ряду с пилюлями.
- Дашборд: единая карточка ошибки health/stats, подсказка Docker/Podman, поле container_cli в GET /stats, total_workers_from_meta всегда число (0 без meta).
- Правки кластеров, job_store, compose, документация и частичные шаблоны.
This commit is contained in:
Sergey Antropoff
2026-04-04 07:58:19 +03:00
parent aa8003061e
commit 6f3daa33ec
20 changed files with 1885 additions and 451 deletions
+57 -13
View File
@@ -295,14 +295,49 @@ def _run_checked_stream(
def _docker_pull_plain_progress_enabled() -> bool:
"""
``docker pull --progress plain`` — построчный текст без TTY-ANSI; удобно с pipe (без PTY).
Разрешить попытку ``docker pull --progress=plain`` (если CLI это поддерживает).
Отключить: ``KIND_K8S_DOCKER_PULL_PLAIN=0`` (если старая версия docker не знает флаг).
Полностью отключить: ``KIND_K8S_DOCKER_PULL_PLAIN=0``.
"""
v = (os.environ.get("KIND_K8S_DOCKER_PULL_PLAIN") or "1").strip().lower()
return v not in ("0", "false", "no", "off", "нет")
# Кэш: есть ли в выводе ``docker pull --help`` опция ``--progress`` (в старых CLI её нет).
_docker_pull_help_has_progress: bool | None = None
def _docker_pull_cli_supports_progress_flag() -> bool:
"""
Узнать по справке, объявляет ли данный ``docker`` флаг ``--progress`` для ``pull``.
Без этого на старых клиентах в журнал попадало «unknown flag: --progress», а код выхода часто **1**,
а не 125 — повтор без флага не срабатывал.
"""
global _docker_pull_help_has_progress
if _docker_pull_help_has_progress is not None:
return _docker_pull_help_has_progress
exe = shutil.which("docker")
if not exe:
_docker_pull_help_has_progress = False
return False
try:
p = subprocess.run(
[exe, "pull", "--help"],
capture_output=True,
text=True,
timeout=12,
)
blob = (p.stdout or "") + (p.stderr or "")
_docker_pull_help_has_progress = "--progress" in blob
if not _docker_pull_help_has_progress:
logger.info("docker pull: в справке нет --progress — используем обычный pull (PTY по KIND_K8S_STREAM_PTY)")
except (OSError, subprocess.TimeoutExpired) as e:
logger.warning("Не удалось выполнить docker pull --help (%s), без флага --progress", e)
_docker_pull_help_has_progress = False
return _docker_pull_help_has_progress
def _pull_node_image_streaming(*, image: str, on_line: Callable[[str], None], job_id: str | None) -> None:
"""
Скачать образ узлов через ``docker pull`` / ``podman pull`` с выводом в журнал задания.
@@ -316,7 +351,12 @@ def _pull_node_image_streaming(*, image: str, on_line: Callable[[str], None], jo
"Скачивание образа недоступно: не найдена программа для работы с контейнерами (docker/podman).",
exit_code=127,
)
if cli == "docker" and _docker_pull_plain_progress_enabled():
use_docker_plain = (
cli == "docker"
and _docker_pull_plain_progress_enabled()
and _docker_pull_cli_supports_progress_flag()
)
if use_docker_plain:
try:
_run_checked_stream(
[cli, "pull", "--progress=plain", image],
@@ -326,16 +366,12 @@ def _pull_node_image_streaming(*, image: str, on_line: Callable[[str], None], jo
)
return
except KindClusterError as e:
# У docker CLI код 125 — «неверная команда/флаг»; тогда пробуем pull без plain.
err_low = str(e).lower()
if e.exit_code == 125 or any(
x in err_low
for x in ("progress", "unknown flag", "bad flag", "invalid option", "unknown shorthand")
):
logger.warning("Повтор docker pull без --progress=plain: %s", e)
_run_checked_stream([cli, "pull", image], on_line=on_line, job_id=job_id, use_pty=None)
return
raise
if e.exit_code == 130:
raise
# На всякий случай, если справка устарела или другой код ошибки.
logger.warning("Повтор docker pull без --progress=plain (код %s): %s", e.exit_code, e)
_run_checked_stream([cli, "pull", image], on_line=on_line, job_id=job_id, use_pty=None)
return
_run_checked_stream([cli, "pull", image], on_line=on_line, job_id=job_id, use_pty=None)
@@ -800,6 +836,14 @@ def _container_cli_bin() -> str:
return (os.environ.get("CONTAINER_CLI") or "docker").strip() or "docker"
def configured_container_cli() -> str:
"""Активное имя CLI из ``CONTAINER_CLI`` (docker/podman).
Единый источник для проверки движка, сбора ``docker stats``/``podman stats`` и поля ``container_cli`` в GET /stats.
"""
return _container_cli_bin()
def container_engine_ping(*, timeout_sec: float = 12.0) -> tuple[bool, str, str]:
"""
Проверить доступ к движку контейнеров (``docker info`` / ``podman info``).