Панель: редактирование конфига кластера, reapply при старте, UI и таблица
- PUT/GET конфигурации кластера, страница редактирования и модалка после сохранения - После смены kind-config: флаг в meta и start_cluster_reapply (kind delete + create) - Старт/стоп: полноэкранный спиннер до завершения job; модалки и документация API - Таблица кластеров: колонка Имя 40% при table-layout fixed; чекбоксы без width 100% - Карточки ресурсов узлов на странице кластера: до 3 в ряд; прочие правки стилей и dashboard.js
This commit is contained in:
+128
-12
@@ -20,12 +20,13 @@
|
||||
| Маршрут | Описание |
|
||||
|---------|----------|
|
||||
| `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 /cluster/{name}` | HTML **страница кластера**: донаты «Ресурсы узлов (сводка)», карточки узлов, **таблицы Kubernetes** (данные API кластера в JSON), кнопка **Рестарт** у подов (**`POST …/pods/restart`**), те же кнопки действий, что в таблице на главной; данные — **`GET /api/v1/clusters/{name}/overview`** (автообновление с интервалом панели). |
|
||||
| `GET /cluster/{name}/edit` | HTML **редактирование** сохранённого `kind-config.yaml` и полей `meta.json` (простой режим: тег/workers; расширенный: полный YAML kind Cluster). Сохранение — **`PUT /api/v1/clusters/{name}/config`**. |
|
||||
| `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/cluster_detail.html` (страница кластера), `app/templates/documentation.html` (README).
|
||||
Шаблоны: `app/templates/base.html` (шапка, навигация), `app/templates/dashboard.html` (контент панели), `app/templates/cluster_detail.html` (страница кластера), `app/templates/cluster_edit.html` (редактирование конфигурации), `app/templates/documentation.html` (README).
|
||||
|
||||
**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**.
|
||||
|
||||
@@ -42,11 +43,13 @@
|
||||
| GET | `/api/v1/stats` | Сводка для дашборда |
|
||||
| GET | `/api/v1/clusters` | Список кластеров (поле `has_provision_log` — есть ли `clusters/<имя>/provision_log.json`) |
|
||||
| POST | `/api/v1/clusters` | Создание в фоне (**202** + `job_id`) |
|
||||
| POST | `/api/v1/clusters/{name}/start` | Запуск в фоне (**202** + `job_id`, поле `mode`: `containers` или `kind_config`); журнал — `GET /jobs/{job_id}` |
|
||||
| POST | `/api/v1/clusters/{name}/start` | Запуск в фоне (**202** + `job_id`, поле `mode`: `containers`, `kind_config` или `kind_config_reapply`); журнал — `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}/config` | Текущие `meta.json` и текст `kind-config.yaml`; подсказка `kind_note` про пересоздание |
|
||||
| PUT | `/api/v1/clusters/{name}/config` | Сохранить YAML и/или meta (простой режим или полный YAML); ответ включает обновлённый `summary` |
|
||||
| GET | `/api/v1/clusters/{name}/kubeconfig` | Скачать файл kubeconfig |
|
||||
| GET | `/api/v1/clusters/{name}/provision-log` | Полный журнал последнего **create_cluster** / **start_cluster** (JSON с диска) |
|
||||
| GET | `/api/v1/clusters/{name}/provision-log` | Полный журнал последнего **create_cluster** / **start_cluster** / **start_cluster_reapply** (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`) для мягкого рестарта |
|
||||
@@ -64,9 +67,9 @@
|
||||
- Создание кластера: `POST /api/v1/clusters` → опрос `GET /api/v1/jobs/{job_id}` (как в веб-UI).
|
||||
- В ответе задания поля **`progress_stage`** (текст этапа) и **`progress_percent`** (0–100) обновляются во время создания.
|
||||
- В **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**); при переполнении старые строки вытесняются. Для **create_cluster** и **start_cluster** полный журнал без обрезки дополнительно сохраняется в **`clusters/<имя>/provision_log.json`** (перезапись при каждом завершении такого задания); чтение — **GET /api/v1/clusters/{name}/provision-log**.
|
||||
- Буфер строк в памяти на задание: `KIND_K8S_JOB_LOG_MAX_LINES` (по умолчанию **2500**); при переполнении старые строки вытесняются. Для **create_cluster**, **start_cluster** и **start_cluster_reapply** полный журнал без обрезки дополнительно сохраняется в **`clusters/<имя>/provision_log.json`** (перезапись при каждом завершении такого задания); чтение — **GET /api/v1/clusters/{name}/provision-log**.
|
||||
- Для **`docker pull`**: если в справке **`docker pull --help`** объявлен флаг **`--progress`**, при **`KIND_K8S_DOCKER_PULL_PLAIN=1`** вызывается **`--progress=plain`** без PTY; на старых CLI флаг не передаётся (нет строки «unknown flag» в журнале). Для **podman** и **kind** — псевдо-TTY по `KIND_K8S_STREAM_PTY`, из строк убираются ANSI-коды.
|
||||
- Тип задания **`kind`**: `create_cluster`, `start_cluster` (подъём по сохранённому конфигу), `start_containers` (запуск уже созданных узлов), `stop_containers` (остановка узлов).
|
||||
- Тип задания **`kind`**: `create_cluster`, `start_cluster` (подъём по сохранённому конфигу), `start_cluster_reapply` (после правки `kind-config.yaml`: `kind delete` без удаления каталога + `kind create`), `start_containers` (запуск уже созданных узлов), `stop_containers` (остановка узлов).
|
||||
- Статус **`cancelled`** — запрошена отмена (`POST .../cancel`); дочерний процесс текущей команды получает принудительное завершение.
|
||||
|
||||
---
|
||||
@@ -311,7 +314,7 @@ Accept: text/markdown
|
||||
|
||||
## POST /api/v1/jobs/{job_id}/cancel
|
||||
|
||||
Прервать фоновое задание (`create_cluster`, `start_cluster`, `start_containers`, `stop_containers`). Для длительной команды завершается связанный дочерний процесс; между шагами запуска/остановки отдельных узлов также проверяется флаг отмены.
|
||||
Прервать фоновое задание (`create_cluster`, `start_cluster`, `start_cluster_reapply`, `start_containers`, `stop_containers`). Для длительной команды завершается связанный дочерний процесс; между шагами запуска/остановки отдельных узлов также проверяется флаг отмены.
|
||||
|
||||
**Пример ответа 200:**
|
||||
|
||||
@@ -328,6 +331,102 @@ Accept: text/markdown
|
||||
|
||||
---
|
||||
|
||||
## GET /api/v1/clusters/{name}/config
|
||||
|
||||
Чтение **`clusters/<имя>/meta.json`** и текста **`kind-config.yaml`**. Поле **`kind_note`** напоминает: kind **не** меняет топологию и образ уже созданного кластера — новый YAML на диске применится при следующем **`kind create`** (после `kind delete` и «Старт» / пересоздания).
|
||||
|
||||
**Ошибка 404:** каталога кластера нет.
|
||||
**Ошибка 400:** некорректное имя.
|
||||
|
||||
**Пример ответа 200 (JSON):**
|
||||
|
||||
```json
|
||||
{
|
||||
"cluster_name": "dev",
|
||||
"meta": {
|
||||
"cluster_name": "dev",
|
||||
"kubernetes_version_tag": "v1.29.4",
|
||||
"worker_nodes": 2,
|
||||
"node_image": "kindest/node:v1.29.4",
|
||||
"description": "Тестовый стенд"
|
||||
},
|
||||
"kind_config_yaml": "kind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n image: kindest/node:v1.29.4\n- role: worker\n image: kindest/node:v1.29.4\n",
|
||||
"has_kind_config": true,
|
||||
"registered_in_kind": true,
|
||||
"kind_note": "Файлы сохранены на диске. Кластер уже зарегистрирован в kind: действующие узлы и образ не меняются до удаления кластера из kind и повторного создания по обновлённому kind-config.yaml (или «Старт», если запись в kind удалена, а каталог данных остался)."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PUT /api/v1/clusters/{name}/config
|
||||
|
||||
Сохранение на диск. Нужно **хотя бы одно** поле тела:
|
||||
|
||||
| Поле | Назначение |
|
||||
|------|------------|
|
||||
| `kubernetes_version` | Тег `kindest/node` для **пересборки** простого YAML (control-plane + N worker) |
|
||||
| `workers` | Число worker-нод 0–20 (вместе с версией из запроса или из meta) |
|
||||
| `kind_config_yaml` | **Полная замена** `kind-config.yaml` (проверка: `kind: Cluster`, `apiVersion` с `kind.x-k8s.io`, `nodes`, есть `control-plane`) |
|
||||
| `description` | Строка в meta (пустая строка **сбрасывает** поле) |
|
||||
|
||||
Если передан непустой **`kind_config_yaml`**, поля **`kubernetes_version`** и **`workers`** для генерации YAML **не используются** (можно не отправлять).
|
||||
|
||||
**Пример тела (только описание):**
|
||||
|
||||
```json
|
||||
{
|
||||
"description": "Обновлённая заметка"
|
||||
}
|
||||
```
|
||||
|
||||
**Пример тела (простой режим):**
|
||||
|
||||
```json
|
||||
{
|
||||
"kubernetes_version": "1.30.0",
|
||||
"workers": 3,
|
||||
"description": "Три воркера"
|
||||
}
|
||||
```
|
||||
|
||||
**Пример тела (расширенный YAML):**
|
||||
|
||||
```json
|
||||
{
|
||||
"kind_config_yaml": "kind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n image: kindest/node:v1.29.4\n",
|
||||
"description": ""
|
||||
}
|
||||
```
|
||||
|
||||
**Пример ответа 200 (JSON):**
|
||||
|
||||
```json
|
||||
{
|
||||
"cluster_name": "dev",
|
||||
"updated_kind_config_yaml": true,
|
||||
"message": "Сохранено.",
|
||||
"registered_in_kind": true,
|
||||
"kind_note": "Файлы сохранены на диске. Кластер уже зарегистрирован в kind: действующие узлы и образ не меняются до удаления кластера из kind и повторного создания по обновлённому kind-config.yaml (или «Старт», если запись в kind удалена, а каталог данных остался).",
|
||||
"summary": {
|
||||
"name": "dev",
|
||||
"registered_in_kind": true,
|
||||
"kind_nodes_running": true,
|
||||
"has_local_kubeconfig": true,
|
||||
"has_provision_log": true,
|
||||
"meta": {
|
||||
"kubernetes_version_tag": "v1.30.0",
|
||||
"worker_nodes": 3
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Ошибка 404:** каталога кластера нет.
|
||||
**Ошибка 400:** некорректное имя, пустое тело, ошибка разбора или структуры YAML.
|
||||
|
||||
---
|
||||
|
||||
## GET /api/v1/clusters/{name}/kubeconfig
|
||||
|
||||
Скачать kubeconfig для **kubectl на машине пользователя** (ответ — тело файла, `Content-Disposition`: `kubeconfig-{name}.yaml`).
|
||||
@@ -342,7 +441,7 @@ Accept: text/markdown
|
||||
|
||||
## GET /api/v1/clusters/{name}/provision-log
|
||||
|
||||
Содержимое **`clusters/<имя>/provision_log.json`**: полный журнал строк последнего завершённого задания **create_cluster** или **start_cluster** (включая успех, ошибку и отмену), плюс метаданные (`job_id`, `kind`, `status`, `message`, `result`, `finished_at_utc`).
|
||||
Содержимое **`clusters/<имя>/provision_log.json`**: полный журнал строк последнего завершённого задания **create_cluster**, **start_cluster** или **start_cluster_reapply** (включая успех, ошибку и отмену), плюс метаданные (`job_id`, `kind`, `status`, `message`, `result`, `finished_at_utc`).
|
||||
|
||||
**Ошибка 404:** файла нет (кластер ещё не создавали/не стартовали с сохранением журнала, или каталог без записи).
|
||||
|
||||
@@ -392,6 +491,8 @@ Accept: text/markdown
|
||||
|
||||
Если kubeconfig нет: `error` — строка вида **`Нет сохранённого kubeconfig в clusters/<имя>/`**, поля вывода kubectl могут быть `null`.
|
||||
|
||||
Если кластер **есть в kind**, но **узлы остановлены** (`docker stop` / «Стоп» в UI): **`error`** — краткое сообщение без вызова kubectl (вместо сырого stderr про DNS `*-control-plane`).
|
||||
|
||||
**Ошибка 400:** некорректное имя кластера.
|
||||
|
||||
---
|
||||
@@ -401,6 +502,7 @@ Accept: text/markdown
|
||||
Единый ответ для **страницы кластера** (`GET /cluster/{name}`): флаги и `meta` как в списке кластеров, **метрики узлов** (docker/podman), **`aggregate_resources`** (как в `GET /stats` для донатов), данные Kubernetes в виде **`kubectl get … -o json`** (разбор `items` на фронтенде в таблицы).
|
||||
|
||||
- При отсутствии kubeconfig: **`kubeconfig_error`** — строка-пояснение; блоки **`k8s_*`** недоступны (см. ниже).
|
||||
- Если кластер **зарегистрирован в kind**, но **узлы не запущены** (`kind_nodes_running: false`): **`kubectl` не вызывается**; в **`kubeconfig_error`** и в **`message`** каждого блока **`k8s_*`** — одно и то же дружелюбное пояснение (без stderr про недоступный API).
|
||||
- Поля **`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`;
|
||||
@@ -563,10 +665,13 @@ Accept: text/markdown
|
||||
|
||||
## POST /api/v1/clusters/{name}/start
|
||||
|
||||
Запуск кластера двумя сценариями (оба с **202** и `job_id`, журнал в `GET /jobs/{job_id}`):
|
||||
Запуск кластера (все варианты — **202** и `job_id`, журнал в `GET /jobs/{job_id}`):
|
||||
|
||||
1. Кластер **зарегистрирован** в kind — задание **`start_containers`** (поочерёдный запуск узлов, `mode`: **`containers`**).
|
||||
2. В kind кластера **нет**, но есть сохранённый **`clusters/<имя>/kind-config.yaml`** — задание **`start_cluster`** (`mode`: **`kind_config`**), логика как при создании (в т.ч. скачивание образа при необходимости).
|
||||
1. Кластер **зарегистрирован** в kind и в **`meta.json`** **нет** флага **`apply_kind_config_on_next_start`** — задание **`start_containers`** (поочерёдный `docker start` узлов, `mode`: **`containers`**).
|
||||
2. Кластер **зарегистрирован** в kind и после **PUT …/config** с изменением **`kind-config.yaml`** в **`meta.json`** выставлен **`apply_kind_config_on_next_start`: true** — задание **`start_cluster_reapply`** (`mode`: **`kind_config_reapply`**): `kind delete cluster` **без** удаления `clusters/<имя>/`, затем **`kind create`** по сохранённому YAML (как при создании; флаг в новом `meta.json` не сохраняется).
|
||||
3. В kind кластера **нет**, но есть **`clusters/<имя>/kind-config.yaml`** — задание **`start_cluster`** (`mode`: **`kind_config`**).
|
||||
|
||||
Остановка (**POST …/stop**) флаг пересоздания **не сбрасывает**; достаточно после правки конфига нажать **«Старт»** (после стопа или сразу — выполнится сценарий 2, если кластер всё ещё в kind).
|
||||
|
||||
**Пример ответа 202 (запуск узлов):**
|
||||
|
||||
@@ -590,7 +695,18 @@ Accept: text/markdown
|
||||
}
|
||||
```
|
||||
|
||||
**Ошибка 400:** некорректное имя или нет ни кластера в kind, ни `kind-config.yaml` в `clusters/<имя>/`.
|
||||
**Пример ответа 202 (пересоздание после правки YAML):**
|
||||
|
||||
```json
|
||||
{
|
||||
"job_id": "c0ffee...",
|
||||
"status": "queued",
|
||||
"mode": "kind_config_reapply",
|
||||
"message": "Удаление записи в kind и создание кластера по обновлённому kind-config.yaml; GET /api/v1/jobs/{job_id}"
|
||||
}
|
||||
```
|
||||
|
||||
**Ошибка 400:** некорректное имя; или нет ни кластера в kind, ни `kind-config.yaml`; или для **`kind_config_reapply`** отсутствует `kind-config.yaml`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user