Веб-UI кластера: страница деталей, kubectl по карточкам, мета 3 колонки

- Страница /cluster/<имя>: сводка, ресурсы узлов полосами, отдельные карточки на каждый kubectl get … -o json, рестарт пода.
- API: overview с блоками k8s_*, POST pods/restart, расширенный набор ресурсов (-A).
- Панель: спиннер загрузки, правки дашборда и стилей; документация api_routes, compose и прочие сопутствующие изменения.
This commit is contained in:
Sergey Antropoff
2026-04-04 09:13:08 +03:00
parent 4546f50aef
commit af0d1705cc
17 changed files with 2605 additions and 208 deletions
+41 -2
View File
@@ -221,8 +221,8 @@ def aggregate_kind_cluster_resources(blocks: list[KindClusterResources]) -> Aggr
cpu_l = f"ср. {av_cpu:.1f}%" if av_cpu is not None else ""
mem_l = f"ср. {av_mem_pct:.1f}%" if av_mem_pct is not None else ""
ratio_l = f"ср. {av_mem_ratio:.1f}%" if av_mem_ratio is not None else ""
net_l = f"Σ {_fmt_bytes_ru(sum_net)}" if sum_net > 0 else ""
disk_l = f"Σ {_fmt_bytes_ru(sum_blk)}" if sum_blk > 0 else ""
net_l = f"всего {_fmt_bytes_ru(sum_net)}" if sum_net > 0 else ""
disk_l = f"всего {_fmt_bytes_ru(sum_blk)}" if sum_blk > 0 else ""
logger.debug(
"aggregate_kind_cluster_resources: узлов=%s cpu_ring=%.1f net_ring=%.1f",
@@ -322,3 +322,42 @@ def collect_kind_clusters_resource_stats() -> tuple[list[KindClusterResources],
blocks.append(KindClusterResources(cluster_name=cluster_name, nodes=stats, note=None))
return blocks, None
def collect_single_cluster_resource_stats(cluster_name: str) -> tuple[KindClusterResources, str | None]:
"""
Метрики узлов **одного** кластера (без полного прохода по всем kind-кластерам).
Если кластера нет в ``kind get clusters`` — пустой список узлов и пояснение в ``note``.
"""
name = cluster_name.strip()
cli = _container_cli_bin()
if not shutil.which(cli):
return KindClusterResources(cluster_name=name, nodes=[], note=None), f"CLI «{cli}» не найден в PATH"
if name not in list_registered_kind_clusters():
return KindClusterResources(
cluster_name=name,
nodes=[],
note="Кластер не в списке kind — метрики контейнеров-узлов недоступны",
), None
all_lines, ps_err = _all_container_names_ps_a(cli)
if ps_err:
return KindClusterResources(cluster_name=name, nodes=[], note=None), ps_err
running = _list_running_container_names(cli)
prefix = f"{name}-"
node_names = _sort_kind_node_containers([n for n in all_lines if n.startswith(prefix)])
active = [n for n in node_names if n in running]
if not active:
return KindClusterResources(
cluster_name=name,
nodes=[],
note="узлы остановлены или контейнеры отсутствуют",
), None
stats = _stats_for_container_names(cli, active)
order = {n: i for i, n in enumerate(node_names)}
stats.sort(key=lambda s: order.get(s.container_name, 99))
return KindClusterResources(cluster_name=name, nodes=stats, note=None), None