Веб-интерфейс: страница /clusters, навигация и крошки для кластеров

- Выделена страница списка кластеров, панель упрощена; nav_active и крошки
  ведут в раздел Кластеры; theme.js синхронизирует активную пилюлю по URL.
- Доработки дашборда, аддонов, журнала, стилей и API-документации.
- Поддержка Podman: docker-compose.podman.yml, скрипты сокета; Makefile и env.
This commit is contained in:
Sergey Antropoff
2026-04-04 13:42:21 +03:00
parent 17f6233fd7
commit eb063aec20
38 changed files with 3990 additions and 734 deletions
+49
View File
@@ -44,10 +44,59 @@
setTheme(getTheme() === "dark" ? "light" : "dark");
}
/**
* Нормализует pathname: убирает хвостовые слэши (кроме корня «/»).
* Нужен для сопоставления с маршрутами FastAPI без зависимости от редиректов.
*/
function normalizePathname(path) {
var p = path || "/";
if (p.length > 1 && p.charAt(p.length - 1) === "/") {
p = p.replace(/\/+$/, "");
}
return p || "/";
}
/**
* Какой пункт шапки (href пилюли) должен быть активен по текущему URL.
* Дублирует логику nav_active из шаблонов: страницы кластера и /cluster/…/edit
* относятся к разделу «Кластеры», чтобы подсветка не зависела только от SSR
* (кэш образа, устаревший контейнер и т.п.).
*/
function desiredNavHrefFromPath() {
var path = normalizePathname(window.location.pathname);
if (path === "/") return "/";
if (path === "/clusters") return "/clusters";
if (/^\/cluster\/[^/]+\/edit$/.test(path)) return "/clusters";
if (/^\/cluster\/[^/]+$/.test(path)) return "/clusters";
if (path.indexOf("/cluster-addons") === 0) return "/cluster-addons";
if (path.indexOf("/journal") === 0) return "/journal";
if (path.indexOf("/documentation") === 0) return "/documentation";
if (path.indexOf("/cluster-create") === 0) return "/clusters";
return null;
}
/** Снимает активность со всех пилюль в прокручиваемой полосе и включает одну по href. */
function syncNavPillActive() {
var want = desiredNavHrefFromPath();
if (!want) return;
var scroll = document.querySelector(".nav-links-scroll");
if (!scroll) return;
var pills = scroll.querySelectorAll("a.nav-link.nav-pill[href]");
pills.forEach(function (a) {
a.classList.remove("nav-pill--active");
});
pills.forEach(function (a) {
if (a.getAttribute("href") === want) {
a.classList.add("nav-pill--active");
}
});
}
function init() {
var btn = document.getElementById("theme-toggle");
if (btn) btn.addEventListener("click", toggleTheme);
syncToggleButton();
syncNavPillActive();
}
/* defer: скрипт в <head> выполняется после разбора документа — кнопка уже есть. */