UI/документация: крошки, метрики узлов в stats, правки навигации и подвала

- Документация: хлебные крошки; секции H2 в одной карточке; заголовок вкладки от H1
- Навигация: активна только текущая пилюля (Панель без постоянного home-стиля)
- GET /api/v1/stats: cluster_resources (docker stats CPU/RAM/I/O по узлам kind)
- Панель: блок ресурсов в карточке статистики; убраны строки подвала про api_routes/clusters
- Удалён app/docs/README.md; крошки app/docs → api_routes.md; README корня обновлён
This commit is contained in:
Sergey Antropoff
2026-04-04 06:37:36 +03:00
parent 710b360e4a
commit 6d4bc65c8a
12 changed files with 495 additions and 62 deletions
+78 -8
View File
@@ -35,6 +35,50 @@
return DOC_BASE + "?path=" + encodeURIComponent(relPath);
}
/**
* Хлебные крошки: Документация → файл (для app/docs/* кроме api_routes — ссылка «app/docs» на api_routes.md).
*/
function updateBreadcrumbs(docPath) {
var ol = document.getElementById("doc-breadcrumbs-list");
if (!ol) return;
ol.replaceChildren();
function addLiLink(href, text) {
var li = document.createElement("li");
li.className = "doc-breadcrumbs__item";
var a = document.createElement("a");
a.href = href;
a.className = "doc-breadcrumbs__link";
a.textContent = text;
li.appendChild(a);
ol.appendChild(li);
}
function addLiCurrent(text) {
var li = document.createElement("li");
li.className = "doc-breadcrumbs__item doc-breadcrumbs__item--current";
li.setAttribute("aria-current", "page");
li.textContent = text;
ol.appendChild(li);
}
addLiLink(DOC_BASE, "Документация");
if (!docPath) {
addLiCurrent("README.md (корень репозитория)");
return;
}
if (docPath.indexOf("app/docs/") === 0) {
if (docPath === "app/docs/api_routes.md") {
addLiCurrent("api_routes.md");
} else {
addLiLink(docUrlForPath("app/docs/api_routes.md"), "app/docs");
addLiCurrent(docPath.replace(/^.*\//, ""));
}
} else {
addLiCurrent(docPath);
}
}
/**
* Внутренняя ссылка на .md под app/docs/: полный путь или относительно текущего файла.
*/
@@ -76,8 +120,27 @@
});
}
/** Текст первого H1 в разобранном HTML (для заголовка вкладки). */
function extractFirstH1Text(root) {
var h = root.querySelector("h1");
if (!h) return "";
return (h.textContent || "").replace(/\s+/g, " ").trim();
}
/**
* До первого H2 — одна карточка; для каждого H2 — карточка заголовка и карточка тела до следующего H2.
* Заголовок вкладки: «Документация — <первый H1> — <app_title>» (как в api_routes: заголовок документа).
*/
function applyDocumentationTabTitle(h1Text) {
var appTitle = (body.dataset.appTitle || "").trim() || "kind";
if (h1Text) {
document.title = "Документация — " + h1Text + " — " + appTitle;
} else {
document.title = "Документация — " + appTitle;
}
}
/**
* До первого H2 — одна карточка; для каждого H2 — одна карточка: заголовок h2 и всё содержимое до следующего H2.
*/
function sectionizeFromNodes(sourceRoot) {
var page = document.createElement("div");
@@ -110,25 +173,23 @@
appendCard("readme-section-card--intro", chunk);
}
function flushH2Pair() {
function flushH2Section() {
if (i >= nodes.length) return;
var h2 = nodes[i];
if (h2.nodeType !== 1 || h2.tagName !== "H2") return;
i++;
appendCard("readme-section-card--head", [h2]);
var bodyChunk = [];
var chunk = [h2];
while (i < nodes.length) {
var n = nodes[i];
if (n.nodeType === 1 && n.tagName === "H2") break;
bodyChunk.push(n);
chunk.push(n);
i++;
}
appendCard("readme-section-card--body", bodyChunk);
appendCard("readme-section-card--block", chunk);
}
flushIntro();
while (i < nodes.length) flushH2Pair();
while (i < nodes.length) flushH2Section();
return page;
}
@@ -136,6 +197,7 @@
if (typeof marked === "undefined" || typeof DOMPurify === "undefined") {
showErr(errEl, "Не загружены скрипты marked или DOMPurify из /static/js/vendor/.");
if (loadEl) loadEl.classList.add("hidden");
applyDocumentationTabTitle("");
return;
}
@@ -155,12 +217,16 @@
temp.innerHTML = DOMPurify.sanitize(rawHtml);
rewriteMdLinks(temp, docPath);
var h1ForTitle = extractFirstH1Text(temp);
applyDocumentationTabTitle(h1ForTitle);
while (rootEl.firstChild) rootEl.removeChild(rootEl.firstChild);
var pageInner = sectionizeFromNodes(temp);
rootEl.appendChild(pageInner);
rootEl.removeAttribute("hidden");
} catch (e2) {
showErr(errEl, "Ошибка разбора: " + (e2.message || String(e2)));
applyDocumentationTabTitle("");
}
}
@@ -184,6 +250,8 @@
while (rootEl.firstChild) rootEl.removeChild(rootEl.firstChild);
loadEl.classList.remove("hidden");
updateBreadcrumbs(docPath);
var url = fetchUrlForPath(docPath);
try {
var r = await fetch(url, {
@@ -200,6 +268,7 @@
}
showErr(errEl, "Не удалось загрузить документ: " + (detail || r.statusText));
loadEl.classList.add("hidden");
applyDocumentationTabTitle("");
return;
}
@@ -215,6 +284,7 @@
} catch (e) {
showErr(errEl, "Ошибка: " + (e.message || String(e)));
loadEl.classList.add("hidden");
applyDocumentationTabTitle("");
}
}