diff --git a/README.md b/README.md index 94301fe..c5cf466 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # Kind Clusters Dashboard — локальные кластеры Kubernetes (kind) -**Репозиторий Git:** `ssh://git@git.antropoff.ru:222/DevOpsTools/KindClustersDashboard.git` — клон: `git clone ssh://git@git.antropoff.ru:222/DevOpsTools/KindClustersDashboard.git`. Уже клонировали раньше: `git remote set-url origin ssh://git@git.antropoff.ru:222/DevOpsTools/KindClustersDashboard.git`. - Образ **kind-k8s-tools:local** и **Makefile** поднимают **веб-интерфейс** (FastAPI) на порту **8080** на хосте по умолчанию (или **`KIND_K8S_WEB_PORT`** в `.env`; внутри контейнера приложение слушает **6000**). Порт **6000 на хосте** не используем по умолчанию: Chrome и другие браузеры на Chromium отдают **ERR_UNSAFE_PORT**. Через браузер создаёте и удаляете кластеры, смотрите статистику и вывод `kubectl`. **kubeconfig** сохраняется в `clusters/<имя>/`. На хосте достаточно **Docker** (или Podman) и **make**; **kind** и **kubectl** — внутри контейнера. ## Документация @@ -9,8 +7,17 @@ | Ресурс | Описание | |--------|----------| | **[app/docs/api_routes.md](app/docs/api_routes.md)** | Описание REST API `/api/v1/*` с примерами JSON (для фронтенда и интеграций) | +| **[app/docs/screenshots.md](app/docs/screenshots.md)** | Скриншоты веб-интерфейса (**светлая** и **тёмная** тема); файлы в **`app/docs/images/`** | | **`/docs`** (Swagger), **`/redoc`**, **`/api/v1/health`** | На панели открываются в **отдельном окне** браузера (`window.open`); прямой URL — тот же порт, что и UI (по умолчанию **8080**) | +### Скриншоты (превью) + +Полная галерея и подписи — в **[app/docs/screenshots.md](app/docs/screenshots.md)**. Ниже — главная панель в обеих темах. + +| Светлая тема | Тёмная тема | +| :------------: | :-----------: | +| ![](app/docs/images/dashboard-light.png) | ![](app/docs/images/dashboard-dark.png) | + В корне репозитория — файл **`env.example`**: перечислены **только имена** переменных (без значений), для ориентира при ручной настройке **`.env`**. Полноценно создать **`.env`** можно интерактивно скриптом **`scripts/setup_env_interactive.py`** (`make setup`; в начале — выбор **docker** или **podman**, путь **`CONTAINER_SOCKET`** подставляется автоматически). ## Зачем это нужно diff --git a/app/docs/api_routes.md b/app/docs/api_routes.md index c1c9fe1..880e421 100644 --- a/app/docs/api_routes.md +++ b/app/docs/api_routes.md @@ -28,6 +28,7 @@ | `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). Полноэкранный спиннер при первой загрузке и при **каждом** переходе по внутренним ссылкам / **popstate**. Каждая секция по **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` на `` (по умолчанию `/api/v1`). | +| `GET /static/docs-images/…` | PNG из **`app/docs/images/`** для страницы **`/documentation`** (в Markdown пути вида `images/*.png` в **`documentation.js`** переписываются на этот префикс; см. **`app/docs/screenshots.md`**). | Шаблоны: `app/templates/base.html` (шапка, навигация; **меню «гамбургер»** при узком viewport — `nav-mobile.js`), `app/templates/dashboard.html` (панель), `app/templates/clusters.html` (список кластеров и донаты узлов), `app/templates/cluster_create.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 и `app/docs/*.md`). diff --git a/app/docs/images/addons-dark.png b/app/docs/images/addons-dark.png new file mode 100644 index 0000000..41152d1 Binary files /dev/null and b/app/docs/images/addons-dark.png differ diff --git a/app/docs/images/addons-light.png b/app/docs/images/addons-light.png new file mode 100644 index 0000000..b09c809 Binary files /dev/null and b/app/docs/images/addons-light.png differ diff --git a/app/docs/images/clusters-dark.png b/app/docs/images/clusters-dark.png new file mode 100644 index 0000000..cb75823 Binary files /dev/null and b/app/docs/images/clusters-dark.png differ diff --git a/app/docs/images/create-cluster-light.png b/app/docs/images/create-cluster-light.png new file mode 100644 index 0000000..29ba933 Binary files /dev/null and b/app/docs/images/create-cluster-light.png differ diff --git a/app/docs/images/dashboard-dark.png b/app/docs/images/dashboard-dark.png new file mode 100644 index 0000000..3ef39bc Binary files /dev/null and b/app/docs/images/dashboard-dark.png differ diff --git a/app/docs/images/dashboard-light.png b/app/docs/images/dashboard-light.png new file mode 100644 index 0000000..f95d4a6 Binary files /dev/null and b/app/docs/images/dashboard-light.png differ diff --git a/app/docs/images/docs-dark.png b/app/docs/images/docs-dark.png new file mode 100644 index 0000000..ca9efc7 Binary files /dev/null and b/app/docs/images/docs-dark.png differ diff --git a/app/docs/images/docs-light.png b/app/docs/images/docs-light.png new file mode 100644 index 0000000..96fa7f2 Binary files /dev/null and b/app/docs/images/docs-light.png differ diff --git a/app/docs/images/logs-dark.png b/app/docs/images/logs-dark.png new file mode 100644 index 0000000..172a889 Binary files /dev/null and b/app/docs/images/logs-dark.png differ diff --git a/app/docs/images/logs-light.png b/app/docs/images/logs-light.png new file mode 100644 index 0000000..6f5c1b9 Binary files /dev/null and b/app/docs/images/logs-light.png differ diff --git a/app/docs/screenshots.md b/app/docs/screenshots.md new file mode 100644 index 0000000..fb46387 --- /dev/null +++ b/app/docs/screenshots.md @@ -0,0 +1,63 @@ +# Скриншоты интерфейса + +Снимки экрана в **светлой** и **тёмной** теме (переключатель темы в шапке). Исходные файлы лежат в каталоге **`app/docs/images/`** (в репозитории рядом с этим файлом). + +Открыть в веб-приложении: страница **«Документация»** → путь **`app/docs/screenshots.md`**. + +--- + +## Главная панель + +**Светлая тема** + +![Главная панель, светлая тема](images/dashboard-light.png) + +**Тёмная тема** + +![Главная панель, тёмная тема](images/dashboard-dark.png) + +## Страница «Кластеры» + +Снимок в тёмной теме (отдельный скрин светлой темы не добавлялся). + +![Кластеры, тёмная тема](images/clusters-dark.png) + +## Создание кластера + +Снимок в светлой теме. + +![Создание кластера, светлая тема](images/create-cluster-light.png) + +## Аддоны (Helm) + +**Светлая тема** + +![Аддоны, светлая тема](images/addons-light.png) + +**Тёмная тема** + +![Аддоны, тёмная тема](images/addons-dark.png) + +## Встроенная документация (Markdown) + +**Светлая тема** + +![Документация в UI, светлая тема](images/docs-light.png) + +**Тёмная тема** + +![Документация в UI, тёмная тема](images/docs-dark.png) + +## Журнал + +**Светлая тема** + +![Журнал, светлая тема](images/logs-light.png) + +**Тёмная тема** + +![Журнал, тёмная тема](images/logs-dark.png) + +--- + +*Автор: Сергей Антропов — https://devops.org.ru* diff --git a/app/main.py b/app/main.py index 6546803..3b63513 100644 --- a/app/main.py +++ b/app/main.py @@ -57,6 +57,15 @@ _static_dir = _BASE / "static" if _static_dir.is_dir(): app.mount("/static", StaticFiles(directory=str(_static_dir)), name="static") +# PNG-скриншоты для app/docs/screenshots.md и др.: относительные пути images/* в Markdown → /static/docs-images/* +_docs_images_dir = _BASE / "docs" / "images" +if _docs_images_dir.is_dir(): + app.mount( + "/static/docs-images", + StaticFiles(directory=str(_docs_images_dir)), + name="docs_images", + ) + templates = Jinja2Templates(directory=str(_templates_dir)) diff --git a/app/static/js/documentation.js b/app/static/js/documentation.js index f40a435..565f1f6 100644 --- a/app/static/js/documentation.js +++ b/app/static/js/documentation.js @@ -139,6 +139,33 @@ }); } + /** + * Картинки из app/docs/*.md с src вида images/foo.png → /static/docs-images/foo.png + * (каталог app/docs/images смонтирован в FastAPI как StaticFiles). + */ + function rewriteMdImages(container, currentDocPath) { + if (!currentDocPath || currentDocPath.indexOf("app/docs/") !== 0) return; + var baseDir = currentDocPath.replace(/[^/]+\.md$/i, ""); + if (!baseDir) return; + container.querySelectorAll("img[src]").forEach(function (img) { + var src = (img.getAttribute("src") || "").trim(); + if (!src || /^https?:\/\//i.test(src) || src.startsWith("data:")) return; + if (src.indexOf("..") >= 0) return; + if (src.startsWith("/static/docs-images/")) return; + try { + var u = new URL(src, "http://doc.local/" + baseDir); + var pathname = decodeURIComponent(u.pathname.replace(/^\/+/, "")); + if (pathname.indexOf("..") >= 0) return; + if (!pathname.startsWith("app/docs/images/")) return; + var rest = pathname.replace(/^app\/docs\/images\/?/, ""); + if (!rest) return; + img.setAttribute("src", "/static/docs-images/" + rest); + } catch (e) { + /* ignore */ + } + }); + } + /** Текст первого H1 в разобранном HTML (для заголовка вкладки). */ function extractFirstH1Text(root) { var h = root.querySelector("h1"); @@ -261,6 +288,7 @@ var temp = document.createElement("div"); temp.innerHTML = DOMPurify.sanitize(rawHtml); rewriteMdLinks(temp, docPath); + rewriteMdImages(temp, docPath); var h1ForTitle = extractFirstH1Text(temp); applyDocumentationTabTitle(h1ForTitle); diff --git a/app/static/style.css b/app/static/style.css index e5ba018..7082ca9 100644 --- a/app/static/style.css +++ b/app/static/style.css @@ -2935,6 +2935,16 @@ button.btn-danger:hover { .readme-section-card.markdown-body li { margin: 0.2rem 0; } + +/* Скриншоты в app/docs/screenshots.md: не вылезают за карточку */ +.readme-section-card.markdown-body img { + max-width: 100%; + height: auto; + border-radius: 8px; + margin: 0.5rem 0; + border: 1px solid var(--color-border-subtle, rgba(128, 128, 128, 0.25)); +} + .readme-section-card.markdown-body a { color: var(--accent); text-decoration: underline;