Files
KindClustersDashboard/app/docs/api_routes.md
T
inecs 96d5f6ebee UI/документация: крошки, метрики узлов в stats, правки навигации и подвала
- Документация: хлебные крошки; секции H2 в одной карточке; заголовок вкладки от H1
- Навигация: активна только текущая пилюля (Панель без постоянного home-стиля)
- GET /api/v1/stats: cluster_resources (docker stats CPU/RAM/I/O по узлам kind)
- Панель: блок ресурсов в карточке статистики; убраны строки подвала про api_routes/clusters
- Удалён app/docs/README.md; крошки app/docs → api_routes.md; README корня обновлён
2026-04-04 06:37:36 +03:00

503 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Описание REST API веб-интерфейса Kind Clusters Dashboard
**Базовый префикс:** `/api/v1`
**Автор:** Сергей Антропов — [devops.org.ru](https://devops.org.ru)
## Как смотреть документацию
| Способ | URL / путь |
|--------|------------|
| Swagger UI (OpenAPI) | `http://127.0.0.1:<порт>/docs` (порт на хосте по умолчанию **8080**, см. `KIND_K8S_WEB_PORT`; 6000 на хосте блокируется Chrome) |
| ReDoc | `http://127.0.0.1:<порт>/redoc` |
| Health (JSON) | `http://127.0.0.1:<порт>/api/v1/health` |
| Документация проекта | `http://127.0.0.1:<порт>/documentation` — по умолчанию **README** (`GET /api/v1/docs/readme`); ссылки на `app/docs/*.md` открываются в той же странице (`?path=...` + `GET /api/v1/docs/file`); рендер **Markdown** в браузере (**marked** + **DOMPurify** из `app/static/js/vendor/`, без CDN) |
| Этот файл | `app/docs/api_routes.md` в репозитории |
С **веб-панели** (`GET /`) пункты меню **Swagger**, **ReDoc** и **Health** вызывают `window.open` с именами окон `kind_swagger`, `kind_redoc`, `kind_health` (отдельное окно, повторный клик переиспользует то же окно). Пункт **Документация** открывает `GET /documentation` в той же вкладке.
## Веб-интерфейс и статика (не JSON)
| Маршрут | Описание |
|---------|----------|
| `GET /` | HTML-панель: единая карточка «панель + среда», статистика, создание кластера (прогресс, **журнал** `kind create`, отмена), таблица кластеров с **иконками** действий и **всплывающими подсказками**, модалка узлов/подов; шапка — пилюли, Swagger / ReDoc / Health в отдельных окнах. |
| `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).
**kubectl на хосте не обязателен:** бинарник есть в образе; узлы и поды доступны через API (**`GET /api/v1/clusters/{name}/workloads`**) и веб-UI. Для интерактивной консоли из корня репозитория при запущенном compose: **`make docker kubectl CLUSTER=<имя>`** (или **`make podman kubectl …`**), внутри контейнера kubeconfig — **`/work/clusters/<имя>/kubeconfig`**. Подробности — **README.md** (раздел «kubectl без установки на хост»).
---
## Сводка маршрутов API
| Метод | Путь | Кратко |
|-------|------|--------|
| GET | `/api/v1/health` | Среда: kind, kubectl, движок контейнеров |
| GET | `/api/v1/docs/readme` | Текст **README.md** (`text/markdown`; страница `/documentation` без `path`) |
| GET | `/api/v1/docs/file` | Текст одного **`.md`** под `app/docs/` (query `path=app/docs/…`; для `/documentation?path=…`) |
| GET | `/api/v1/versions` | Теги `kindest/node` (Docker Hub) или пусто при `KIND_K8S_SKIP_VERSION_LIST` |
| GET | `/api/v1/stats` | Сводка для дашборда |
| GET | `/api/v1/clusters` | Список кластеров |
| POST | `/api/v1/clusters` | Создание в фоне (**202** + `job_id`) |
| POST | `/api/v1/clusters/{name}/start` | Запуск: **200**`docker start` узлов (кластер в kind); **202** + `job_id` — фоновый `kind create` по сохранённому `kind-config.yaml` |
| POST | `/api/v1/clusters/{name}/stop` | Остановка узлов (`docker`/`podman` **stop**), запись в kind сохраняется |
| GET | `/api/v1/clusters/{name}` | Детали + `kubectl get nodes` при наличии kubeconfig |
| GET | `/api/v1/clusters/{name}/kubeconfig` | Скачать файл kubeconfig |
| GET | `/api/v1/clusters/{name}/workloads` | Узлы и поды (`kubectl`) |
| DELETE | `/api/v1/clusters/{name}` | Удалить кластер и данные в `clusters/` |
| GET | `/api/v1/jobs` | Последние задания создания |
| GET | `/api/v1/jobs/{job_id}` | Статус одного задания (включая `progress_stage`, `progress_percent`) |
| POST | `/api/v1/jobs/{job_id}/cancel` | Запросить отмену создания (между этапами; `kind create` до конца не прерывается) |
### Фоновые задания (jobs)
- Хранятся **только в памяти** процесса uvicorn; после перезапуска контейнера история обнуляется.
- В памяти держится не более **200** записей; при превышении старые задания вытесняются (`app/core/job_store.py`).
- Создание кластера: `POST /api/v1/clusters` → опрос `GET /api/v1/jobs/{job_id}` (как в веб-UI).
- В ответе задания поля **`progress_stage`** (текст этапа) и **`progress_percent`** (0–100) обновляются во время создания.
- Поле **`progress_log`** — массив последних строк журнала (вывод `kind create`: pull образов, подъём нод и т.д.); размер ограничен (см. `KIND_K8S_JOB_LOG_MAX_LINES` в коде `job_store`, по умолчанию до **500** строк в буфере, в JSON отдаётся хвост).
- Тип задания **`kind`**: `create_cluster` или `start_cluster` (повторный подъём по `clusters/<имя>/kind-config.yaml`).
- Статус **`cancelled`** — пользователь запросил отмену (`POST .../cancel`); этап `kind create cluster` до завершения не прерывается.
---
## GET /api/v1/health
Проверка: `kind`/`kubectl` в PATH и ответ движка контейнеров (`docker info` / `podman info` по `CONTAINER_CLI`).
`status`: `ok` — всё готово к созданию кластеров; `degraded` — чего-то не хватает (см. поля ниже).
**Пример ответа 200 (JSON):**
```json
{
"status": "ok",
"kind_in_path": true,
"kubectl_in_path": true,
"container_cli": "docker",
"container_engine_ok": true,
"container_engine_detail": null
}
```
**Если сокет Docker недоступен:**
```json
{
"status": "degraded",
"kind_in_path": true,
"kubectl_in_path": true,
"container_cli": "docker",
"container_engine_ok": false,
"container_engine_detail": "Cannot connect to the Docker daemon..."
}
```
---
## GET /api/v1/docs/readme
Сырое содержимое **README.md** проекта в кодировке UTF-8, заголовок **`Content-Type: text/markdown; charset=utf-8`**.
Используется страницей **`GET /documentation`**: скрипт `documentation.js` загружает текст и превращает его в HTML через **marked** и **DOMPurify** (файлы лежат в репозитории: `app/static/js/vendor/`, без внешних CDN).
**Ошибка 404:** файл не найден. В Compose смонтируйте `./README.md:/opt/kind-k8s/README.md:ro`, задайте `KIND_K8S_README_PATH` или пересоберите образ (`COPY README.md`). См. `app/core/readme_doc.py`.
---
## GET /api/v1/docs/file
Параметр запроса **`path`** — относительный путь вида **`app/docs/<имя>.md`**. Допускаются только такие пути (префикс `app/docs/`, расширение `.md`, без `..`); иначе **404**.
Тело ответа — UTF-8 Markdown, **`Content-Type: text/markdown; charset=utf-8`**.
**Пример запроса:**
```http
GET /api/v1/docs/file?path=app%2Fdocs%2Fapi_routes.md HTTP/1.1
Host: 127.0.0.1:8080
Accept: text/markdown
```
**Пример начала тела ответа 200** (не JSON, текст Markdown):
```markdown
# Описание REST API веб-интерфейса Kind Clusters Dashboard
**Базовый префикс:** `/api/v1`
```
Логика проверки пути: `app/core/readme_doc.py` (`resolve_app_docs_markdown`).
---
## GET /api/v1/versions
Список стабильных тегов `kindest/node` с Docker Hub (для выпадающего списка в UI).
При `KIND_K8S_SKIP_VERSION_LIST=1` список пустой.
**Пример ответа 200:**
```json
{
"tags": ["v1.32.0", "v1.31.4"],
"skipped": false
}
```
**Пример при пропуске загрузки:**
```json
{
"tags": [],
"skipped": true,
"reason": "KIND_K8S_SKIP_VERSION_LIST"
}
```
---
## GET /api/v1/stats
Сводная статистика для дашборда и **метрики запущенных узлов kind** (один вызов `docker stats` / `podman stats` на набор имён контейнеров `имя-control-plane`, `имя-worker`…).
**Пример ответа 200:**
```json
{
"kind_clusters_count": 1,
"local_cluster_dirs_count": 1,
"total_workers_from_meta": 2,
"jobs_total": 3,
"jobs_recent_failed": 0,
"cluster_resources": [
{
"cluster_name": "dev",
"nodes": [
{
"container_name": "dev-control-plane",
"cpu_percent": "2.50%",
"memory_usage": "512MiB / 7.7GiB",
"memory_percent": "6.50%",
"net_io": "1.2MB / 800kB",
"block_io": "2GB / 150MB",
"pids": 245
},
{
"container_name": "dev-worker",
"cpu_percent": "1.00%",
"memory_usage": "380MiB / 7.7GiB",
"memory_percent": "4.80%",
"net_io": "512kB / 400kB",
"block_io": "500MB / 50MB",
"pids": 198
}
],
"note": null
}
],
"cluster_resources_error": null
}
```
- `total_workers_from_meta` может быть `null`, если ни в одном `meta.json` нет `worker_nodes`.
- `jobs_total` — число заданий в текущей памяти процесса (не более 200).
- `jobs_recent_failed` — сколько заданий в этом хранилище сейчас в статусе `failed` (не «последние N», а счётчик по всему снимку).
- `cluster_resources` — по каждому имени из `kind get clusters`; если узлы остановлены, `nodes` пустой, в `note` пояснение.
- `cluster_resources_error` — если CLI (`CONTAINER_CLI`) не найден в PATH и т.п.; тогда `cluster_resources` может быть пустым.
---
## GET /api/v1/clusters
Список: объединение `kind get clusters` и подкаталогов `clusters/*` (без дубликатов).
**Пример ответа 200 (массив):**
```json
[
{
"name": "dev",
"registered_in_kind": true,
"has_local_kubeconfig": true,
"meta": {
"cluster_name": "dev",
"kubernetes_version_tag": "v1.29.4",
"node_image": "kindest/node:v1.29.4",
"worker_nodes": 2,
"kubeconfig_patched_for_host": true
}
}
]
```
---
## GET /api/v1/jobs
Список последних фоновых заданий (создание кластера), от новых к старым.
**Query:** `limit` (1–200, по умолчанию **30**).
**Пример ответа 200 (массив `JobView`):**
```json
[
{
"job_id": "abc123",
"kind": "create_cluster",
"status": "success",
"cluster_name": "dev",
"created_at_utc": "2026-04-04T12:00:00+00:00",
"message": "Кластер создан",
"result": { "cluster_name": "dev", "kubernetes_version_tag": "v1.29.4" },
"progress_stage": null,
"progress_percent": null
}
]
```
---
## POST /api/v1/jobs/{job_id}/cancel
Запрос отмены создания кластера. Пока задание в статусе `queued` или `running`, между этапами выполняется проверка флага; после уже запущенного `kind create cluster` нужно дождаться окончания этого шага.
**Пример ответа 200:**
```json
{
"job_id": "a1b2…",
"cancel_requested": true,
"message": "Отмена обрабатывается между этапами; во время kind create дождитесь окончания шага"
}
```
**Ошибка 400:** задание уже завершено.
**Ошибка 404:** неизвестный `job_id`.
---
## GET /api/v1/clusters/{name}/kubeconfig
Скачать файл `kubeconfig` (ответ — тело файла, `Content-Disposition` с именем `kubeconfig-{name}.yaml`).
**Ошибка 404:** файла нет в `clusters/{name}/`.
**Ошибка 400:** некорректное имя кластера.
---
## GET /api/v1/clusters/{name}/workloads
`kubectl get nodes -o wide` и `kubectl get pods -A` по сохранённому kubeconfig.
**Пример ответа 200:**
```json
{
"cluster_name": "dev",
"nodes_rc": 0,
"nodes_output": "NAME STATUS ROLES ...",
"pods_rc": 0,
"pods_output": "NAMESPACE NAME READY STATUS ...",
"error": null
}
```
Если kubeconfig нет: `error` — строка вида **`Нет сохранённого kubeconfig в clusters/<имя>/`**, поля вывода kubectl могут быть `null`.
**Ошибка 400:** некорректное имя кластера.
---
## GET /api/v1/clusters/{name}
Детали и попытка `kubectl get nodes -o wide` с **сохранённого** `clusters/{name}/kubeconfig` (если файл есть).
**Пример ответа 200:**
```json
{
"name": "dev",
"registered_in_kind": true,
"has_local_kubeconfig": true,
"kubeconfig_path": "/work/clusters/dev/kubeconfig",
"meta": { "worker_nodes": 2 },
"kubectl_get_nodes_rc": 0,
"kubectl_get_nodes": "NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME\n..."
}
```
---
## POST /api/v1/clusters
Создание кластера **в фоне** (ответ **202**).
**Тело запроса (JSON):**
```json
{
"name": "dev",
"kubernetes_version": "1.29.4",
"workers": 2
}
```
**Пример ответа 202:**
```json
{
"job_id": "a1b2c3d4e5f6...",
"status": "queued",
"message": "Создание кластера выполняется в фоне; опросите GET /api/v1/jobs/{job_id}"
}
```
**Ошибка 400:** невалидное имя кластера или тело не проходит валидацию Pydantic.
**Ошибка 409 (кластер уже есть в kind):**
```json
{
"detail": "Кластер с таким именем уже есть в kind"
}
```
---
## POST /api/v1/clusters/{name}/start
Запуск кластера двумя сценариями:
1. Кластер **есть** в `kind get clusters` (узлы когда-либо создавались) — выполняется **`docker start`** / **`podman start`** для всех контейнеров с именами вида `<имя>-control-plane`, `<имя>-worker`, … Ответ **200**.
2. В **kind** кластера **нет**, но в `clusters/<имя>/kind-config.yaml` файл **есть** — ставится фоновое задание **`start_cluster`** (как при создании: `kind create` по сохранённому конфигу, журнал в `GET /jobs/{job_id}`). Ответ **202** + `job_id`.
**Пример ответа 200 (контейнеры запущены):**
```json
{
"name": "dev",
"mode": "containers",
"containers_started_ok": true,
"summary": "dev-control-plane: OK; dev-worker: OK; dev-worker2: OK"
}
```
**Пример ответа 202 (подъём по конфигу):**
```json
{
"job_id": "cafebabe...",
"status": "queued",
"message": "Подъём кластера по kind-config.yaml; опросите GET /api/v1/jobs/{job_id}"
}
```
**Ошибка 400:** некорректное имя или нет ни кластера в kind, ни `kind-config.yaml` в `clusters/<имя>/`.
---
## POST /api/v1/clusters/{name}/stop
Остановка **всех** контейнеров узлов кластера (`docker stop` / `podman stop` по префиксу имени). Запись кластера в kind **не удаляется**; позже можно снова вызвать **POST …/start** (режим `containers`).
**Пример ответа 200:**
```json
{
"name": "dev",
"containers_stopped_ok": true,
"summary": "dev-control-plane: OK; dev-worker: OK"
}
```
**Ошибка 400:** некорректное имя кластера.
---
## GET /api/v1/jobs/{job_id}
Статус фонового задания создания.
**В процессе (пример 200):**
```json
{
"job_id": "a1b2...",
"kind": "create_cluster",
"status": "running",
"cluster_name": "dev",
"created_at_utc": "2026-04-04T12:00:00+00:00",
"message": null,
"result": null,
"progress_stage": "kind create cluster (скачивание образов и подъём нод — может занять несколько минут)",
"progress_percent": 28,
"progress_log": [
"[12%] Подготовка каталога и kind-config",
"--- kind create cluster ---",
"Creating cluster \"dev\" ...",
" • Ensuring node image (kindest/node:v1.29.4) 🖼 ..."
]
}
```
**Успех (пример 200):**
```json
{
"job_id": "a1b2...",
"kind": "create_cluster",
"status": "success",
"cluster_name": "dev",
"created_at_utc": "2026-04-04T12:00:00+00:00",
"message": "Кластер создан",
"progress_log": ["[95%] Финализация", "kubectl wait nodes: ..."],
"result": {
"cluster_name": "dev",
"kubernetes_version_tag": "v1.29.4",
"node_image": "kindest/node:v1.29.4",
"workers": 2,
"kubeconfig_path": "/work/clusters/dev/kubeconfig",
"kubeconfig_patched_for_host": true,
"nodes_ready": true,
"nodes_ready_message": "..."
}
}
```
**Ошибка 404:**
```json
{
"detail": "Задание не найдено"
}
```
---
## DELETE /api/v1/clusters/{name}
`kind delete cluster` и удаление каталога `clusters/{name}/`.
**Пример ответа 200:**
```json
{
"name": "dev",
"kind_delete_ok": true,
"summary": "kind delete: OK; удалена папка /work/clusters/dev"
}
```
**Ошибка 400:** некорректное имя кластера.
**Ошибка 500:** логическая ошибка удаления (тело с `detail`).
---
## GET /
HTML-дашборд (не JSON): см. раздел «Веб-интерфейс и статика» выше.