const STORAGE_KEY = "ecosystem-panel-preferences";
// Меняй навигацию здесь: разделы -> подразделы -> элементы.
// url можно заменить на реальные адреса, tags помогают поиску.
const ecosystemSections = [
{
id: "user",
number: "1",
title: "Интерфейс пользователя",
description: "Рабочие инструменты пользователя для фильтрации и обработки сообщений.",
groups: [
{
id: "message-filtering",
number: "1.1",
title: "Управление фильтрацией сообщений",
description: "Правила, задания, события и статистика фильтрации.",
items: [
createItem("1.1.1", "Редактирование правил", "rules", ["правила", "фильтрация", "редактор"]),
createItem("1.1.2", "Управление заданиями", "redmine", ["redmine", "задачи", "задания"]),
createItem("1.1.3", "Применение правил", "rules", ["правила", "применение", "фильтрация"]),
createItem("1.1.4", "Просмотр статистики", "grafana", ["grafana", "статистика", "дашборд"]),
createItem("1.1.5", "Управление событиями фильтрации", "grafana", ["grafana", "события", "алерты"]),
createItem("1.1.6", "Просмотр журнала событий фильтрации", "grafana", ["grafana", "журнал", "события"]),
],
},
{
id: "message-processing",
number: "1.2",
title: "Обработка сообщений",
description: "Просмотр, оформление и доступ к хранилищам сообщений.",
items: [
createItem("1.2.1", "Просмотр и оформление", "messages", ["сообщения", "просмотр", "оформление"]),
createItem("1.2.2", "Прямой доступ", "ceph", ["ceph", "доступ", "объекты"]),
createItem("1.2.3", "S3 Хранилище", "ceph", ["ceph", "s3", "хранилище"]),
],
},
],
},
{
id: "admin",
number: "2",
title: "Интерфейс администратора",
description: "Администрирование сервисов, кластеров, мониторинга и аппаратной части.",
groups: [
{
id: "service-monitoring",
number: "2.1",
title: "Управление и мониторинг сервисов",
description: "Кластер, модули, события мониторинга и журналы ошибок.",
items: [
createItem("2.1.1", "Управление кластером", "kubernetes", ["kubernetes", "кластер", "k8s"]),
createItem("2.1.2", "Мониторинг состояния модулей", "grafana", ["grafana", "модули", "мониторинг"]),
createItem("2.1.3", "Управление событиями мониторинга", "grafana", ["grafana", "события", "алерты"]),
createItem("2.1.4", "Просмотр журнала событий мониторинга", "grafana", ["grafana", "журнал", "мониторинг"]),
createItem("2.1.5", "Просмотр журналов ошибок", "opensearch", ["opensearch", "ошибки", "логи"]),
],
},
{
id: "service-configuration",
number: "2.2",
title: "Настройка сервисов",
description: "Пользователи, очереди, xPath, модули и конфигурации.",
items: [
createItem("2.2.1", "Управление пользователями и очередями", "rabbitmq", ["пользователи", "очереди", "rabbitmq"]),
createItem("2.2.2", "Отладка xPath-запросов", "xpath", ["xpath", "отладка", "запросы"]),
createItem("2.2.3", "Конфигурирование модулей", "config", ["конфигурация", "модули", "настройки"]),
createItem("2.2.4", "Просмотр журнала конфигурирования", "gitea", ["gitea", "журнал", "конфигурация"]),
createItem("2.2.5", "Прямой доступ к конфигурации", "gitea", ["gitea", "конфигурация", "git"]),
],
},
{
id: "hardware",
number: "2.3",
title: "Управление и мониторинг аппаратной части",
description: "Сенсоры, ресурсы и прямые панели серверов.",
items: [
createItem("2.3.1", "Мониторинг показаний сенсоров", "grafana", ["grafana", "сенсоры", "железо"]),
createItem("2.3.2", "Мониторинг потребления ресурсов", "grafana", ["grafana", "ресурсы", "метрики"]),
createItem("2.3.3", "СЕРВЕР01", "ipmi", ["ipmi", "сервер01", "железо"]),
createItem("2.3.4", "СЕРВЕР02", "ipmi", ["ipmi", "сервер02", "железо"]),
createItem("2.3.5", "СЕРВЕР03", "ipmi", ["ipmi", "сервер03", "железо"]),
createItem("2.3.6", "СЕРВЕР04", "ipmi", ["ipmi", "сервер04", "железо"]),
createItem("2.3.7", "СЕРВЕР05", "ipmi", ["ipmi", "сервер05", "железо"]),
],
},
{
id: "service-panels",
number: "2.4",
title: "Настройка отдельных сервисов",
description: "Панели управления инфраструктурными сервисами.",
items: [
createItem("2.4.1", "Панель управления LDAP Account Manager", "ldap", ["ldap", "accounts", "пользователи"]),
createItem("2.4.2", "Панель управления кластера RabbitMQ", "rabbitmq", ["rabbitmq", "кластер", "очереди"]),
createItem("2.4.3", "Панель управления кластера Ceph", "ceph", ["ceph", "кластер", "storage"]),
createItem("2.4.4", "Панель управления Prometheus", "prometheus", ["prometheus", "метрики", "мониторинг"]),
createItem("2.4.5", "Панель управления XMPP-сервера", "xmpp", ["xmpp", "чат", "сервер"]),
createItem("2.4.6", "Панель управления PostgreSQL", "postgresql", ["postgresql", "база", "db"]),
createItem("2.4.7", "Подключение к БД", "cloudbeaver", ["cloudbeaver", "database", "sql"]),
],
},
{
id: "packages",
number: "2.5",
title: "Образы и пакеты",
description: "Реестры образов и deb-пакетов.",
items: [
createItem("2.5.1", "Реестр docker-образов", "gitea", ["gitea", "docker", "registry"]),
createItem("2.5.2", "Репозиторий deb-пакетов", "gitea", ["gitea", "deb", "packages"]),
],
},
],
},
];
const serviceMeta = {
ceph: { label: "Ceph", icon: "assets/ceph.svg" },
cloudbeaver: { label: "CloudBeaver", icon: "assets/database.svg" },
config: { label: "Config", icon: "assets/config.svg" },
gitea: { label: "Gitea", icon: "assets/gitea.svg" },
grafana: { label: "Grafana", icon: "assets/grafana.svg" },
ipmi: { label: "IPMI", icon: "assets/server.svg" },
kubernetes: { label: "Kubernetes", icon: "assets/kubernetes.svg" },
ldap: { label: "LDAP", icon: "assets/ldap.svg" },
messages: { label: "Messages", icon: "assets/messages.svg" },
opensearch: { label: "OpenSearch", icon: "assets/opensearch.svg" },
postgresql: { label: "PostgreSQL", icon: "assets/database.svg" },
prometheus: { label: "Prometheus", icon: "assets/prometheus.svg" },
rabbitmq: { label: "RabbitMQ", icon: "assets/rabbitmq.svg" },
redmine: { label: "Redmine", icon: "assets/redmine.svg" },
rules: { label: "Rules", icon: "assets/rules.svg" },
xpath: { label: "xPath", icon: "assets/xpath.svg" },
xmpp: { label: "XMPP", icon: "assets/xmpp.svg" },
};
const defaultPreferences = {
theme: window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark",
view: "cards",
accent: "violet",
};
const availableViews = ["cards", "list", "compact-cards", "compact-list"];
const preferences = { ...defaultPreferences, ...readPreferences() };
const catalog = document.querySelector("[data-catalog-root]");
const searchInput = document.querySelector("[data-search-input]");
const themeButtons = document.querySelectorAll("[data-theme]");
const viewButtons = document.querySelectorAll("[data-view]");
const accentButtons = document.querySelectorAll("[data-accent]");
const emptyTemplate = document.querySelector("#empty-template");
const totals = calculateTotals(ecosystemSections);
document.querySelector("[data-total-sections]").textContent = totals.sections;
document.querySelector("[data-total-groups]").textContent = totals.groups;
document.querySelector("[data-total-items]").textContent = totals.items;
applyPreferences(preferences);
renderCatalog();
searchInput.addEventListener("input", renderCatalog);
themeButtons.forEach((button) => {
button.addEventListener("click", () => {
preferences.theme = button.dataset.theme;
applyPreferences(preferences);
savePreferences(preferences);
});
});
viewButtons.forEach((button) => {
button.addEventListener("click", () => {
preferences.view = button.dataset.view;
applyPreferences(preferences);
savePreferences(preferences);
});
});
accentButtons.forEach((button) => {
button.addEventListener("click", () => {
preferences.accent = button.dataset.accent;
applyPreferences(preferences);
savePreferences(preferences);
});
});
function createItem(number, title, type, tags = [], url = "#") {
return { number, title, type, tags, url };
}
function renderCatalog() {
const query = normalize(searchInput.value);
const fragment = document.createDocumentFragment();
let visibleItems = 0;
ecosystemSections.forEach((section) => {
const sectionElement = createSectionElement(section);
let visibleGroups = 0;
let sectionVisibleItems = 0;
section.groups.forEach((group) => {
const filteredItems = group.items.filter((item) => matchesQuery(item, group, section, query));
if (!filteredItems.length) {
return;
}
visibleGroups += 1;
sectionVisibleItems += filteredItems.length;
sectionElement.querySelector(".section-groups").append(createGroupElement(group, filteredItems));
});
if (visibleGroups) {
sectionElement.querySelector("[data-section-count]").textContent = sectionVisibleItems;
fragment.append(sectionElement);
visibleItems += sectionVisibleItems;
}
});
catalog.replaceChildren();
if (visibleItems) {
catalog.append(fragment);
attachCardInteractions();
} else {
catalog.append(emptyTemplate.content.cloneNode(true));
}
document.querySelector("[data-visible-items]").textContent = visibleItems;
}
function createSectionElement(section) {
const sectionElement = document.createElement("article");
sectionElement.className = "catalog-section";
sectionElement.innerHTML = `
${section.number} раздел ${section.description}${section.title}
${group.number}
${group.description}
${item.number}
${meta.label}