Панель: журнал заданий, pull --progress plain, PTY/EIO, старт/стоп в фоне, очистка jobs
- Фоновые stop/start с job_id и poll; отмена с kill; docker pull plain + снятие ANSI - Лимиты журнала API/буфера; список jobs без progress_log; DELETE /jobs - UI: опрос чаще, подсказка при пустом логе, кнопка очистки завершённых
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
|
||||
| Маршрут | Описание |
|
||||
|---------|----------|
|
||||
| `GET /` | HTML-панель: единая карточка «панель + среда», статистика, создание кластера (прогресс, **журнал** `kind create`, отмена), таблица кластеров с **иконками** действий и **всплывающими подсказками**, модалка узлов/подов; шапка — пилюли, Swagger / ReDoc / Health в отдельных окнах. |
|
||||
| `GET /` | HTML-панель: единая карточка «панель + среда», статистика, создание кластера (прогресс, **журнал** операции в реальном времени, **отмена** с прерыванием текущей команды), таблица кластеров с **иконками** действий и **всплывающими подсказками**, модалка узлов/подов; шапка — пилюли, 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`). |
|
||||
@@ -41,15 +41,16 @@
|
||||
| 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 сохраняется |
|
||||
| POST | `/api/v1/clusters/{name}/start` | Запуск в фоне (**202** + `job_id`, поле `mode`: `containers` или `kind_config`); журнал — `GET /jobs/{job_id}` |
|
||||
| POST | `/api/v1/clusters/{name}/stop` | Остановка узлов в фоне (**202** + `job_id`, `mode`: `stop`); журнал — `GET /jobs/{job_id}` |
|
||||
| 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` до конца не прерывается) |
|
||||
| GET | `/api/v1/jobs` | Последние задания (`progress_log` в ответе пустой — полный журнал только в GET по `job_id`) |
|
||||
| GET | `/api/v1/jobs/{job_id}` | Статус задания + полный хвост `progress_log` (лимит см. `KIND_K8S_JOB_API_LOG_MAX_LINES`) |
|
||||
| DELETE | `/api/v1/jobs` | Удалить из памяти **завершённые** задания (`removed` — число записей) |
|
||||
| POST | `/api/v1/jobs/{job_id}/cancel` | Прервать задание: активная команда завершается принудительно (скачивание образа, создание кластера, старт/стоп узла) |
|
||||
|
||||
### Фоновые задания (jobs)
|
||||
|
||||
@@ -57,9 +58,11 @@
|
||||
- В памяти держится не более **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/jobs** (список) поле **`progress_log`** всегда **пустой массив** — меньше трафика; полный хвост — в **GET /api/v1/jobs/{job_id}** (лимит строк: `KIND_K8S_JOB_API_LOG_MAX_LINES`, по умолчанию **5000**).
|
||||
- Буфер строк в памяти на задание: `KIND_K8S_JOB_LOG_MAX_LINES` (по умолчанию **2500**); при переполнении старые строки вытесняются.
|
||||
- Для **`docker pull`** по умолчанию: **`--progress=plain`** и вывод **без PTY** (`KIND_K8S_DOCKER_PULL_PLAIN=1`) — в журнале идут строки с процентами/слоями. Для **podman** и **kind** — псевдо-TTY по `KIND_K8S_STREAM_PTY`, из строк убираются ANSI-коды.
|
||||
- Тип задания **`kind`**: `create_cluster`, `start_cluster` (подъём по сохранённому конфигу), `start_containers` (запуск уже созданных узлов), `stop_containers` (остановка узлов).
|
||||
- Статус **`cancelled`** — запрошена отмена (`POST .../cancel`); дочерний процесс текущей команды получает принудительное завершение.
|
||||
|
||||
---
|
||||
|
||||
@@ -236,7 +239,7 @@ Accept: text/markdown
|
||||
|
||||
## GET /api/v1/jobs
|
||||
|
||||
Список последних фоновых заданий (создание кластера), от новых к старым.
|
||||
Список последних фоновых заданий, от новых к старым. Поле **`progress_log`** в каждом элементе **пустое** — используйте **GET /api/v1/jobs/{job_id}** для журнала.
|
||||
|
||||
**Query:** `limit` (1–200, по умолчанию **30**).
|
||||
|
||||
@@ -253,16 +256,31 @@ Accept: text/markdown
|
||||
"message": "Кластер создан",
|
||||
"result": { "cluster_name": "dev", "kubernetes_version_tag": "v1.29.4" },
|
||||
"progress_stage": null,
|
||||
"progress_percent": null
|
||||
"progress_percent": null,
|
||||
"progress_log": []
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## DELETE /api/v1/jobs
|
||||
|
||||
Удаляет из памяти записи заданий со статусом **success**, **failed** или **cancelled**. Задания **queued** и **running** не удаляются.
|
||||
|
||||
**Пример ответа 200:**
|
||||
|
||||
```json
|
||||
{
|
||||
"removed": 4
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## POST /api/v1/jobs/{job_id}/cancel
|
||||
|
||||
Запрос отмены создания кластера. Пока задание в статусе `queued` или `running`, между этапами выполняется проверка флага; после уже запущенного `kind create cluster` нужно дождаться окончания этого шага.
|
||||
Прервать фоновое задание (`create_cluster`, `start_cluster`, `start_containers`, `stop_containers`). Для длительной команды завершается связанный дочерний процесс; между шагами запуска/остановки отдельных узлов также проверяется флаг отмены.
|
||||
|
||||
**Пример ответа 200:**
|
||||
|
||||
@@ -270,7 +288,7 @@ Accept: text/markdown
|
||||
{
|
||||
"job_id": "a1b2…",
|
||||
"cancel_requested": true,
|
||||
"message": "Отмена обрабатывается между этапами; во время kind create дождитесь окончания шага"
|
||||
"message": "Запрошено прерывание; текущая команда будет остановлена, задание перейдёт в отменено"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -370,19 +388,19 @@ Accept: text/markdown
|
||||
|
||||
## POST /api/v1/clusters/{name}/start
|
||||
|
||||
Запуск кластера двумя сценариями:
|
||||
Запуск кластера двумя сценариями (оба с **202** и `job_id`, журнал в `GET /jobs/{job_id}`):
|
||||
|
||||
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`.
|
||||
1. Кластер **зарегистрирован** в kind — задание **`start_containers`** (поочерёдный запуск узлов, `mode`: **`containers`**).
|
||||
2. В kind кластера **нет**, но есть сохранённый **`clusters/<имя>/kind-config.yaml`** — задание **`start_cluster`** (`mode`: **`kind_config`**), логика как при создании (в т.ч. скачивание образа при необходимости).
|
||||
|
||||
**Пример ответа 200 (контейнеры запущены):**
|
||||
**Пример ответа 202 (запуск узлов):**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "dev",
|
||||
"job_id": "cafebabe...",
|
||||
"status": "queued",
|
||||
"mode": "containers",
|
||||
"containers_started_ok": true,
|
||||
"summary": "dev-control-plane: OK; dev-worker: OK; dev-worker2: OK"
|
||||
"message": "Запуск узлов; опросите GET /api/v1/jobs/{job_id}"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -390,9 +408,10 @@ Accept: text/markdown
|
||||
|
||||
```json
|
||||
{
|
||||
"job_id": "cafebabe...",
|
||||
"job_id": "deadbeef...",
|
||||
"status": "queued",
|
||||
"message": "Подъём кластера по kind-config.yaml; опросите GET /api/v1/jobs/{job_id}"
|
||||
"mode": "kind_config",
|
||||
"message": "Подъём кластера по сохранённому конфигу; опросите GET /api/v1/jobs/{job_id}"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -402,15 +421,16 @@ Accept: text/markdown
|
||||
|
||||
## POST /api/v1/clusters/{name}/stop
|
||||
|
||||
Остановка **всех** контейнеров узлов кластера (`docker stop` / `podman stop` по префиксу имени). Запись кластера в kind **не удаляется**; позже можно снова вызвать **POST …/start** (режим `containers`).
|
||||
Остановка узлов кластера **в фоне** (задание **`stop_containers`**). Запись кластера в kind **не удаляется**; позже снова **POST …/start**.
|
||||
|
||||
**Пример ответа 200:**
|
||||
**Пример ответа 202:**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "dev",
|
||||
"containers_stopped_ok": true,
|
||||
"summary": "dev-control-plane: OK; dev-worker: OK"
|
||||
"job_id": "baba...",
|
||||
"status": "queued",
|
||||
"mode": "stop",
|
||||
"message": "Остановка узлов; опросите GET /api/v1/jobs/{job_id}"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -433,13 +453,13 @@ Accept: text/markdown
|
||||
"created_at_utc": "2026-04-04T12:00:00+00:00",
|
||||
"message": null,
|
||||
"result": null,
|
||||
"progress_stage": "kind create cluster (скачивание образов и подъём нод — может занять несколько минут)",
|
||||
"progress_percent": 28,
|
||||
"progress_stage": "Создание узлов кластера",
|
||||
"progress_percent": 45,
|
||||
"progress_log": [
|
||||
"[12%] Подготовка каталога и kind-config",
|
||||
"--- kind create cluster ---",
|
||||
"Creating cluster \"dev\" ...",
|
||||
" • Ensuring node image (kindest/node:v1.29.4) 🖼 ..."
|
||||
"Подготовка конфигурации",
|
||||
"Using default tag: latest",
|
||||
"Status: Downloaded newer image for kindest/node:v1.29.4",
|
||||
"Creating cluster \"dev\" ..."
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -454,7 +474,7 @@ Accept: text/markdown
|
||||
"cluster_name": "dev",
|
||||
"created_at_utc": "2026-04-04T12:00:00+00:00",
|
||||
"message": "Кластер создан",
|
||||
"progress_log": ["[95%] Финализация", "kubectl wait nodes: ..."],
|
||||
"progress_log": ["Завершение", "Узлы готовы: condition met"],
|
||||
"result": {
|
||||
"cluster_name": "dev",
|
||||
"kubernetes_version_tag": "v1.29.4",
|
||||
|
||||
Reference in New Issue
Block a user