Веб-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
+121 -7
View File
@@ -19,14 +19,15 @@
| Маршрут | Описание |
|---------|----------|
| `GET /` | HTML-панель: единая карточка «панель + среда», статистика, создание кластера (прогресс, **журнал** в реальном времени, **«Отменить»** прерывает текущую команду), **старт/стоп** кластера с тем же журналом (фоновые **POST …/start** и **…/stop**), таблица **последних заданий** с кнопкой **«Очистить завершённые»** (**DELETE /api/v1/jobs**), модалка узлов/подов; шапка — пилюли, Swagger / ReDoc / Health в отдельных окнах. |
| `GET /` | HTML-панель: единая карточка «панель + среда», статистика, **ссылка с имени кластера на** `GET /cluster/<имя>` (сводка ресурсов, kubectl, действия), **старт/стоп** кластера с тем же журналом (фоновые **POST …/start** и **…/stop**), модалка узлов/подов; шапка — пилюли, Swagger / ReDoc / Health в отдельных окнах. |
| `GET /cluster/{name}` | HTML **страница кластера**: донаты «Ресурсы узлов (сводка)», карточки узлов, **таблицы Kubernetes** из JSON (`kubectl get … -o json`), кнопка **Рестарт** у подов (**`POST …/pods/restart`**), те же кнопки действий, что в таблице на главной; данные — **`GET /api/v1/clusters/{name}/overview`** (автообновление с интервалом панели). |
| `GET /documentation` | HTML-оболочка; **`documentation.js`**: без `path`**`GET /api/v1/docs/readme`**, с `?path=app/docs/…`**`GET /api/v1/docs/file`**; разбор Markdown из **`/static/js/vendor/`** (marked, DOMPurify). Каждая секция по **H2****одна карточка** (заголовок h2 и содержимое до следующего h2 вместе). Заголовок вкладки браузера: **«Документация — …»** + текст **первого H1** документа + имя приложения (`KIND_K8S_APP_TITLE` на `body`). В шапке на этой странице активна только **Документация**; **Панель** как обычная пилюля (на дашборде активна **Панель**). Путь к README: `KIND_K8S_README_PATH` или `README.md` рядом с `app/`; в образе — `/opt/kind-k8s/README.md`. |
| `GET /ui` | Редирект **307** на `/` (удобный ярлык). |
| `GET /static/…` | CSS (`style.css`), скрипты панели (`js/dashboard.js`) и документации (`js/documentation.js`); базовый URL API задаётся атрибутом `data-api-base` на `<body>` (по умолчанию `/api/v1`). |
Шаблоны: `app/templates/base.html` (шапка, навигация), `app/templates/dashboard.html` (контент панели), `app/templates/documentation.html` (README).
Шаблоны: `app/templates/base.html` (шапка, навигация), `app/templates/dashboard.html` (контент панели), `app/templates/cluster_detail.html` (страница кластера), `app/templates/documentation.html` (README).
**kubectl на хосте не обязателен:** бинарник есть в образе; узлы и поды доступны через API (**`GET /api/v1/clusters/{name}/workloads`**) и веб-UI. Для интерактивной консоли из корня репозитория при запущенном compose: **`make docker kubectl CLUSTER=<имя>`** (или **`make podman kubectl …`**), внутри контейнера kubeconfig — **`/work/clusters/<имя>/kubeconfig`**. Перезапуск только веб-сервиса без `down`/`up`: **`make docker restart`** / **`make podman restart`**. Подробности — **README.md**.
**kubectl на хосте не обязателен:** бинарник есть в образе; узлы и поды доступны через API и веб-UI. Внутри контейнера веб-приложения `kubectl` использует временный kubeconfig с `server` через **`host.docker.internal:<порт>`** (см. `kubeconfig_patch.py`, `extra_hosts` в compose). Скачивание для хоста — **`GET …/kubeconfig`** (файл **`kubeconfig.host`** при наличии). Для консоли: **`make docker kubectl CLUSTER=<имя>`** **`/work/clusters/<имя>/kubeconfig`**; при сбое попробуйте kubectl **с хоста** с **`clusters/<имя>/kubeconfig.host`**. Перезапуск веб-сервиса: **`make docker restart`**. Подробности — **README.md**.
---
@@ -47,6 +48,8 @@
| GET | `/api/v1/clusters/{name}/kubeconfig` | Скачать файл kubeconfig |
| GET | `/api/v1/clusters/{name}/provision-log` | Полный журнал последнего **create_cluster** / **start_cluster** (JSON с диска) |
| GET | `/api/v1/clusters/{name}/workloads` | Узлы и поды (`kubectl`) |
| GET | `/api/v1/clusters/{name}/overview` | Сводка для страницы UI: метрики узлов, агрегаты, блоки **`k8s_*`** (JSON из `kubectl get … -o json`) |
| POST | `/api/v1/clusters/{name}/pods/restart` | Удалить под (`kubectl delete pod`) для мягкого рестарта |
| DELETE | `/api/v1/clusters/{name}` | Удалить кластер и данные в `clusters/` |
| GET | `/api/v1/jobs` | Последние задания (`progress_log` в ответе пустой — полный журнал только в GET по `job_id`) |
| GET | `/api/v1/jobs/{job_id}` | Статус задания + полный хвост `progress_log` (лимит см. `KIND_K8S_JOB_API_LOG_MAX_LINES`) |
@@ -218,9 +221,9 @@ Accept: text/markdown
"memory_used_ratio_ring": 5.8,
"memory_used_ratio_label": "ср. 5.8%",
"network_ring": 12.5,
"network_label": "Σ 1.71 MiB",
"network_label": "всего 1.71 MiB",
"disk_ring": 45.0,
"disk_label": "Σ 2.5 GiB"
"disk_label": "всего 2.5 GiB"
}
}
```
@@ -327,9 +330,11 @@ Accept: text/markdown
## GET /api/v1/clusters/{name}/kubeconfig
Скачать файл `kubeconfig` (ответ — тело файла, `Content-Disposition` с именем `kubeconfig-{name}.yaml`).
Скачать kubeconfig для **kubectl на машине пользователя** (ответ — тело файла, `Content-Disposition`: `kubeconfig-{name}.yaml`).
**Ошибка 404:** файла нет в `clusters/{name}/`.
При каждом запросе копируется **`clusters/{name}/kubeconfig`** и патчится `server=https://<KIND_K8S_KUBECONFIG_CLIENT_HOST или localhost>:<порт>` (порт из `docker port … 6443/tcp`). Если патч не удалился — отдаётся сохранённый **`kubeconfig.host`** или сырой **`kubeconfig`**.
**Ошибка 404:** нет ни `kubeconfig.host`, ни `kubeconfig` в `clusters/{name}/`.
**Ошибка 400:** некорректное имя кластера.
@@ -391,6 +396,115 @@ Accept: text/markdown
---
## GET /api/v1/clusters/{name}/overview
Единый ответ для **страницы кластера** (`GET /cluster/{name}`): флаги и `meta` как в списке кластеров, **метрики узлов** (docker/podman), **`aggregate_resources`** (как в `GET /stats` для донатов), данные Kubernetes в виде **`kubectl get … -o json`** (разбор `items` на фронтенде в таблицы).
- При отсутствии kubeconfig: **`kubeconfig_error`** — строка-пояснение; блоки **`k8s_*`** недоступны (см. ниже).
- Поля **`nodes_rc` / `nodes_output`**, **`pods_rc` / `pods_output`**, … — устаревший текстовый вывод; в текущей версии API могут быть **`null`** (UI использует только JSON-блоки).
- Блоки **`k8s_nodes`**, **`k8s_namespaces`**, **`k8s_pods`**, **`k8s_deployments`**, **`k8s_statefulsets`**, **`k8s_daemonsets`**, **`k8s_replicasets`**, **`k8s_jobs`**, **`k8s_cronjobs`**, **`k8s_services`**, **`k8s_ingresses`** (ресурс **`ingresses.networking.k8s.io -A`**), **`k8s_pvcs`** — объекты вида **`K8sListJsonBlock`**; для ресурсов в namespace везде **`kubectl get … -A`** (все пространства имён).
- **`ok`**: `true` при успешном `kubectl`;
- **`rc`**: код возврата;
- **`items`**: массив объектов из `kubectl` (как в API Kubernetes);
- **`message`**: текст ошибки при `ok: false`.
- **`resources_error`** — ошибка сбора метрик контейнерного CLI (как `cluster_resources_error` в `/stats`).
- **`cluster_resources`** — один блок `KindClusterResources` (узлы с полями `cpu_percent`, `memory_usage`, …).
**Пример ответа 200 (фрагмент):**
```json
{
"cluster_name": "dev",
"registered_in_kind": true,
"kind_nodes_running": true,
"has_local_kubeconfig": true,
"has_provision_log": true,
"meta": { "worker_nodes": 2, "kubernetes_version_tag": "v1.29.4" },
"resources_error": null,
"cluster_resources": {
"cluster_name": "dev",
"nodes": [
{
"container_name": "dev-control-plane",
"cpu_percent": "2.10%",
"memory_usage": "800MiB / 7.7GiB",
"memory_percent": "10.14%",
"net_io": "1.2MB / 800kB",
"block_io": "0B / 0B",
"pids": 120
}
],
"note": null
},
"aggregate_resources": {
"nodes_count": 3,
"cpu_ring": 5.2,
"cpu_label": "5.2%",
"memory_percent_ring": 12.0,
"memory_percent_label": "12%",
"memory_used_ratio_ring": 45.0,
"memory_used_ratio_label": "2.1 / 4.6 GiB",
"network_ring": 8.0,
"network_label": "↓ 10 MB",
"disk_ring": 3.0,
"disk_label": "R/W 2 MB"
},
"kubeconfig_error": null,
"k8s_nodes": {
"ok": true,
"rc": 0,
"items": [{ "metadata": { "name": "dev-control-plane" }, "status": { "conditions": [] } }],
"message": null
},
"k8s_pods": { "ok": true, "rc": 0, "items": [], "message": null },
"k8s_deployments": { "ok": true, "rc": 0, "items": [], "message": null },
"k8s_statefulsets": { "ok": true, "rc": 0, "items": [], "message": null },
"k8s_daemonsets": { "ok": true, "rc": 0, "items": [], "message": null },
"k8s_services": { "ok": true, "rc": 0, "items": [], "message": null },
"k8s_ingresses": { "ok": true, "rc": 0, "items": [], "message": null },
"k8s_namespaces": { "ok": true, "rc": 0, "items": [], "message": null },
"k8s_replicasets": { "ok": true, "rc": 0, "items": [], "message": null },
"k8s_jobs": { "ok": true, "rc": 0, "items": [], "message": null },
"k8s_cronjobs": { "ok": true, "rc": 0, "items": [], "message": null },
"k8s_pvcs": { "ok": true, "rc": 0, "items": [], "message": null }
}
```
**Ошибка 400:** некорректное имя кластера.
---
## POST /api/v1/clusters/{name}/pods/restart
Мягкий **рестарт пода**: выполняется **`kubectl delete pod`** в указанном namespace (под пересоздаётся, если им управляет Deployment/ReplicaSet и т.д.).
**Тело запроса (JSON):**
```json
{
"namespace": "default",
"pod": "my-app-7d4f8b9-xk2cp"
}
```
**Пример ответа 200:**
```json
{
"ok": true,
"cluster_name": "dev",
"namespace": "default",
"pod": "my-app-7d4f8b9-xk2cp",
"message": null
}
```
**Ошибка 400:** неверное имя кластера, namespace или pod (валидация как в Kubernetes).
**Ошибка 502:** `kubectl delete` завершился с ненулевым кодом (текст stderr в теле ответа от сервера).
---
## GET /api/v1/clusters/{name}
Детали и попытка `kubectl get nodes -o wide` с **сохранённого** `clusters/{name}/kubeconfig` (если файл есть).