Журнал: пагинация 30 записей на страницу, API offset/total_pages

- job_journal: collect_recent_journal_entries_page_sync(limit, offset)
- GET /api/v1/journal/recent: limit по умолчанию 30, offset, total, page, total_pages
- journal.html/js: навигация Первая/Назад/номера/Вперёд/Последняя, стили
- app/docs/api_routes.md: описание query и пример ответа
- Прочие изменения UI/API (аддоны, helm, job_journal в кластерах) в том же коммите
This commit is contained in:
Sergey Antropoff
2026-04-04 11:16:43 +03:00
parent 52538d9816
commit 17f6233fd7
22 changed files with 2811 additions and 4 deletions
+125
View File
@@ -283,3 +283,128 @@ class ClusterConfigUpdateResponse(BaseModel):
registered_in_kind: bool
kind_note: str
summary: ClusterSummary = Field(description="Актуальная сводка для строки таблицы / панели")
# --- Helm-аддоны (страница «Дополнения кластера») ---
class ClusterAddonsStatusResponse(BaseModel):
"""Какие релизы Helm обнаружены для кластера (по helm list -A)."""
cluster_name: str
ingress_nginx: bool = Field(description="Релиз ingress-nginx в ns ingress-nginx")
kube_prometheus_stack: bool = Field(description="Релиз kube-prometheus-stack в ns monitoring")
metrics_server: bool = Field(description="Релиз metrics-server в ns kube-system")
istio_base: bool
istiod: bool
kiali_server: bool
istio_mesh_ready: bool = Field(
description="Установлены istio-base, istiod и kiali-server в istio-system",
)
class HelmAddonApplyResponse(BaseModel):
"""Результат синхронной установки/удаления чарта."""
ok: bool = True
message: str
log: str = Field(default="", description="Сводный вывод helm/kubectl")
class IngressNginxInstallRequest(BaseModel):
"""Тело POST …/addons/ingress-nginx — установка ingress-nginx."""
chart_version: str | None = Field(
default=None,
max_length=64,
description="Версия чарта; пусто — последняя из репозитория",
)
class KubePrometheusStackInstallRequest(BaseModel):
"""Тело POST …/addons/kube-prometheus-stack."""
grafana_admin_user: str = Field(default="admin", min_length=1, max_length=256)
grafana_admin_password: str = Field(..., min_length=8, max_length=256)
chart_version: str | None = Field(
default=None,
max_length=64,
description="Версия чарта kube-prometheus-stack; пусто — последняя",
)
class MetricsServerInstallRequest(BaseModel):
"""Тело POST …/addons/metrics-server (все поля необязательны)."""
chart_version: str | None = Field(
default=None,
max_length=64,
description="Версия чарта metrics-server; пусто — последняя",
)
class IstioKialiInstallRequest(BaseModel):
"""Тело POST …/addons/istio-kiali — Istio + Kiali со входом по логину."""
kiali_username: str = Field(default="kiali-admin", min_length=1, max_length=256)
kiali_password: str = Field(..., min_length=8, max_length=256)
istio_chart_version: str | None = Field(
default=None,
max_length=64,
description="Версия чартов istio/base и istio/istiod; пусто — последняя",
)
kiali_chart_version: str | None = Field(
default=None,
max_length=64,
description="Версия чарта kiali/kiali-server; пусто — последняя",
)
class HelmAddonChartVersionsResponse(BaseModel):
"""Список версий чартов для выпадающих списков на странице аддонов (после helm repo update)."""
ingress_nginx: list[str] = Field(default_factory=list)
kube_prometheus_stack: list[str] = Field(default_factory=list)
metrics_server: list[str] = Field(default_factory=list)
istio: list[str] = Field(default_factory=list, description="Версии istio/base (для base+istiod)")
kiali_server: list[str] = Field(default_factory=list)
class JournalEntryModel(BaseModel):
"""Одна запись из ``clusters/<имя>/journal/jobs_history.json``."""
model_config = {"extra": "allow"}
version: int | None = Field(default=None, description="Версия схемы записи в файле")
job_id: str = ""
kind: str = ""
cluster_name: str = ""
status: str = ""
message: str | None = None
created_at_utc: str = ""
finished_at_utc: str | None = None
log_lines: list[str] = Field(default_factory=list)
result: dict[str, Any] | None = None
source_cluster: str | None = Field(
default=None,
description="Имя каталога кластера в ответе GET /journal/recent",
)
class ClusterJournalResponse(BaseModel):
"""Содержимое журнала одного кластера."""
cluster_name: str
file_version: int | None = None
entries: list[JournalEntryModel] = Field(default_factory=list)
class JournalRecentResponse(BaseModel):
"""Страница агрегированного журнала (все ``clusters/*/journal/jobs_history.json``)."""
limit: int = Field(description="Размер страницы (записей за запрос)")
offset: int = Field(description="Смещение от начала отсортированного списка (0 = первая страница)")
total: int = Field(ge=0, description="Всего записей во всех журналах")
page: int = Field(ge=1, description="Номер текущей страницы с 1")
total_pages: int = Field(ge=0, description="Число страниц при данном limit; 0 если записей нет")
entries: list[JournalEntryModel] = Field(default_factory=list)