Веб-интерфейс: страница /clusters, навигация и крошки для кластеров

- Выделена страница списка кластеров, панель упрощена; nav_active и крошки
  ведут в раздел Кластеры; theme.js синхронизирует активную пилюлю по URL.
- Доработки дашборда, аддонов, журнала, стилей и API-документации.
- Поддержка Podman: docker-compose.podman.yml, скрипты сокета; Makefile и env.
This commit is contained in:
Sergey Antropoff
2026-04-04 13:42:21 +03:00
parent 17f6233fd7
commit eb063aec20
38 changed files with 3990 additions and 734 deletions
+134 -1
View File
@@ -117,6 +117,11 @@ class StatsResponse(BaseModel):
)
jobs_total: int
jobs_recent_failed: int
helm_addons_installable_count: int = Field(
0,
ge=0,
description="Число типовых Helm-аддонов в каталоге UI (страница «Аддоны»), доступных для установки",
)
cluster_resources: list[KindClusterResources] = Field(
default_factory=list,
description="CPU/RAM/I/O узлов kind по данным container CLI",
@@ -311,6 +316,9 @@ class HelmAddonApplyResponse(BaseModel):
log: str = Field(default="", description="Сводный вывод helm/kubectl")
_VALUES_YAML_MAX = 131_072
class IngressNginxInstallRequest(BaseModel):
"""Тело POST …/addons/ingress-nginx — установка ingress-nginx."""
@@ -319,6 +327,15 @@ class IngressNginxInstallRequest(BaseModel):
max_length=64,
description="Версия чарта; пусто — последняя из репозитория",
)
values_yaml: str | None = Field(
default=None,
max_length=_VALUES_YAML_MAX,
description=(
"Полный YAML values для чарта (корень — объект), как в редакторе на странице аддонов "
"(``helm show values`` + сервис для kind). Пусто — собрать автоматически на сервере. "
"После ``-f`` применяются ``--set`` NodePort (приоритет Helm)."
),
)
class KubePrometheusStackInstallRequest(BaseModel):
@@ -331,6 +348,14 @@ class KubePrometheusStackInstallRequest(BaseModel):
max_length=64,
description="Версия чарта kube-prometheus-stack; пусто — последняя",
)
values_yaml: str | None = Field(
default=None,
max_length=_VALUES_YAML_MAX,
description=(
"Полный YAML values чарта (``helm show values`` + логин/пароль Grafana из формы). "
"Пусто — собрать на сервере из версии чарта и полей формы."
),
)
class MetricsServerInstallRequest(BaseModel):
@@ -341,6 +366,14 @@ class MetricsServerInstallRequest(BaseModel):
max_length=64,
description="Версия чарта metrics-server; пусто — последняя",
)
values_yaml: str | None = Field(
default=None,
max_length=_VALUES_YAML_MAX,
description=(
"Полный YAML values чарта (``helm show values`` + args для kind). "
"Пусто — собрать на сервере."
),
)
class IstioKialiInstallRequest(BaseModel):
@@ -358,6 +391,75 @@ class IstioKialiInstallRequest(BaseModel):
max_length=64,
description="Версия чарта kiali/kiali-server; пусто — последняя",
)
values_yaml: str | None = Field(
default=None,
max_length=_VALUES_YAML_MAX,
description=(
"Полный YAML values чарта **kiali-server** (``helm show values``). "
"Пусто — без файла ``-f`` (дефолты чарта). Секрет логина/пароля из формы отдельно; "
"``auth.strategy=login`` через ``--set``."
),
)
istio_base_values_yaml: str | None = Field(
default=None,
max_length=_VALUES_YAML_MAX,
description="Полный YAML values чарта **istio/base**; пусто — без ``-f``.",
)
istio_istiod_values_yaml: str | None = Field(
default=None,
max_length=_VALUES_YAML_MAX,
description="Полный YAML values чарта **istio/istiod**; пусто — без ``-f``.",
)
class HelmAddonComposeValuesRequest(BaseModel):
"""Тело POST /helm/addons/compose-values — собрать YAML для редактора (helm show values + форма)."""
addon: Literal["ingress-nginx", "kube-prometheus-stack", "metrics-server", "istio-kiali"]
chart_version: str | None = Field(
default=None,
max_length=64,
description="Версия чарта: ingress-nginx, kube-prometheus-stack или metrics-server",
)
grafana_admin_user: str | None = Field(default=None, max_length=256)
grafana_admin_password: str | None = Field(
default=None,
max_length=256,
description="Для preview kube-prometheus-stack; если пусто — подставляется временный пароль в YAML",
)
istio_chart_version: str | None = Field(default=None, max_length=64)
kiali_chart_version: str | None = Field(default=None, max_length=64)
class HelmAddonComposeValuesResponse(BaseModel):
"""Ответ compose-values: строки YAML для текстовых полей UI."""
values_yaml: str = Field(..., description="Основной чарт; для istio-kiali — kiali-server")
istio_base_values_yaml: str | None = Field(default=None, description="Только istio-kiali: istio/base")
istio_istiod_values_yaml: str | None = Field(default=None, description="Только istio-kiali: istio/istiod")
class HelmAddonComposeBatchRequest(BaseModel):
"""Тело POST /helm/addons/compose-values-batch — одним запросом обновить все редакторы на странице аддонов."""
ingress_chart_version: str | None = Field(default=None, max_length=64)
grafana_admin_user: str | None = Field(default=None, max_length=256)
grafana_admin_password: str | None = Field(default=None, max_length=256)
kube_prometheus_chart_version: str | None = Field(default=None, max_length=64)
metrics_server_chart_version: str | None = Field(default=None, max_length=64)
istio_chart_version: str | None = Field(default=None, max_length=64)
kiali_chart_version: str | None = Field(default=None, max_length=64)
class HelmAddonComposeBatchResponse(BaseModel):
"""Шесть строк YAML за один вызов (один ``helm repo update``)."""
ingress_nginx_values_yaml: str
kube_prometheus_values_yaml: str
metrics_server_values_yaml: str
kiali_values_yaml: str
istio_base_values_yaml: str
istio_istiod_values_yaml: str
class HelmAddonChartVersionsResponse(BaseModel):
@@ -400,7 +502,7 @@ class ClusterJournalResponse(BaseModel):
class JournalRecentResponse(BaseModel):
"""Страница агрегированного журнала (все ``clusters/*/journal/jobs_history.json``)."""
"""Страница журнала заданий из ``journal/jobs_history.json`` (все кластеры или один)."""
limit: int = Field(description="Размер страницы (записей за запрос)")
offset: int = Field(description="Смещение от начала отсортированного списка (0 = первая страница)")
@@ -408,3 +510,34 @@ class JournalRecentResponse(BaseModel):
page: int = Field(ge=1, description="Номер текущей страницы с 1")
total_pages: int = Field(ge=0, description="Число страниц при данном limit; 0 если записей нет")
entries: list[JournalEntryModel] = Field(default_factory=list)
class ClusterDirLogEntryModel(BaseModel):
"""Запись из ``provision_log.json`` или ``helm_addon_log.json`` (формат как у развёртывания)."""
model_config = {"extra": "allow"}
version: int | None = None
job_id: str = ""
kind: str = ""
cluster_name: str = ""
finished_at_utc: str = ""
status: str = ""
message: str | None = None
lines: list[str] = Field(default_factory=list)
result: dict[str, Any] | None = None
source_cluster: str | None = Field(
default=None,
description="Имя каталога кластера в агрегированных ответах /journal/provision и /journal/helm-addons",
)
class JournalPagedDirLogsResponse(BaseModel):
"""Пагинация по одному последнему логу на кластер (развёртывание или Helm-аддоны)."""
limit: int
offset: int
total: int
page: int
total_pages: int
entries: list[ClusterDirLogEntryModel] = Field(default_factory=list)