# Панель экосистемы Статическая навигационная панель для быстрого доступа к сервисам инфраструктуры. Поддерживает гибкую иерархию данных, поиск, дерево навигации, несколько режимов отображения и настройку внешнего вида. Стек: HTML, CSS, vanilla JavaScript. Без сборщиков и фреймворков. ## Оглавление - [Возможности](#возможности) - [Быстрый старт](#быстрый-старт) - [Структура проекта](#структура-проекта) - [Конфигурация](#конфигурация) - [`settings.json`](#settingsjson) - [Пресет организации (`defaults` + `features`)](#пресет-организации-defaults--features) - [Форматы иерархии](#форматы-иерархии) - [Схема `panel-data`](#схема-panel-data) - [Поля данных](#поля-данных) - [Примеры данных](#примеры-данных) - [Готовые демо-страницы (`examples/`)](#готовые-демо-страницы-examples) - [Типы сервисов (`serviceMeta`)](#типы-сервисов-servicemeta) - [Рекомендуемые типы сервисов](#рекомендуемые-типы-сервисов) - [Настройки интерфейса (⚙)](#настройки-интерфейса-) - [Поведение интерфейса](#поведение-интерфейса) - [localStorage](#localstorage) - [Разработка](#разработка) - [Ограничения](#ограничения) - [Лицензия](#лицензия) ## Возможности - **Древовидная навигация** слева в стиле файлового менеджера - **Каталог карточек** справа с 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 # Данные + пресет UI организации ├── settings-top-bar.example.json # Пример пресета «шапка сверху» ├── panel-data.json # Данные навигации (рабочий файл) ├── examples/ # Готовые демо-страницы (см. examples/README.md) │ ├── index.html # Оглавление примеров │ ├── shared/ # styles, script, assets для демо │ ├── full/ # section → group → item │ ├── section-items/ # section → item │ ├── flat/ # плоский items[] │ └── mixed/ # items + groups ├── panel-data-full.example.json # JSON-сниппет (без страницы) ├── assets/ # SVG-иконки сервисов └── README.md ``` ## Конфигурация ### `settings.json` Указывает, откуда загружать данные панели. **Вариант 1 — загрузка из файла или URL:** ```json { "dataUrl": "./panel-data.json" } ``` Можно указать внешний адрес или любой из example-файлов: ```json { "dataUrl": "./panel-data-flat.example.json" } ``` **Вариант 2 — inline-объект в том же файле:** ```json { "dataUrl": "", "data": { "title": "Заголовок панели", "sections": [] } } ``` > Если `dataUrl` не пустой, он имеет **приоритет** над полем `data`. При ошибке загрузки или невалидном JSON на странице показывается экран с описанием проблемы. #### Пресет организации (`defaults` + `features`) В `settings.json` можно задать дефолтный вид панели для организации и ограничить, что пользователь может менять в ⚙. ```json { "dataUrl": "./panel-data.json", "defaults": { "navLayout": "sidebar", "theme": "dark", "view": "cards", "accent": "violet", "showNumbers": false, "showTreeTags": true, "treeTwoColumn": false, "autoCollapseSearch": true, "sidebarWidth": 390, "searchPlaceholder": "Grafana, S3, журнал, сервер..." }, "features": { "allowNavLayoutChange": true, "allowSearch": true, "allowViewChange": true, "allowShowNumbers": true, "allowTreeOptions": true, "allowSidebarResize": true, "allowThemeChange": true, "allowAccentChange": true } } ``` | `defaults` | Варианты | Описание | |------------|----------|----------| | `navLayout` | `"sidebar"`, `"top"` | `"sidebar"` — дерево слева; `"top"` — шапка с названием и поиском, без дерева | | `theme` | `"dark"`, `"light"` | Стартовая цветовая тема | | `view` | `"cards"`, `"list"`, `"compact-cards"`, `"compact-list"` | Режим отображения каталога | | `accent` | `"violet"`, `"cyan"`, `"lime"`, `"amber"`, `"custom"` | Пресет акцента; при `"custom"` используются поля градиента ниже | | `customAccentFrom` | `#rrggbb` | Цвет «от» для `accent: "custom"` (по умолчанию `#8b5cf6`) | | `customAccentTo` | `#rrggbb` | Цвет «до» для `accent: "custom"` (по умолчанию `#22d3ee`) | | `customAccentAngle` | `0`–`360` | Угол градиента в градусах (по умолчанию `135`) | | `showNumbers` | `true`, `false` | Показывать номера разделов, подразделов и элементов | | `showTreeTags` | `true`, `false` | Теги рядом с элементами в дереве (взаимоисключимо с `treeTwoColumn`) | | `treeTwoColumn` | `true`, `false` | Элементы папок дерева в 2 колонки (взаимоисключимо с `showTreeTags`) | | `autoCollapseSearch` | `true`, `false` | При поиске сворачивать ветки дерева без совпадений | | `sidebarWidth` | `260`–`560` (`760` при `treeTwoColumn: true`) | Ширина боковой панели в px; игнорируется при `navLayout: "top"` | | `searchPlaceholder` | произвольная строка | Текст-подсказка в поле поиска | | `features` | Варианты | Описание | |------------|----------|----------| | `allowNavLayoutChange` | `true`, `false` | Переключатель «Сбоку / Сверху» | | `allowSearch` | `true`, `false` | Поле поиска | | `allowViewChange` | `true`, `false` | Режимы каталога | | `allowShowNumbers` | `true`, `false` | Нумерация | | `allowTreeOptions` | `true`, `false` | Теги, 2 колонки, авто-раскрытие дерева | | `allowSidebarResize` | `true`, `false` | Drag-resize боковой панели | | `allowThemeChange` | `true`, `false` | Светлая / тёмная тема | | `allowAccentChange` | `true`, `false` | Пресеты и свой градиент | Пользовательские изменения сохраняются в `localStorage`, но заблокированные `features` всегда возвращаются к `defaults`. Пример пресета «только шапка»: [`settings-top-bar.example.json`](settings-top-bar.example.json) — `navLayout: "top"`, дерево и resize отключены. ### Форматы иерархии Панель поддерживает три базовых формата. Старый **section → group → item** работает без изменений. | Формат | Структура | Дерево | Каталог | |--------|-----------|--------|---------| | **Полная** | `sections[].groups[].items[]` | раздел → подраздел → файлы | collapsible разделы и подразделы | | **Раздел → item** | `sections[].items[]` | раздел → файлы | карточки внутри раздела | | **Плоский список** | корневой `items[]` | плоский список файлов | только сетка карточек | | **Смешанный** | `sections[].items[]` + `groups[]` | прямые файлы, затем подразделы | прямые карточки, затем подразделы | Минимум данных: корень должен содержать `sections` **или** `items`. Каждый раздел — `groups` **или** `items`. Каждый элемент — `title` и `type`. ### Схема `panel-data` Абстрактная структура файла данных (без привязки к конкретным сервисам): ```json { "title": "<заголовок панели>", "sections": [ { "id": "", "number": "<опционально>", "title": "<название раздела>", "description": "<опционально>", "items": [ { "id": "<стабильный-ключ>", "number": "<опционально>", "title": "<название>", "type": "<ключ из serviceMeta>", "tags": ["<тег>"], "url": "<ссылка>" } ], "groups": [ { "id": "", "number": "<опционально>", "title": "<название подраздела>", "description": "<опционально>", "items": ["<элементы — та же форма>"] } ] } ], "items": ["<альтернатива sections: плоский список>"], "serviceMeta": { "": { "label": "<подпись типа>", "icon": "assets/<иконка>.svg" } } } ``` Используй **либо** `sections`, **либо** корневой `items`. Внутри раздела — **либо** `groups`, **либо** `items`, **либо** оба. ### Поля данных | Уровень | Поля | Описание | |---------|------|----------| | **Корень** | `title` | Заголовок панели (sidebar + ``) | | | `sections` | Массив разделов (если нет корневого `items`) | | | `items` | Плоский список без разделов | | | `serviceMeta` | Дополнительные или переопределённые типы сервисов | | **section** | `id`, `title` | Обязательные идентификатор и название | | | `number`, `description` | Опционально | | | `groups`, `items` | Хотя бы одно из двух | | **group** | `id`, `title`, `items` | Подраздел с элементами | | | `number`, `description` | Опционально | | **item** | `title`, `type` | Обязательные название и тип | | | `url` | Ссылка; `"#"` — заглушка | | | `tags` | Массив строк для поиска | | | `id` | Стабильный ключ (DOM, фокус из дерева) | | | `number` | Отображаемый номер | | | `icon` | Опционально: свой путь к SVG (иначе — иконка из `type`) | - `type` — ключ из встроенного `defaultServiceMeta` или из `serviceMeta` в JSON; определяет иконку и подпись на карточке - `id` элемента нужен, если нет `number` — иначе ключ генерируется автоматически ### Примеры данных Готовые демо-страницы — в папке [`examples/`](examples/README.md). Корневые `panel-data-*.example.json` — те же структуры в виде JSON-сниппетов. ### Готовые демо-страницы (`examples/`) Каждый пример — отдельная страница с `index.html`, `settings.json` и `panel-data.json`. Общие ресурсы в `examples/shared/`. ```bash cd examples npx serve . ``` | Путь | Формат | |------|--------| | `/` | оглавление примеров | | `/full/` | section → group → item | | `/section-items/` | section → item | | `/flat/` | плоский `items[]` | | `/mixed/` | items + groups в разделе | Подробнее: [examples/README.md](examples/README.md). ### Типы сервисов (`serviceMeta`) Базовые типы заданы в `script.js` (`defaultServiceMeta`): `ceph`, `cloudbeaver`, `config`, `gitea`, `grafana`, `ipmi`, `kubernetes`, `ldap`, `messages`, `opensearch`, `postgresql`, `prometheus`, `rabbitmq`, `redmine`, `rules`, `xpath`, `xmpp` Каждый тип имеет собственную SVG в `assets/` (например, `postgresql.svg`, `cloudbeaver.svg`). Иконка показывается на карточке каталога и в дереве навигации. Переопределение или добавление типов в JSON: ```json { "serviceMeta": { "my-type": { "label": "Подпись", "icon": "assets/config.svg" } } } ``` ### Рекомендуемые типы сервисов Популярные `type`, которые можно добавить в `serviceMeta` (иконки — переиспользовать из `assets/`): | Категория | Типы (`type`) | Комментарий | |-----------|---------------|-------------| | **Прокси / балансировка** | `nginx`, `traefik` | reverse proxy, ingress | | **Контейнеры** | `docker`, `portainer`, `argocd` | runtime, UI, GitOps | | **CI/CD** | `jenkins`, `gitlab`, `harbor` | сборка, репозиторий, registry | | **Базы данных** | `mysql`, `mongodb`, `redis` | рядом с уже имеющимися `postgresql`, `opensearch` | | **Очереди / стриминг** | `kafka` | аналог `rabbitmq` | | **Мониторинг / логи** | `loki`, `zabbix`, `sentry` | логи, метрики, ошибки | | **Безопасность / IAM** | `vault`, `keycloak`, `consul` | секреты, SSO, service mesh | | **Поиск** | `elasticsearch`, `sonarqube` | полнотекст, качество кода | | **Хранилище** | `minio` | S3-совместимое | | **IaC** | `terraform` | инфраструктура как код | | **Трекеры** | `jira` | задачи, рядом с `redmine` | Чтобы подключить тип: 1. Добавь запись в `serviceMeta` вашего JSON **или** в `defaultServiceMeta` в `script.js` 2. При необходимости положи отдельную SVG в `assets/` 3. Укажи `"type": "nginx"` (и т.д.) в элементах ## Настройки интерфейса (⚙) Popup сгруппирован по разделам. Доступные блоки зависят от `features` в `settings.json`. Изменения пользователя сохраняются в `localStorage` (`ecosystem-panel-preferences`). | Раздел | Настройки | |--------|-----------| | **Макет** | Панель навигации: **Сбоку** (дерево + поиск) или **Сверху** (только название и поиск) | | **Каталог** | Вид карточек, нумерация | | **Дерево** | Теги, 2 колонки, авто-раскрытие поиска (только при `navLayout: sidebar`) | | **Оформление** | Тема, акцент, свой градиент | ### Режим «Сверху» Компактная шапка над каталогом: заголовок панели, поиск и ⚙. Дерево навигации скрыто — переход только через карточки каталога. Подходит для простых порталов и когда дерево избыточно. ### Свой градиент Кнопка **«Свой»** открывает настройки: - угол (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` (или свой JSON из `dataUrl`) 2. Найди нужный раздел / подраздел **или** добавь в `items` / корневой `items` 3. Добавь объект с полями `title`, `type`, при необходимости `tags`, `url`, `id`, `number` 4. Обнови страницу ### Добавить иконку и тип 1. Положи SVG в `assets/` (или переиспользуй существующую) 2. Зарегистрируй тип в `serviceMeta` JSON или в `defaultServiceMeta` в `script.js` ### Внешний источник данных ```json { "dataUrl": "https://your-server/panel-data.json" } ``` Убедись, что сервер отдаёт `Content-Type: application/json` и разрешает CORS при другом домене. ## Ограничения - Требуется HTTP-сервер (`fetch` не работает с `file://`) - Внешний `dataUrl` должен быть доступен по CORS - Настройки UI хранятся локально в браузере и не синхронизируются между устройствами ## Лицензия Приватный проект. Используй и изменяй по своему усмотрению.