"""Модели запросов/ответов REST API веб-интерфейса. Автор: Сергей Антропов Сайт: https://devops.org.ru """ from __future__ import annotations from typing import Any, Literal from pydantic import BaseModel, Field class ClusterCreateRequest(BaseModel): """Тело POST /api/v1/clusters — создание кластера.""" name: str = Field(..., min_length=1, max_length=63, description="DNS-имя кластера (a-z0-9-)") kubernetes_version: str = Field( ..., min_length=1, description="Версия Kubernetes / тег kindest/node, например 1.29.4 или v1.29.4", ) workers: int = Field(2, ge=0, le=20, description="Число worker-нод (0–20)") class ClusterCreateAccepted(BaseModel): """Ответ 202 — задание поставлено в очередь.""" job_id: str status: Literal["queued"] = "queued" message: str = "Создание кластера выполняется в фоне; опросите GET /api/v1/jobs/{job_id}" class JobView(BaseModel): """Статус фонового задания.""" job_id: str kind: str status: Literal["queued", "running", "success", "failed", "cancelled"] cluster_name: str | None created_at_utc: str message: str | None = None result: dict[str, Any] | None = None progress_stage: str | None = Field(default=None, description="Текущий этап операции (пока задание активно)") progress_percent: int | None = Field(default=None, description="Прогресс 0–100 для индикатора в UI") progress_log: list[str] = Field( default_factory=list, description="Хвост журнала (скачивание образа, длительные команды, этапы); при опросе GET /jobs/{id}", ) class ClusterSummary(BaseModel): """Элемент списка кластеров.""" name: str registered_in_kind: bool kind_nodes_running: bool = Field( default=False, description="Запущены контейнеры узлов kind (имя-control-plane, имя-worker…; docker/podman ps)", ) has_local_kubeconfig: bool meta: dict[str, Any] = Field(default_factory=dict) class KindNodeResourceStat(BaseModel): """Одна нода kind (контейнер): снимок ``docker stats`` / ``podman stats``.""" container_name: str cpu_percent: str | None = Field(default=None, description="Доля CPU, напр. 1.23%") memory_usage: str | None = Field(default=None, description="Использование / лимит, напр. 120MiB / 7.7GiB") memory_percent: str | None = Field(default=None, description="Процент памяти от лимита контейнера") net_io: str | None = Field(default=None, description="Сетевой I/O (накопительно за жизнь контейнера)") block_io: str | None = Field(default=None, description="Блочный I/O") pids: int | None = Field(default=None, description="Число процессов в контейнере") class KindClusterResources(BaseModel): """Ресурсы узлов одного кластера kind (только запущенные контейнеры).""" cluster_name: str nodes: list[KindNodeResourceStat] = Field(default_factory=list) note: str | None = Field(default=None, description="Пояснение, если узлов нет или ошибка списка") class AggregateResourcesSummary(BaseModel): """Сводка по всем запущенным узлам kind для донат-диаграмм на главной.""" nodes_count: int = Field(0, description="Число учтённых контейнеров-нод") cpu_ring: float = Field(0, ge=0, le=100, description="Заполнение кольца «средний CPU», 0–100") cpu_label: str = Field("—", description="Подпись в центре доната") memory_percent_ring: float = Field(0, ge=0, le=100) memory_percent_label: str = Field("—") memory_used_ratio_ring: float = Field(0, ge=0, le=100, description="Доля занятой RAM от лимита по строке MemUsage") memory_used_ratio_label: str = Field("—") network_ring: float = Field(0, ge=0, le=100, description="Индикатор суммарного сетевого I/O (условная шкала)") network_label: str = Field("—") disk_ring: float = Field(0, ge=0, le=100, description="Индикатор суммарного блочного I/O") disk_label: str = Field("—") class StatsResponse(BaseModel): """Краткая статистика для дашборда.""" container_cli: str = Field( description="CLI движка контейнеров (docker или podman): узлы kind и метрики собираются через него", ) kind_clusters_count: int local_cluster_dirs_count: int total_workers_from_meta: int = Field( 0, ge=0, description="Сумма worker_nodes по meta.json каталогов; 0 если ни в одном meta нет поля или данных", ) jobs_total: int jobs_recent_failed: int cluster_resources: list[KindClusterResources] = Field( default_factory=list, description="CPU/RAM/I/O узлов kind по данным container CLI", ) cluster_resources_error: str | None = Field( default=None, description="Глобальная ошибка сбора метрик (например CLI не найден)", ) aggregate_cluster_resources: AggregateResourcesSummary = Field( default_factory=AggregateResourcesSummary, description="Агрегаты по узлам для донатов на главной странице", ) class ClusterWorkloadsResponse(BaseModel): """Вывод kubectl по кластеру (узлы и поды).""" cluster_name: str nodes_rc: int | None = None nodes_output: str | None = None pods_rc: int | None = None pods_output: str | None = None error: str | None = None