Веб-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:
@@ -17,6 +17,7 @@ from fastapi.responses import FileResponse, JSONResponse
|
||||
from core.cluster_lifecycle import (
|
||||
KindClusterError,
|
||||
cluster_summary_for_api,
|
||||
configured_container_cli,
|
||||
create_cluster_non_interactive,
|
||||
delete_kind_cluster_and_data,
|
||||
kubectl_nodes_wide,
|
||||
@@ -27,7 +28,11 @@ from core.cluster_lifecycle import (
|
||||
stop_kind_cluster_containers,
|
||||
validate_cluster_name,
|
||||
)
|
||||
from core.container_resource_stats import collect_kind_clusters_resource_stats
|
||||
from core.container_resource_stats import (
|
||||
aggregate_kind_cluster_resources,
|
||||
collect_kind_clusters_resource_stats,
|
||||
running_kind_clusters_mask,
|
||||
)
|
||||
from core.job_store import (
|
||||
JobRecord,
|
||||
end_job_tracking,
|
||||
@@ -39,6 +44,7 @@ from core.job_store import (
|
||||
from core.kind_guard import kind_cluster_lock
|
||||
from kind_k8s_paths import clusters_dir
|
||||
from models.schemas import (
|
||||
AggregateResourcesSummary,
|
||||
ClusterCreateAccepted,
|
||||
ClusterCreateRequest,
|
||||
ClusterSummary,
|
||||
@@ -100,7 +106,6 @@ def _stats_sync() -> StatsResponse:
|
||||
subdirs = sorted(p.name for p in cdir.iterdir() if p.is_dir() and not p.name.startswith("."))
|
||||
|
||||
total_workers = 0
|
||||
counted = False
|
||||
for name in subdirs:
|
||||
meta = read_meta_json(name)
|
||||
if not meta:
|
||||
@@ -110,7 +115,6 @@ def _stats_sync() -> StatsResponse:
|
||||
continue
|
||||
try:
|
||||
total_workers += int(w)
|
||||
counted = True
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
|
||||
@@ -118,15 +122,22 @@ def _stats_sync() -> StatsResponse:
|
||||
failed = sum(1 for j in jobs if j.status == "failed")
|
||||
|
||||
res_blocks, res_err = collect_kind_clusters_resource_stats()
|
||||
agg: AggregateResourcesSummary
|
||||
if res_err:
|
||||
agg = AggregateResourcesSummary()
|
||||
else:
|
||||
agg = aggregate_kind_cluster_resources(res_blocks)
|
||||
|
||||
return StatsResponse(
|
||||
container_cli=configured_container_cli(),
|
||||
kind_clusters_count=len(kind_names),
|
||||
local_cluster_dirs_count=len(subdirs),
|
||||
total_workers_from_meta=total_workers if counted else None,
|
||||
total_workers_from_meta=total_workers,
|
||||
jobs_total=len(jobs),
|
||||
jobs_recent_failed=failed,
|
||||
cluster_resources=res_blocks,
|
||||
cluster_resources_error=res_err,
|
||||
aggregate_cluster_resources=agg,
|
||||
)
|
||||
|
||||
|
||||
@@ -163,6 +174,7 @@ async def list_clusters() -> list[ClusterSummary]:
|
||||
if cdir.is_dir():
|
||||
dir_names = {p.name for p in cdir.iterdir() if p.is_dir() and not p.name.startswith(".")}
|
||||
all_names = sorted(kind_names | dir_names)
|
||||
running_map = running_kind_clusters_mask(all_names)
|
||||
out: list[ClusterSummary] = []
|
||||
for name in all_names:
|
||||
summary = cluster_summary_for_api(name)
|
||||
@@ -170,6 +182,7 @@ async def list_clusters() -> list[ClusterSummary]:
|
||||
ClusterSummary(
|
||||
name=str(summary["name"]),
|
||||
registered_in_kind=bool(summary["registered_in_kind"]),
|
||||
kind_nodes_running=bool(running_map.get(name, False)),
|
||||
has_local_kubeconfig=bool(summary["has_local_kubeconfig"]),
|
||||
meta=dict(summary["meta"]) if isinstance(summary.get("meta"), dict) else {},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user