Журнал: пагинация 30 записей на страницу, API offset/total_pages
- job_journal: collect_recent_journal_entries_page_sync(limit, offset) - GET /api/v1/journal/recent: limit по умолчанию 30, offset, total, page, total_pages - journal.html/js: навигация Первая/Назад/номера/Вперёд/Последняя, стили - app/docs/api_routes.md: описание query и пример ответа - Прочие изменения UI/API (аддоны, helm, job_journal в кластерах) в том же коммите
This commit is contained in:
+106
-1
@@ -22,11 +22,13 @@
|
||||
| `GET /` | HTML-панель: единая карточка «панель + среда», статистика, **ссылка с имени кластера на** `GET /cluster/<имя>` (сводка ресурсов, kubectl, действия), **старт/стоп** кластера с тем же журналом (фоновые **POST …/start** и **…/stop**), модалка узлов/подов; шапка — пилюли, Swagger / ReDoc / Health в отдельных окнах. |
|
||||
| `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 /cluster-addons` | HTML **Аддоны**: выбор кластера, **`GET /api/v1/helm/chart-versions`** для выпадающих списков версий чартов (или «Последняя»), установка/удаление Helm-релизов. |
|
||||
| `GET /journal` | HTML **Журнал**: **`GET /api/v1/journal/recent`** с пагинацией (**30** записей на страницу), навигация по страницам внизу таблицы. |
|
||||
| `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/cluster_edit.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/cluster_addons.html` (Helm-аддоны), `app/templates/journal.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**.
|
||||
|
||||
@@ -54,6 +56,18 @@
|
||||
| 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/clusters/{name}/addons/status` | Статус Helm-релизов (ingress-nginx, kube-prometheus-stack, metrics-server, Istio+Kiali); нужен **helm** в образе |
|
||||
| GET | `/api/v1/helm/chart-versions` | Версии чартов для UI (после `helm repo update`); кэш **`KIND_K8S_HELM_VERSIONS_CACHE_SEC`**; поля: `ingress_nginx`, `kube_prometheus_stack`, `metrics_server`, `istio`, `kiali_server` |
|
||||
| POST | `/api/v1/clusters/{name}/addons/ingress-nginx` | Установить **ingress-nginx** (NodePort 30080); тело: `{ "chart_version": "опционально" }` (пусто = последняя) |
|
||||
| DELETE | `/api/v1/clusters/{name}/addons/ingress-nginx` | Удалить ingress-nginx |
|
||||
| POST | `/api/v1/clusters/{name}/addons/kube-prometheus-stack` | Установить **kube-prometheus-stack**; тело: `grafana_admin_user`, `grafana_admin_password` (≥8), опционально **`chart_version`** |
|
||||
| DELETE | `/api/v1/clusters/{name}/addons/kube-prometheus-stack` | Удалить стек |
|
||||
| POST | `/api/v1/clusters/{name}/addons/metrics-server` | Установить **metrics-server** (kind: `--kubelet-insecure-tls`); тело опционально: `{ "chart_version": "…" }` или `{}` |
|
||||
| DELETE | `/api/v1/clusters/{name}/addons/metrics-server` | Удалить metrics-server |
|
||||
| POST | `/api/v1/clusters/{name}/addons/istio-kiali` | Установить **istio-base**, **istiod**, секрет и **kiali-server**; тело: `kiali_username`, `kiali_password` (≥8), опционально **`istio_chart_version`**, **`kiali_chart_version`** |
|
||||
| DELETE | `/api/v1/clusters/{name}/addons/istio-kiali` | Удалить kiali-server, istiod, istio-base |
|
||||
| GET | `/api/v1/journal/recent` | Пагинация журнала по всем кластерам: query **`limit`** (по умолчанию **30**, макс. 100), **`offset`** (по умолчанию **0**); в ответе **`total`**, **`total_pages`**, **`page`** |
|
||||
| GET | `/api/v1/clusters/{name}/journal` | Полный файл журнала кластера (массив `entries` или пусто, если файла нет) |
|
||||
| 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` — число записей) |
|
||||
@@ -64,6 +78,7 @@
|
||||
- Хранятся **только в памяти** процесса uvicorn; после перезапуска контейнера история обнуляется.
|
||||
- В памяти держится не более **200** записей; при превышении старые задания вытесняются (`app/core/job_store.py`).
|
||||
- Снимок заданий сохраняется в JSON в каталоге **`clusters/`** (файл **`kind_k8s_jobs.json`** на томе с хоста) — после перезапуска контейнера список восстанавливается. Записи в статусе **queued**/**running** при старте помечаются как **failed** (процесс уже не выполняется). Путь переопределяется переменной **`KIND_K8S_JOBS_JSON`**.
|
||||
- При **завершении** задания (успех / ошибка / отмена), если указано **`cluster_name`** и существует каталог **`clusters/<имя>/`**, в **`clusters/<имя>/journal/jobs_history.json`** дозаписывается запись с хвостом лога и **`result`** (без секретов). Лимиты: **`KIND_K8S_CLUSTER_JOURNAL_MAX_ENTRIES`**, **`KIND_K8S_CLUSTER_JOURNAL_MAX_LOG_LINES`**. Чтение: **`GET /api/v1/journal/recent`**, **`GET /api/v1/clusters/{name}/journal`**, страница **`GET /journal`**.
|
||||
- Создание кластера: `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**).
|
||||
@@ -271,6 +286,96 @@ Accept: text/markdown
|
||||
|
||||
---
|
||||
|
||||
## GET /api/v1/helm/chart-versions
|
||||
|
||||
Список версий чартов для выпадающих списков на **`/cluster-addons`**. Выполняется **`helm repo update`** и для каждого чарта — **`helm search repo <chart> --versions -o json`**. Результаты кэшируются на **`KIND_K8S_HELM_VERSIONS_CACHE_SEC`** (по умолчанию **600** с) отдельно для каждого рефа чарта. Длина списка ограничена **`KIND_K8S_HELM_VERSIONS_MAX`** (по умолчанию **80**).
|
||||
|
||||
**Пример ответа 200:**
|
||||
|
||||
```json
|
||||
{
|
||||
"ingress_nginx": ["4.14.0", "4.13.3", "4.12.1"],
|
||||
"kube_prometheus_stack": ["69.0.0", "68.4.3"],
|
||||
"metrics_server": ["3.12.2", "3.12.1"],
|
||||
"istio": ["1.24.3", "1.24.2"],
|
||||
"kiali_server": ["2.1.0", "2.0.0"]
|
||||
}
|
||||
```
|
||||
|
||||
При недоступном **helm** — **503** с текстом ошибки.
|
||||
|
||||
---
|
||||
|
||||
## GET /api/v1/journal/recent
|
||||
|
||||
Объединение записей из всех **`clusters/*/journal/jobs_history.json`**, сортировка по **`finished_at_utc`** (новые первыми). В каждой записи добавляется **`source_cluster`** — имя каталога кластера.
|
||||
|
||||
**Query:**
|
||||
|
||||
| Параметр | Описание |
|
||||
|----------|----------|
|
||||
| `limit` | Записей на страницу, **1–100**, по умолчанию **30** (как в веб-интерфейсе **`/journal`**) |
|
||||
| `offset` | Смещение от начала списка, **≥ 0**, по умолчанию **0** (первая страница: `offset=0`, вторая при `limit=30`: `offset=30`) |
|
||||
|
||||
**Поля ответа:** **`limit`**, **`offset`**, **`total`** (всего записей), **`page`** (текущая страница с 1), **`total_pages`**, **`entries`** (фрагмент текущей страницы).
|
||||
|
||||
**Пример ответа 200 (первая страница, до 30 записей):**
|
||||
|
||||
```json
|
||||
{
|
||||
"limit": 30,
|
||||
"offset": 0,
|
||||
"total": 75,
|
||||
"page": 1,
|
||||
"total_pages": 3,
|
||||
"entries": [
|
||||
{
|
||||
"job_id": "a1b2c3",
|
||||
"kind": "create_cluster",
|
||||
"cluster_name": "dev",
|
||||
"source_cluster": "dev",
|
||||
"status": "success",
|
||||
"message": null,
|
||||
"created_at_utc": "2026-04-04T11:58:00+00:00",
|
||||
"finished_at_utc": "2026-04-04T12:05:00+00:00",
|
||||
"log_lines": ["[1] …", "[2] …"],
|
||||
"result": { "cluster_name": "dev" }
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GET /api/v1/clusters/{name}/journal
|
||||
|
||||
Содержимое файла **`clusters/<имя>/journal/jobs_history.json`**. Если файла нет — **`entries`: []**.
|
||||
|
||||
**Пример ответа 200:**
|
||||
|
||||
```json
|
||||
{
|
||||
"cluster_name": "dev",
|
||||
"file_version": 1,
|
||||
"entries": [
|
||||
{
|
||||
"version": 1,
|
||||
"job_id": "a1b2c3",
|
||||
"kind": "stop_containers",
|
||||
"cluster_name": "dev",
|
||||
"status": "success",
|
||||
"message": null,
|
||||
"created_at_utc": "2026-04-04T10:00:00+00:00",
|
||||
"finished_at_utc": "2026-04-04T10:00:15+00:00",
|
||||
"log_lines": [],
|
||||
"result": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GET /api/v1/jobs
|
||||
|
||||
Список последних фоновых заданий, от новых к старым. Поле **`progress_log`** в каждом элементе **пустое** — используйте **GET /api/v1/jobs/{job_id}** для журнала.
|
||||
|
||||
Reference in New Issue
Block a user