вынесение настроек в отдельный файл, добавление readme
This commit is contained in:
parent
75ed13712a
commit
91ca4e8d57
255
README.md
Normal file
255
README.md
Normal file
@ -0,0 +1,255 @@
|
||||
# Панель экосистемы
|
||||
|
||||
Статическая навигационная панель для быстрого доступа к сервисам инфраструктуры. Поддерживает иерархию **раздел → подраздел → элемент**, поиск, дерево навигации, несколько режимов отображения и настройку внешнего вида.
|
||||
|
||||
Стек: HTML, CSS, vanilla JavaScript. Без сборщиков и фреймворков.
|
||||
|
||||
## Возможности
|
||||
|
||||
- **Древовидная навигация** слева в стиле файлового менеджера
|
||||
- **Каталог карточек** справа с collapsible-разделами
|
||||
- **Поиск** по названию, тегам, типу сервиса и родительским разделам
|
||||
- **4 режима отображения**: плитки, список, компактные плитки, компактный список
|
||||
- **Темы**: светлая / тёмная, 4 пресета акцента + свой градиент
|
||||
- **Настройки** через popup (иконка ⚙)
|
||||
- **Изменяемая ширина** бокового меню (drag)
|
||||
- **Сохранение состояния** в `localStorage` (настройки UI, раскрытие веток)
|
||||
- **Конфигурация через JSON** — локальный объект или загрузка по URL
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
Панель использует `fetch`, поэтому её нужно открывать через HTTP-сервер, а не напрямую как `file://`.
|
||||
|
||||
```bash
|
||||
cd ecosystem
|
||||
npx serve .
|
||||
```
|
||||
|
||||
Открой в браузере адрес, который покажет сервер (обычно `http://localhost:3000`).
|
||||
|
||||
## Структура проекта
|
||||
|
||||
```
|
||||
ecosystem/
|
||||
├── index.html # Разметка страницы
|
||||
├── styles.css # Стили, темы, адаптив
|
||||
├── script.js # Логика, рендер, настройки
|
||||
├── settings.json # Источник данных панели (URL или inline)
|
||||
├── panel-data.json # Данные навигации (по умолчанию)
|
||||
├── assets/ # SVG-иконки сервисов
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## Конфигурация
|
||||
|
||||
### `settings.json`
|
||||
|
||||
Указывает, откуда загружать данные панели.
|
||||
|
||||
**Вариант 1 — загрузка из файла или URL:**
|
||||
|
||||
```json
|
||||
{
|
||||
"dataUrl": "./panel-data.json"
|
||||
}
|
||||
```
|
||||
|
||||
Можно указать внешний адрес:
|
||||
|
||||
```json
|
||||
{
|
||||
"dataUrl": "https://example.com/ecosystem/panel-data.json"
|
||||
}
|
||||
```
|
||||
|
||||
**Вариант 2 — inline-объект в том же файле:**
|
||||
|
||||
```json
|
||||
{
|
||||
"dataUrl": "",
|
||||
"data": {
|
||||
"title": "Панель экосистемы",
|
||||
"sections": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> Если `dataUrl` не пустой, он имеет **приоритет** над полем `data`.
|
||||
|
||||
При ошибке загрузки или невалидном JSON на странице показывается экран с описанием проблемы.
|
||||
|
||||
### `panel-data.json`
|
||||
|
||||
Основной файл с содержимым панели.
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "Панель экосистемы",
|
||||
"sections": [
|
||||
{
|
||||
"id": "user",
|
||||
"number": "1",
|
||||
"title": "Интерфейс пользователя",
|
||||
"description": "Описание раздела",
|
||||
"groups": [
|
||||
{
|
||||
"id": "message-filtering",
|
||||
"number": "1.1",
|
||||
"title": "Управление фильтрацией",
|
||||
"description": "Описание подраздела",
|
||||
"items": [
|
||||
{
|
||||
"number": "1.1.1",
|
||||
"title": "Редактирование правил",
|
||||
"type": "rules",
|
||||
"tags": ["правила", "фильтрация"],
|
||||
"url": "https://rules.example.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"serviceMeta": {}
|
||||
}
|
||||
```
|
||||
|
||||
### Поля данных
|
||||
|
||||
| Уровень | Поля | Описание |
|
||||
|---------|------|----------|
|
||||
| **Корень** | `title` | Заголовок панели (sidebar + `<title>`) |
|
||||
| | `sections` | Массив разделов |
|
||||
| | `serviceMeta` | Опционально: доп. типы сервисов |
|
||||
| **section** | `id`, `number`, `title`, `description`, `groups` | Крупный раздел |
|
||||
| **group** | `id`, `number`, `title`, `description`, `items` | Подраздел |
|
||||
| **item** | `number`, `title`, `type`, `tags`, `url` | Кликабельный элемент |
|
||||
|
||||
- `type` — ключ из `serviceMeta` (определяет иконку и подпись типа)
|
||||
- `tags` — используются в поиске и опционально в дереве
|
||||
- `url` — ссылка перехода; `"#"` означает заглушку (клик не ведёт никуда)
|
||||
|
||||
### Типы сервисов (`serviceMeta`)
|
||||
|
||||
Базовые типы заданы в `script.js`:
|
||||
|
||||
`ceph`, `cloudbeaver`, `config`, `gitea`, `grafana`, `ipmi`, `kubernetes`, `ldap`, `messages`, `opensearch`, `postgresql`, `prometheus`, `rabbitmq`, `redmine`, `rules`, `xpath`, `xmpp`
|
||||
|
||||
В `panel-data.json` можно добавить или переопределить типы:
|
||||
|
||||
```json
|
||||
{
|
||||
"serviceMeta": {
|
||||
"my-service": {
|
||||
"label": "My Service",
|
||||
"icon": "assets/config.svg"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Настройки интерфейса (⚙)
|
||||
|
||||
Доступны в popup-окне и сохраняются в `localStorage` (`ecosystem-panel-preferences`):
|
||||
|
||||
| Настройка | Описание |
|
||||
|-----------|----------|
|
||||
| **Тема** | Светлая / тёмная |
|
||||
| **Вид** | Плитки, список, компакт плитки, компакт список |
|
||||
| **Нумерация** | Показывать номера разделов, подразделов и элементов |
|
||||
| **Теги дерева** | Поисковые теги рядом с элементами в дереве |
|
||||
| **2 колонки в дереве** | Элементы папок в 2 колонки (взаимоисключимо с тегами) |
|
||||
| **Авто раскрывать поиск** | При поиске сворачивать ветки без совпадений |
|
||||
| **Акцент** | violet, cyan, lime, amber или свой градиент |
|
||||
|
||||
### Свой градиент
|
||||
|
||||
Кнопка **«Свой»** открывает настройки:
|
||||
|
||||
- угол (0–360°)
|
||||
- цвет «от» и «до»
|
||||
|
||||
Значения сохраняются в `localStorage`.
|
||||
|
||||
## Поведение интерфейса
|
||||
|
||||
### Поиск
|
||||
|
||||
Фильтрует каталог и подсвечивает совпадения в дереве и карточках. Ищет по:
|
||||
|
||||
- названию элемента
|
||||
- номеру
|
||||
- тегам
|
||||
- типу сервиса
|
||||
- названиям родительских разделов
|
||||
|
||||
### Дерево навигации
|
||||
|
||||
- Клик по **папке** — раскрытие/сворачивание
|
||||
- Клик по **названию** — прокрутка к элементу в каталоге
|
||||
- Состояние раскрытия сохраняется отдельно для дерева и контента (`ecosystem-panel-collapse-state`)
|
||||
|
||||
### Адаптив
|
||||
|
||||
- **Touch-устройства** (`pointer: coarse` / `hover: none`): stacked-layout, меню сверху
|
||||
- **Мышь** (`pointer: fine` + `hover: hover`): sidebar + resizer + каталог в колонках
|
||||
- **2 колонки в дереве на touch**: в portrait или при ширине от 520px
|
||||
|
||||
### Ширина layout
|
||||
|
||||
- Обычный режим: max **1560px**, sidebar до **560px**
|
||||
- Режим «2 колонки в дереве»: max **1920px**, sidebar до **760px**
|
||||
|
||||
## localStorage
|
||||
|
||||
| Ключ | Содержимое |
|
||||
|------|------------|
|
||||
| `ecosystem-panel-preferences` | Тема, акцент, вид, ширина sidebar, toggle-настройки |
|
||||
| `ecosystem-panel-collapse-state` | Раскрытые/свернутые ветки дерева и каталога |
|
||||
|
||||
## Разработка
|
||||
|
||||
### Добавить новый элемент
|
||||
|
||||
1. Открой `panel-data.json`
|
||||
2. Найди нужный `group` в `sections`
|
||||
3. Добавь объект в `items`:
|
||||
|
||||
```json
|
||||
{
|
||||
"number": "2.5.3",
|
||||
"title": "Новый сервис",
|
||||
"type": "gitea",
|
||||
"tags": ["gitea", "новый"],
|
||||
"url": "https://gitea.example.com"
|
||||
}
|
||||
```
|
||||
|
||||
4. Обнови страницу
|
||||
|
||||
### Добавить новую иконку
|
||||
|
||||
1. Положи SVG в `assets/`
|
||||
2. Зарегистрируй тип в `serviceMeta` внутри `panel-data.json` или используй существующий `type`
|
||||
|
||||
### Внешний источник данных
|
||||
|
||||
Укажи URL в `settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"dataUrl": "https://your-server/panel-data.json"
|
||||
}
|
||||
```
|
||||
|
||||
Убедись, что сервер отдаёт корректный `Content-Type: application/json` и разрешает CORS, если домен отличается от панели.
|
||||
|
||||
## Ограничения
|
||||
|
||||
- Требуется HTTP-сервер (`fetch` не работает с `file://`)
|
||||
- Внешний `dataUrl` должен быть доступен по CORS
|
||||
- Настройки UI хранятся локально в браузере и не синхронизируются между устройствами
|
||||
|
||||
## Лицензия
|
||||
|
||||
Приватный проект. Используй и изменяй по своему усмотрению.
|
||||
11
index.html
11
index.html
@ -227,6 +227,15 @@
|
||||
<!-- Каталог карточек генерируется из ecosystemSections в script.js. -->
|
||||
<section class="catalog cards-view" aria-live="polite" data-catalog-root></section>
|
||||
|
||||
<!-- Шаблон ошибки загрузки settings.json / panel data. -->
|
||||
<template id="config-error-template">
|
||||
<div class="empty-state config-error-state">
|
||||
<img src="assets/config.svg" alt="" />
|
||||
<h2>Не удалось загрузить панель</h2>
|
||||
<p data-config-error-message></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Шаблон состояния, когда поиск не нашел ни одного элемента. -->
|
||||
<template id="empty-template">
|
||||
<div class="empty-state">
|
||||
@ -238,6 +247,6 @@
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script src="script.js"></script>
|
||||
<script src="script.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
113
panel-data.json
Normal file
113
panel-data.json
Normal file
@ -0,0 +1,113 @@
|
||||
{
|
||||
"title": "Панель экосистемы",
|
||||
"sections": [
|
||||
{
|
||||
"id": "user",
|
||||
"number": "1",
|
||||
"title": "Интерфейс пользователя",
|
||||
"description": "Рабочие инструменты пользователя для фильтрации и обработки сообщений.",
|
||||
"groups": [
|
||||
{
|
||||
"id": "message-filtering",
|
||||
"number": "1.1",
|
||||
"title": "Управление фильтрацией сообщений",
|
||||
"description": "Правила, задания, события и статистика фильтрации.",
|
||||
"items": [
|
||||
{ "number": "1.1.1", "title": "Редактирование правил", "type": "rules", "tags": ["правила", "фильтрация", "редактор"], "url": "#" },
|
||||
{ "number": "1.1.2", "title": "Управление заданиями", "type": "redmine", "tags": ["redmine", "задачи", "задания"], "url": "#" },
|
||||
{ "number": "1.1.3", "title": "Применение правил", "type": "rules", "tags": ["правила", "применение", "фильтрация"], "url": "#" },
|
||||
{ "number": "1.1.4", "title": "Просмотр статистики", "type": "grafana", "tags": ["grafana", "статистика", "дашборд"], "url": "#" },
|
||||
{ "number": "1.1.5", "title": "Управление событиями фильтрации", "type": "grafana", "tags": ["grafana", "события", "алерты"], "url": "#" },
|
||||
{ "number": "1.1.6", "title": "Просмотр журнала событий фильтрации", "type": "grafana", "tags": ["grafana", "журнал", "события"], "url": "#" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "message-processing",
|
||||
"number": "1.2",
|
||||
"title": "Обработка сообщений",
|
||||
"description": "Просмотр, оформление и доступ к хранилищам сообщений.",
|
||||
"items": [
|
||||
{ "number": "1.2.1", "title": "Просмотр и оформление", "type": "messages", "tags": ["сообщения", "просмотр", "оформление"], "url": "#" },
|
||||
{ "number": "1.2.2", "title": "Прямой доступ", "type": "ceph", "tags": ["ceph", "доступ", "объекты"], "url": "#" },
|
||||
{ "number": "1.2.3", "title": "S3 Хранилище", "type": "ceph", "tags": ["ceph", "s3", "хранилище"], "url": "#" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "admin",
|
||||
"number": "2",
|
||||
"title": "Интерфейс администратора",
|
||||
"description": "Администрирование сервисов, кластеров, мониторинга и аппаратной части.",
|
||||
"groups": [
|
||||
{
|
||||
"id": "service-monitoring",
|
||||
"number": "2.1",
|
||||
"title": "Управление и мониторинг сервисов",
|
||||
"description": "Кластер, модули, события мониторинга и журналы ошибок.",
|
||||
"items": [
|
||||
{ "number": "2.1.1", "title": "Управление кластером", "type": "kubernetes", "tags": ["kubernetes", "кластер", "k8s"], "url": "#" },
|
||||
{ "number": "2.1.2", "title": "Мониторинг состояния модулей", "type": "grafana", "tags": ["grafana", "модули", "мониторинг"], "url": "#" },
|
||||
{ "number": "2.1.3", "title": "Управление событиями мониторинга", "type": "grafana", "tags": ["grafana", "события", "алерты"], "url": "#" },
|
||||
{ "number": "2.1.4", "title": "Просмотр журнала событий мониторинга", "type": "grafana", "tags": ["grafana", "журнал", "мониторинг"], "url": "#" },
|
||||
{ "number": "2.1.5", "title": "Просмотр журналов ошибок", "type": "opensearch", "tags": ["opensearch", "ошибки", "логи"], "url": "#" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "service-configuration",
|
||||
"number": "2.2",
|
||||
"title": "Настройка сервисов",
|
||||
"description": "Пользователи, очереди, xPath, модули и конфигурации.",
|
||||
"items": [
|
||||
{ "number": "2.2.1", "title": "Управление пользователями и очередями", "type": "rabbitmq", "tags": ["пользователи", "очереди", "rabbitmq"], "url": "#" },
|
||||
{ "number": "2.2.2", "title": "Отладка xPath-запросов", "type": "xpath", "tags": ["xpath", "отладка", "запросы"], "url": "#" },
|
||||
{ "number": "2.2.3", "title": "Конфигурирование модулей", "type": "config", "tags": ["конфигурация", "модули", "настройки"], "url": "#" },
|
||||
{ "number": "2.2.4", "title": "Просмотр журнала конфигурирования", "type": "gitea", "tags": ["gitea", "журнал", "конфигурация"], "url": "#" },
|
||||
{ "number": "2.2.5", "title": "Прямой доступ к конфигурации", "type": "gitea", "tags": ["gitea", "конфигурация", "git"], "url": "#" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "hardware",
|
||||
"number": "2.3",
|
||||
"title": "Управление и мониторинг аппаратной части",
|
||||
"description": "Сенсоры, ресурсы и прямые панели серверов.",
|
||||
"items": [
|
||||
{ "number": "2.3.1", "title": "Мониторинг показаний сенсоров", "type": "grafana", "tags": ["grafana", "сенсоры", "железо"], "url": "#" },
|
||||
{ "number": "2.3.2", "title": "Мониторинг потребления ресурсов", "type": "grafana", "tags": ["grafana", "ресурсы", "метрики"], "url": "#" },
|
||||
{ "number": "2.3.3", "title": "СЕРВЕР01", "type": "ipmi", "tags": ["ipmi", "сервер01", "железо"], "url": "#" },
|
||||
{ "number": "2.3.4", "title": "СЕРВЕР02", "type": "ipmi", "tags": ["ipmi", "сервер02", "железо"], "url": "#" },
|
||||
{ "number": "2.3.5", "title": "СЕРВЕР03", "type": "ipmi", "tags": ["ipmi", "сервер03", "железо"], "url": "#" },
|
||||
{ "number": "2.3.6", "title": "СЕРВЕР04", "type": "ipmi", "tags": ["ipmi", "сервер04", "железо"], "url": "#" },
|
||||
{ "number": "2.3.7", "title": "СЕРВЕР05", "type": "ipmi", "tags": ["ipmi", "сервер05", "железо"], "url": "#" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "service-panels",
|
||||
"number": "2.4",
|
||||
"title": "Настройка отдельных сервисов",
|
||||
"description": "Панели управления инфраструктурными сервисами.",
|
||||
"items": [
|
||||
{ "number": "2.4.1", "title": "Панель управления LDAP Account Manager", "type": "ldap", "tags": ["ldap", "accounts", "пользователи"], "url": "#" },
|
||||
{ "number": "2.4.2", "title": "Панель управления кластера RabbitMQ", "type": "rabbitmq", "tags": ["rabbitmq", "кластер", "очереди"], "url": "#" },
|
||||
{ "number": "2.4.3", "title": "Панель управления кластера Ceph", "type": "ceph", "tags": ["ceph", "кластер", "storage"], "url": "#" },
|
||||
{ "number": "2.4.4", "title": "Панель управления Prometheus", "type": "prometheus", "tags": ["prometheus", "метрики", "мониторинг"], "url": "#" },
|
||||
{ "number": "2.4.5", "title": "Панель управления XMPP-сервера", "type": "xmpp", "tags": ["xmpp", "чат", "сервер"], "url": "#" },
|
||||
{ "number": "2.4.6", "title": "Панель управления PostgreSQL", "type": "postgresql", "tags": ["postgresql", "база", "db"], "url": "#" },
|
||||
{ "number": "2.4.7", "title": "Подключение к БД", "type": "cloudbeaver", "tags": ["cloudbeaver", "database", "sql"], "url": "#" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "packages",
|
||||
"number": "2.5",
|
||||
"title": "Образы и пакеты",
|
||||
"description": "Реестры образов и deb-пакетов.",
|
||||
"items": [
|
||||
{ "number": "2.5.1", "title": "Реестр docker-образов", "type": "gitea", "tags": ["gitea", "docker", "registry"], "url": "#" },
|
||||
{ "number": "2.5.2", "title": "Репозиторий deb-пакетов", "type": "gitea", "tags": ["gitea", "deb", "packages"], "url": "#" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"serviceMeta": {}
|
||||
}
|
||||
265
script.js
265
script.js
@ -10,135 +10,17 @@ const STORAGE_KEY = "ecosystem-panel-preferences";
|
||||
const COLLAPSE_STORAGE_KEY = "ecosystem-panel-collapse-state";
|
||||
|
||||
// ============================================================================
|
||||
// Navigation data model
|
||||
// Panel configuration (settings.json)
|
||||
// ============================================================================
|
||||
// Меняй навигацию здесь: разделы -> подразделы -> элементы.
|
||||
// url можно заменить на реальные адреса, tags помогают поиску.
|
||||
//
|
||||
// Структура:
|
||||
// - section: крупный раздел панели;
|
||||
// - group: подраздел внутри section;
|
||||
// - item: конечный кликабельный элемент.
|
||||
//
|
||||
// Каждый item имеет:
|
||||
// - number: человекочитаемый номер;
|
||||
// - title: название карточки;
|
||||
// - type: ключ из serviceMeta, который задает иконку и тип сервиса;
|
||||
// - tags: поисковые теги, которые также можно показывать в дереве;
|
||||
// - url: ссылка перехода. По умолчанию "#", чтобы заглушки не дергали страницу.
|
||||
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"]),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
// settings.json задает источник данных панели:
|
||||
// - dataUrl: URL или путь к JSON (fetch), например "./panel-data.json"
|
||||
// - data: inline-объект с той же структурой, если dataUrl пустой
|
||||
const SETTINGS_PATH = "./settings.json";
|
||||
|
||||
// Метаданные типов сервисов.
|
||||
// item.type ссылается на ключ здесь: так карточки получают label и icon.
|
||||
const serviceMeta = {
|
||||
let ecosystemSections = [];
|
||||
let panelTitle = "Панель экосистемы";
|
||||
|
||||
const defaultServiceMeta = {
|
||||
ceph: { label: "Ceph", icon: "assets/ceph.svg" },
|
||||
cloudbeaver: { label: "CloudBeaver", icon: "assets/database.svg" },
|
||||
config: { label: "Config", icon: "assets/config.svg" },
|
||||
@ -158,6 +40,8 @@ const serviceMeta = {
|
||||
xmpp: { label: "XMPP", icon: "assets/xmpp.svg" },
|
||||
};
|
||||
|
||||
let serviceMeta = { ...defaultServiceMeta };
|
||||
|
||||
// ============================================================================
|
||||
// Default UI preferences
|
||||
// ============================================================================
|
||||
@ -210,16 +94,31 @@ const showTreeTagsInput = document.querySelector("[data-show-tree-tags]");
|
||||
const treeTwoColumnInput = document.querySelector("[data-tree-two-column]");
|
||||
const autoCollapseSearchInput = document.querySelector("[data-auto-collapse-search]");
|
||||
const emptyTemplate = document.querySelector("#empty-template");
|
||||
const configErrorTemplate = document.querySelector("#config-error-template");
|
||||
const pageTitleElement = document.querySelector("#page-title");
|
||||
|
||||
// ============================================================================
|
||||
// Initial render
|
||||
// ============================================================================
|
||||
// Сначала строим дерево, затем применяем настройки, затем рендерим каталог.
|
||||
// Такой порядок нужен, потому что applyPreferences выставляет data-атрибуты
|
||||
// и CSS-переменные, от которых зависит внешний вид уже созданного DOM.
|
||||
bootstrap();
|
||||
|
||||
async function bootstrap() {
|
||||
try {
|
||||
const panelData = await loadPanelConfig();
|
||||
ecosystemSections = panelData.sections;
|
||||
serviceMeta = panelData.serviceMeta;
|
||||
panelTitle = panelData.title;
|
||||
pageTitleElement.textContent = panelTitle;
|
||||
document.title = panelTitle;
|
||||
} catch (error) {
|
||||
showConfigError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
renderNavigationTree();
|
||||
applyPreferences(preferences);
|
||||
renderCatalog();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Event bindings
|
||||
@ -388,10 +287,114 @@ sidebarResizer.addEventListener("pointerdown", (event) => {
|
||||
sidebarResizer.addEventListener("pointercancel", handlePointerUp);
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Panel config loading
|
||||
// ============================================================================
|
||||
async function loadPanelConfig(settingsPath = SETTINGS_PATH) {
|
||||
const settings = await fetchJson(settingsPath);
|
||||
const dataUrl = typeof settings.dataUrl === "string" ? settings.dataUrl.trim() : "";
|
||||
|
||||
let panelSource = settings.data;
|
||||
if (dataUrl) {
|
||||
panelSource = await fetchJson(dataUrl);
|
||||
}
|
||||
|
||||
if (!panelSource || typeof panelSource !== "object") {
|
||||
throw new Error("settings.json должен содержать data или dataUrl");
|
||||
}
|
||||
|
||||
return normalizePanelData(panelSource);
|
||||
}
|
||||
|
||||
async function fetchJson(url) {
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Не удалось загрузить ${url}: HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
function normalizePanelData(raw) {
|
||||
const sections = Array.isArray(raw.sections) ? raw.sections.map(normalizeSection) : [];
|
||||
|
||||
if (!sections.length) {
|
||||
throw new Error("В данных панели нет sections");
|
||||
}
|
||||
|
||||
return {
|
||||
title: typeof raw.title === "string" && raw.title.trim() ? raw.title.trim() : "Панель экосистемы",
|
||||
sections,
|
||||
serviceMeta: {
|
||||
...defaultServiceMeta,
|
||||
...(raw.serviceMeta && typeof raw.serviceMeta === "object" ? raw.serviceMeta : {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeSection(section) {
|
||||
if (!section?.id || !section?.title) {
|
||||
throw new Error("Каждый section должен содержать id и title");
|
||||
}
|
||||
|
||||
return {
|
||||
id: String(section.id),
|
||||
number: String(section.number ?? ""),
|
||||
title: String(section.title),
|
||||
description: String(section.description ?? ""),
|
||||
groups: Array.isArray(section.groups) ? section.groups.map(normalizeGroup) : [],
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeGroup(group) {
|
||||
if (!group?.id || !group?.title) {
|
||||
throw new Error("Каждый group должен содержать id и title");
|
||||
}
|
||||
|
||||
return {
|
||||
id: String(group.id),
|
||||
number: String(group.number ?? ""),
|
||||
title: String(group.title),
|
||||
description: String(group.description ?? ""),
|
||||
items: Array.isArray(group.items) ? group.items.map(normalizeItemFromConfig) : [],
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeItemFromConfig(item) {
|
||||
if (!item?.title || !item?.type) {
|
||||
throw new Error("Каждый item должен содержать title и type");
|
||||
}
|
||||
|
||||
return {
|
||||
number: String(item.number ?? ""),
|
||||
title: String(item.title),
|
||||
type: String(item.type),
|
||||
tags: Array.isArray(item.tags) ? item.tags.map(String) : [],
|
||||
url: typeof item.url === "string" && item.url.trim() ? item.url.trim() : "#",
|
||||
};
|
||||
}
|
||||
|
||||
function showConfigError(error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
navTree.replaceChildren();
|
||||
catalog.replaceChildren();
|
||||
|
||||
if (configErrorTemplate) {
|
||||
const node = configErrorTemplate.content.cloneNode(true);
|
||||
node.querySelector("[data-config-error-message]").textContent =
|
||||
`${message}. Проверь settings.json и доступность dataUrl.`;
|
||||
catalog.append(node);
|
||||
return;
|
||||
}
|
||||
|
||||
catalog.textContent = message;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Data helpers
|
||||
// ============================================================================
|
||||
// Упрощает запись item-объектов в ecosystemSections.
|
||||
// Упрощает запись item-объектов при ручной правке JS (legacy helper).
|
||||
function createItem(number, title, type, tags = [], url = "#") {
|
||||
return { number, title, type, tags, url };
|
||||
}
|
||||
|
||||
4
settings.json
Normal file
4
settings.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"dataUrl": "./panel-data.json",
|
||||
"data": null
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user