# Панель экосистемы
Статическая навигационная панель для быстрого доступа к сервисам инфраструктуры. Поддерживает иерархию **раздел → подраздел → элемент**, поиск, дерево навигации, несколько режимов отображения и настройку внешнего вида.
Стек: 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 + `
`) |
| | `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 хранятся локально в браузере и не синхронизируются между устройствами
## Лицензия
Приватный проект. Используй и изменяй по своему усмотрению.