Веб-UI кластера: страница деталей, kubectl по карточкам, мета 3 колонки
- Страница /cluster/<имя>: сводка, ресурсы узлов полосами, отдельные карточки на каждый kubectl get … -o json, рестарт пода. - API: overview с блоками k8s_*, POST pods/restart, расширенный набор ресурсов (-A). - Панель: спиннер загрузки, правки дашборда и стилей; документация api_routes, compose и прочие сопутствующие изменения.
This commit is contained in:
+121
-7
@@ -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` (если файл есть).
|
||||
|
||||
Reference in New Issue
Block a user