docs: скриншоты UI (светлая/тёмная тема) в app/docs и README

- app/docs/screenshots.md и каталог app/docs/images/*.png
- раздача /static/docs-images/* из FastAPI; documentation.js переписывает src картинок
- стили .markdown-body img; строка в api_routes.md; превью в README
This commit is contained in:
Sergey Antropoff
2026-04-05 01:18:22 +03:00
parent ec5eedb838
commit c49555b3b9
16 changed files with 120 additions and 2 deletions
+9 -2
View File
@@ -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`** подставляется автоматически).
## Зачем это нужно
+1
View File
@@ -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` на `<body>` (по умолчанию `/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`).
Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

+63
View File
@@ -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*
+9
View File
@@ -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))
+28
View File
@@ -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);
+10
View File
@@ -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;