Панель: редактирование конфига кластера, reapply при старте, UI и таблица
- PUT/GET конфигурации кластера, страница редактирования и модалка после сохранения - После смены kind-config: флаг в meta и start_cluster_reapply (kind delete + create) - Старт/стоп: полноэкранный спиннер до завершения job; модалки и документация API - Таблица кластеров: колонка Имя 40% при table-layout fixed; чекбоксы без width 100% - Карточки ресурсов узлов на странице кластера: до 3 в ряд; прочие правки стилей и dashboard.js
This commit is contained in:
+72
-13
@@ -8,7 +8,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any, Literal
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, model_validator
|
||||
|
||||
|
||||
class ClusterCreateRequest(BaseModel):
|
||||
@@ -143,12 +143,12 @@ class ClusterWorkloadsResponse(BaseModel):
|
||||
|
||||
|
||||
class K8sListJsonBlock(BaseModel):
|
||||
"""Фрагмент ответа ``kubectl get … -o json`` (items) для таблицы в UI."""
|
||||
"""Фрагмент списка ресурсов Kubernetes (поле ``items`` из JSON List) для таблицы в UI."""
|
||||
|
||||
ok: bool = True
|
||||
rc: int | None = Field(default=None, description="Код выхода kubectl")
|
||||
rc: int | None = Field(default=None, description="Код возврата при обращении к API кластера")
|
||||
items: list[dict[str, Any]] = Field(default_factory=list)
|
||||
message: str | None = Field(default=None, description="Ошибка kubectl или разбора JSON")
|
||||
message: str | None = Field(default=None, description="Текст ошибки или разбора JSON")
|
||||
|
||||
|
||||
class PodRestartRequest(BaseModel):
|
||||
@@ -169,7 +169,7 @@ class PodRestartResponse(BaseModel):
|
||||
|
||||
|
||||
class ClusterOverviewResponse(BaseModel):
|
||||
"""Сводка для страницы кластера: мета, ресурсы узлов, kubectl (поды, workloads)."""
|
||||
"""Сводка для страницы кластера: мета, ресурсы узлов, таблицы ресурсов Kubernetes."""
|
||||
|
||||
cluster_name: str
|
||||
registered_in_kind: bool = False
|
||||
@@ -182,7 +182,7 @@ class ClusterOverviewResponse(BaseModel):
|
||||
default_factory=lambda: KindClusterResources(cluster_name="", nodes=[], note=None),
|
||||
)
|
||||
aggregate_resources: AggregateResourcesSummary = Field(default_factory=AggregateResourcesSummary)
|
||||
kubeconfig_error: str | None = Field(default=None, description="Нет kubeconfig или ошибка kubectl")
|
||||
kubeconfig_error: str | None = Field(default=None, description="Нет kubeconfig или недоступен API кластера")
|
||||
nodes_rc: int | None = None
|
||||
nodes_output: str | None = None
|
||||
pods_rc: int | None = None
|
||||
@@ -197,8 +197,8 @@ class ClusterOverviewResponse(BaseModel):
|
||||
services_output: str | None = None
|
||||
ingresses_rc: int | None = None
|
||||
ingresses_output: str | None = None
|
||||
k8s_nodes: K8sListJsonBlock = Field(default_factory=K8sListJsonBlock, description="kubectl get nodes -o json")
|
||||
k8s_pods: K8sListJsonBlock = Field(default_factory=K8sListJsonBlock, description="kubectl get pods -A -o json")
|
||||
k8s_nodes: K8sListJsonBlock = Field(default_factory=K8sListJsonBlock, description="Узлы кластера")
|
||||
k8s_pods: K8sListJsonBlock = Field(default_factory=K8sListJsonBlock, description="Поды, все namespace")
|
||||
k8s_deployments: K8sListJsonBlock = Field(default_factory=K8sListJsonBlock)
|
||||
k8s_statefulsets: K8sListJsonBlock = Field(default_factory=K8sListJsonBlock)
|
||||
k8s_daemonsets: K8sListJsonBlock = Field(default_factory=K8sListJsonBlock)
|
||||
@@ -206,21 +206,80 @@ class ClusterOverviewResponse(BaseModel):
|
||||
k8s_ingresses: K8sListJsonBlock = Field(default_factory=K8sListJsonBlock)
|
||||
k8s_namespaces: K8sListJsonBlock = Field(
|
||||
default_factory=K8sListJsonBlock,
|
||||
description="kubectl get namespaces -o json",
|
||||
description="Namespaces",
|
||||
)
|
||||
k8s_replicasets: K8sListJsonBlock = Field(
|
||||
default_factory=K8sListJsonBlock,
|
||||
description="kubectl get replicasets -A -o json",
|
||||
description="ReplicaSets, все namespace",
|
||||
)
|
||||
k8s_jobs: K8sListJsonBlock = Field(
|
||||
default_factory=K8sListJsonBlock,
|
||||
description="kubectl get jobs -A -o json",
|
||||
description="Jobs, все namespace",
|
||||
)
|
||||
k8s_cronjobs: K8sListJsonBlock = Field(
|
||||
default_factory=K8sListJsonBlock,
|
||||
description="kubectl get cronjobs -A -o json",
|
||||
description="CronJobs, все namespace",
|
||||
)
|
||||
k8s_pvcs: K8sListJsonBlock = Field(
|
||||
default_factory=K8sListJsonBlock,
|
||||
description="kubectl get pvc -A -o json",
|
||||
description="PVC, все namespace",
|
||||
)
|
||||
|
||||
|
||||
class ClusterConfigGetResponse(BaseModel):
|
||||
"""Ответ GET /clusters/{name}/config — текущие meta и kind-config.yaml."""
|
||||
|
||||
cluster_name: str
|
||||
meta: dict[str, Any] = Field(default_factory=dict)
|
||||
kind_config_yaml: str | None = Field(default=None, description="Содержимое kind-config.yaml или null, если файла нет")
|
||||
has_kind_config: bool = False
|
||||
registered_in_kind: bool = Field(
|
||||
default=False,
|
||||
description="Есть ли кластер в списке kind (важно для подсказки про применение конфига)",
|
||||
)
|
||||
kind_note: str = Field(
|
||||
default="",
|
||||
description="Пояснение: kind не меняет топологию уже созданного кластера без пересоздания",
|
||||
)
|
||||
|
||||
|
||||
class ClusterConfigUpdateRequest(BaseModel):
|
||||
"""Тело PUT /clusters/{name}/config.
|
||||
|
||||
Если передан непустой ``kind_config_yaml``, он перезаписывает файл целиком; поля ``kubernetes_version``
|
||||
и ``workers`` в этом запросе для генерации YAML не используются (можно не отправлять).
|
||||
"""
|
||||
|
||||
kubernetes_version: str | None = Field(
|
||||
default=None,
|
||||
description="Тег kindest/node для простого режима (пересборка YAML: control-plane + N worker)",
|
||||
)
|
||||
workers: int | None = Field(default=None, ge=0, le=20)
|
||||
kind_config_yaml: str | None = Field(default=None, description="Полный YAML манифеста kind Cluster")
|
||||
description: str | None = Field(
|
||||
default=None,
|
||||
max_length=2000,
|
||||
description="Текст в meta.json (пустая строка сбрасывает поле)",
|
||||
)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def at_least_one_field(self) -> ClusterConfigUpdateRequest:
|
||||
has_ver = self.kubernetes_version is not None
|
||||
has_w = self.workers is not None
|
||||
yaml_s = (self.kind_config_yaml or "").strip()
|
||||
has_yaml = self.kind_config_yaml is not None and yaml_s != ""
|
||||
has_desc = self.description is not None
|
||||
if not (has_ver or has_w or has_yaml or has_desc):
|
||||
raise ValueError("Укажите хотя бы одно поле для обновления.")
|
||||
return self
|
||||
|
||||
|
||||
class ClusterConfigUpdateResponse(BaseModel):
|
||||
"""Результат сохранения конфигурации кластера на диск."""
|
||||
|
||||
cluster_name: str
|
||||
updated_kind_config_yaml: bool = Field(description="Был ли перезаписан kind-config.yaml")
|
||||
message: str
|
||||
registered_in_kind: bool
|
||||
kind_note: str
|
||||
summary: ClusterSummary = Field(description="Актуальная сводка для строки таблицы / панели")
|
||||
|
||||
Reference in New Issue
Block a user