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
@@ -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)**. Ниже — главная панель в обеих темах.
|
||||
|
||||
| Светлая тема | Тёмная тема |
|
||||
| :------------: | :-----------: |
|
||||
|  |  |
|
||||
|
||||
В корне репозитория — файл **`env.example`**: перечислены **только имена** переменных (без значений), для ориентира при ручной настройке **`.env`**. Полноценно создать **`.env`** можно интерактивно скриптом **`scripts/setup_env_interactive.py`** (`make setup`; в начале — выбор **docker** или **podman**, путь **`CONTAINER_SOCKET`** подставляется автоматически).
|
||||
|
||||
## Зачем это нужно
|
||||
|
||||
@@ -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`).
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 257 KiB |
|
After Width: | Height: | Size: 259 KiB |
|
After Width: | Height: | Size: 226 KiB |
|
After Width: | Height: | Size: 326 KiB |
|
After Width: | Height: | Size: 287 KiB |
|
After Width: | Height: | Size: 226 KiB |
|
After Width: | Height: | Size: 522 KiB |
|
After Width: | Height: | Size: 485 KiB |
|
After Width: | Height: | Size: 302 KiB |
|
After Width: | Height: | Size: 268 KiB |
@@ -0,0 +1,63 @@
|
||||
# Скриншоты интерфейса
|
||||
|
||||
Снимки экрана в **светлой** и **тёмной** теме (переключатель темы в шапке). Исходные файлы лежат в каталоге **`app/docs/images/`** (в репозитории рядом с этим файлом).
|
||||
|
||||
Открыть в веб-приложении: страница **«Документация»** → путь **`app/docs/screenshots.md`**.
|
||||
|
||||
---
|
||||
|
||||
## Главная панель
|
||||
|
||||
**Светлая тема**
|
||||
|
||||

|
||||
|
||||
**Тёмная тема**
|
||||
|
||||

|
||||
|
||||
## Страница «Кластеры»
|
||||
|
||||
Снимок в тёмной теме (отдельный скрин светлой темы не добавлялся).
|
||||
|
||||

|
||||
|
||||
## Создание кластера
|
||||
|
||||
Снимок в светлой теме.
|
||||
|
||||

|
||||
|
||||
## Аддоны (Helm)
|
||||
|
||||
**Светлая тема**
|
||||
|
||||

|
||||
|
||||
**Тёмная тема**
|
||||
|
||||

|
||||
|
||||
## Встроенная документация (Markdown)
|
||||
|
||||
**Светлая тема**
|
||||
|
||||

|
||||
|
||||
**Тёмная тема**
|
||||
|
||||

|
||||
|
||||
## Журнал
|
||||
|
||||
**Светлая тема**
|
||||
|
||||

|
||||
|
||||
**Тёмная тема**
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
*Автор: Сергей Антропов — https://devops.org.ru*
|
||||
@@ -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))
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||