universal-dashboard/README.md

732 lines
43 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Панель экосистемы
Статическая навигационная панель для быстрого доступа к сервисам инфраструктуры. Поддерживает гибкую иерархию данных, поиск, дерево навигации, несколько режимов отображения и настройку внешнего вида.
Стек: HTML, CSS, vanilla JavaScript. Без сборщиков и фреймворков.
## Оглавление
- [Возможности](#возможности)
- [Быстрый старт](#быстрый-старт)
- [Структура проекта](#структура-проекта)
- [Конфигурация](#конфигурация)
- [`settings.json`](#settingsjson)
- [Пресет организации (`defaults` + `features`)](#пресет-организации-defaults--features)
- [Форматы иерархии](#форматы-иерархии)
- [Схема `panel-data`](#схема-panel-data)
- [Поля данных](#поля-данных)
- [Примеры данных](#примеры-данных)
- [Готовые демо-страницы (`examples/`)](#готовые-демо-страницы-examples)
- [Типы сервисов (`serviceMeta`)](#типы-сервисов-servicemeta)
- [Рекомендуемые типы сервисов](#рекомендуемые-типы-сервисов)
- [Настройки интерфейса (⚙)](#настройки-интерфейса-)
- [Узоры панелей](#узоры-панелей)
- [Разделение слоёв узоров](#разделение-слоёв-узоров)
- [Свой узор (custom)](#свой-узор-custom)
- [Экспорт и импорт настроек](#экспорт-и-импорт-настроек)
- [Навигация по коду](#навигация-по-коду)
- [Поведение интерфейса](#поведение-интерфейса)
- [localStorage](#localstorage)
- [Разработка](#разработка)
- [Ограничения](#ограничения)
- [Лицензия](#лицензия)
## Возможности
- **Древовидная навигация** слева в стиле файлового менеджера
- **Каталог карточек** справа с collapsible-разделами
- **Поиск** по названию, тегам, типу сервиса и родительским разделам
- **4 режима отображения**: плитки, список, компактные плитки, компактный список
- **Темы**: светлая / тёмная, 4 пресета акцента + свой градиент
- **Узоры панелей** — 13 встроенных пресетов + свой URL/SVG; отдельно для nav / section / group / item; слайдеры прозрачности, высоты и перехода
- **Разделение слоёв** — подложка и blur, чтобы вложенные узоры не «наезжали» друг на друга
- **Экспорт / импорт / сброс** настроек ⚙ в JSON-файл
- **Настройки** через popup (иконка ⚙)
- **Изменяемая ширина** бокового меню и **высота** верхней шапки (drag, можно скрыть ручки)
- **Габариты** — лимиты ширины контента, меню и шапки (⚙, для администратора)
- **Колонки каталога** — число элементов в строке для каждого из 4 режимов отображения
- **Сохранение состояния** в `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",
"showTree": true,
"showResizeHandles": true,
"theme": "dark",
"view": "cards",
"accent": "violet",
"customAccentFrom": "#8b5cf6",
"customAccentTo": "#22d3ee",
"customAccentAngle": 135,
"panelPatternNav": "hex",
"panelPatternNavOpacity": 58,
"panelPatternNavHeight": 38,
"panelPatternNavFade": 18,
"panelPatternNavGradientOpacity": 62,
"panelPatternNavGradientHeight": 34,
"panelPatternNavGradientFade": 16,
"panelPatternSection": "hex",
"panelPatternSectionOpacity": 58,
"panelPatternSectionHeight": 38,
"panelPatternSectionFade": 18,
"panelPatternSectionGradientOpacity": 62,
"panelPatternSectionGradientHeight": 34,
"panelPatternSectionGradientFade": 16,
"panelPatternGroup": "none",
"panelPatternGroupOpacity": 58,
"panelPatternGroupHeight": 38,
"panelPatternGroupFade": 18,
"panelPatternGroupGradientOpacity": 62,
"panelPatternGroupGradientHeight": 34,
"panelPatternGroupGradientFade": 16,
"panelPatternItem": "none",
"panelPatternItemOpacity": 58,
"panelPatternItemHeight": 38,
"panelPatternItemFade": 18,
"panelPatternItemGradientOpacity": 62,
"panelPatternItemGradientHeight": 34,
"panelPatternItemGradientFade": 16,
"patternIsolation": 46,
"showNumbers": false,
"showTreeTags": true,
"showTreeType": false,
"treeMetaLayout": "inline",
"treeColumns": 1,
"autoCollapseSearch": true,
"sidebarWidth": 360,
"topBarHeight": 140,
"shellMaxWidth": 2240,
"sidebarMinWidth": 240,
"sidebarMaxWidth": 720,
"sidebarMaxWidthWide": 1040,
"treeSidebarBaseWidth": 360,
"treeSidebarColumnWidth": 96,
"topBarMinHeight": 80,
"topBarMaxHeight": 560,
"catalogCardsColumns": 3,
"catalogListColumns": 1,
"catalogCompactCardsColumns": 4,
"catalogCompactListColumns": 2,
"collapseAnimation": "animated",
"collapseDuration": 260,
"searchPlaceholder": "Grafana, S3, журнал, сервер...",
"settingsPanelWidth": 440
},
"limits": {
"shellMaxWidth": { "min": 1200, "max": 3840 },
"sidebarMinWidth": { "min": 220, "max": 480 },
"sidebarMaxWidth": { "min": 400, "max": 1200 },
"sidebarMaxWidthWide": { "min": 560, "max": 2000 },
"treeSidebarBaseWidth": { "min": 280, "max": 720 },
"treeSidebarColumnWidth": { "min": 60, "max": 200 },
"topBarMinHeight": { "min": 64, "max": 240 },
"topBarMaxHeight": { "min": 160, "max": 800 },
"catalogCardsColumns": { "min": 1, "max": 6 },
"catalogListColumns": { "min": 1, "max": 4 },
"catalogCompactCardsColumns": { "min": 2, "max": 8 },
"catalogCompactListColumns": { "min": 1, "max": 6 }
},
"features": {
"allowNavLayoutChange": true,
"allowShowTreeChange": true,
"allowSearch": true,
"allowViewChange": true,
"allowCatalogColumnsChange": true,
"allowShowNumbers": true,
"allowTreeOptions": true,
"allowSidebarResize": true,
"allowLayoutLimitsChange": true,
"allowThemeChange": true,
"allowAccentChange": true,
"allowPanelPatternChange": true
}
}
```
| `defaults` | Варианты | Описание |
|------------|----------|----------|
| `navLayout` | `"sidebar"`, `"top"` | `"sidebar"` — панель слева; `"top"` — шапка с названием и поиском |
| `showTree` | `true`, `false` | Показывать дерево навигации (в любом режиме макета) |
| `showResizeHandles` | `true`, `false` | Показывать ручки drag-resize меню / шапки |
| `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`) |
| `panelPatternNav` … `panelPatternItem` | см. [узоры панелей](#узоры-панелей) | Тип узора для каждого типа блоков (`none`, `hex`, …) |
| `panelPattern{Nav\|Section\|Group\|Item}Opacity` | `0`–`100` | Прозрачность узора в % (по умолчанию `58`) |
| `panelPattern{Nav\|Section\|Group\|Item}Height` | `0`–`100` | Высота узора в % (по умолчанию `38`; `0` — скрыть) |
| `panelPattern{Nav\|Section\|Group\|Item}Fade` | `0`–`100` | Ширина зоны затухания узора в % высоты блока: `0` — резкий обрыв на линии высоты, `100` — плавное затухание от верха (по умолчанию `18`) |
| `panelPattern{Nav\|Section\|Group\|Item}GradientOpacity` | `0`–`100` | Прозрачность акцентного градиента в % (по умолчанию `62`) |
| `panelPattern{Nav\|Section\|Group\|Item}GradientHeight` | `0`–`100` | Высота градиента в % (по умолчанию `34`) |
| `panelPattern{Nav\|Section\|Group\|Item}GradientFade` | `0`–`100` | Зона затухания градиента (по умолчанию `16`) |
| `panelPattern{Nav\|Section\|Group\|Item}CustomImage` | строка URL | Изображение для `custom` (пусто по умолчанию) |
| `panelPattern{Nav\|Section\|Group\|Item}CustomSize` | `"WxH"` в px | Размер плитки custom (по умолчанию `32px 32px`) |
| `patternIsolation` | `0`–`100` | Разделение слоёв узоров (подложка + blur; по умолчанию `46`) |
| `showNumbers` | `true`, `false` | Показывать номера разделов, подразделов и элементов |
| `showTreeTags` | `true`, `false` | Теги элементов в дереве (оформление как на карточках) |
| `showTreeType` | `true`, `false` | Подпись типа сервиса в дереве |
| `treeMetaLayout` | `"inline"`, `"stacked"` | Расположение тегов и типа: сбоку от названия или снизу |
| `treeColumns` | `1`–`6` (по умолчанию `1`) | Число колонок элементов внутри папок дерева |
| `autoCollapseSearch` | `true`, `false` | При поиске сворачивать ветки дерева без совпадений |
| `sidebarWidth` | `sidebarMinWidth`–`sidebarMaxWidth` (или `sidebarMaxWidthWide` при `treeColumns` > 1) | Текущая ширина бокового меню (drag-resize); при `navLayout: "top"` не применяется |
| `topBarHeight` | `topBarMinHeight`–`topBarMaxHeight` (до ~75% высоты окна) | Текущая высота верхней шапки (drag-resize); только при `navLayout: "top"` |
| `shellMaxWidth` | см. `limits.shellMaxWidth` | Максимальная ширина контейнера `.shell` (вся страница) |
| `sidebarMinWidth` | см. `limits.sidebarMinWidth` | Минимальная ширина бокового меню при drag-resize |
| `sidebarMaxWidth` | см. `limits.sidebarMaxWidth` | Максимальная ширина меню при `treeColumns: 1` |
| `sidebarMaxWidthWide` | см. `limits.sidebarMaxWidthWide` | Потолок ширины меню при нескольких колонках дерева |
| `treeSidebarBaseWidth` | см. `limits.treeSidebarBaseWidth` | База формулы ширины меню: `base + columns × step` |
| `treeSidebarColumnWidth` | см. `limits.treeSidebarColumnWidth` | Прирост ширины меню за каждую колонку дерева |
| `topBarMinHeight` | см. `limits.topBarMinHeight` | Минимальная высота шапки в режиме «Сверху» |
| `topBarMaxHeight` | см. `limits.topBarMaxHeight` | Максимальная высота шапки (также ограничена ~75% окна) |
| `catalogCardsColumns` | см. `limits.catalogCardsColumns` | Колонок в строке в режиме «Плитки» |
| `catalogListColumns` | см. `limits.catalogListColumns` | Колонок в строке в режиме «Список» |
| `catalogCompactCardsColumns` | см. `limits.catalogCompactCardsColumns` | Колонок в строке в режиме «Компакт плитки» |
| `catalogCompactListColumns` | см. `limits.catalogCompactListColumns` | Колонок в строке в режиме «Компакт список» |
| `collapseAnimation` | `"instant"`, `"animated"` | Режим раскрытия списков, дерева и collapsible-блоков |
| `collapseDuration` | `100`–`800` | Длительность плавного раскрытия в миллисекундах (по умолчанию `260`) |
| `searchPlaceholder` | произвольная строка | Текст-подсказка в поле поиска |
| `settingsPanelWidth` | `limits.settingsPanelWidth` (по умолчанию `320`–`720`) | Ширина popup настроек в px (по умолчанию `440`) |
| `limits` | Формат | Описание |
|----------|--------|----------|
| `shellMaxWidth` | `{ "min", "max" }` | Диапазон слайдера «Ширина контента» в ⚙ → Габариты |
| `sidebarMinWidth` | `{ "min", "max" }` | Диапазон «Меню: мин. ширина» |
| `sidebarMaxWidth` | `{ "min", "max" }` | Диапазон «Меню: макс. (1 кол.)» |
| `sidebarMaxWidthWide` | `{ "min", "max" }` | Диапазон «Меню: макс. (неск. кол.)» |
| `treeSidebarBaseWidth` | `{ "min", "max" }` | Диапазон «Дерево: база ширины» |
| `treeSidebarColumnWidth` | `{ "min", "max" }` | Диапазон «Дерево: ширина колонки» |
| `topBarMinHeight` | `{ "min", "max" }` | Диапазон «Шапка: мин. высота» |
| `topBarMaxHeight` | `{ "min", "max" }` | Диапазон «Шапка: макс. высота» |
| `catalogCardsColumns` | `{ "min", "max" }` | Диапазон колонок каталога «Плитки» |
| `catalogListColumns` | `{ "min", "max" }` | Диапазон колонок каталога «Список» |
| `catalogCompactCardsColumns` | `{ "min", "max" }` | Диапазон колонок «Компакт плитки» |
| `catalogCompactListColumns` | `{ "min", "max" }` | Диапазон колонок «Компакт список» |
При `treeColumns > 1` фактический потолок ширины меню: `min(sidebarMaxWidthWide, treeSidebarBaseWidth + treeColumns × treeSidebarColumnWidth)`.
`shellMaxWidth` автоматически не опускается ниже `sidebarMaxWidthWide + 320`, чтобы оставалось место для каталога.
| `features` | Варианты | Описание |
|------------|----------|----------|
| `allowNavLayoutChange` | `true`, `false` | Переключатель «Сбоку / Сверху» |
| `allowShowTreeChange` | `true`, `false` | Toggle «Показывать дерево» в ⚙ → Макет |
| `allowSearch` | `true`, `false` | Поле поиска |
| `allowViewChange` | `true`, `false` | Режимы каталога (плитки / список / компакт) |
| `allowCatalogColumnsChange` | `true`, `false` | Слайдеры «колонок в строке» для каждого вида каталога |
| `allowShowNumbers` | `true`, `false` | Нумерация |
| `allowTreeOptions` | `true`, `false` | Колонки дерева (1–6), теги, тип, расположение мета, авто-раскрытие |
| `allowSidebarResize` | `true`, `false` | Drag-resize и toggle «Ручки изменения размера» |
| `allowLayoutLimitsChange` | `true`, `false` | Раздел ⚙ «Габариты»: лимиты ширины контента и навигации |
| `allowThemeChange` | `true`, `false` | Светлая / тёмная тема |
| `allowAccentChange` | `true`, `false` | Пресеты и свой градиент |
| `allowPanelPatternChange` | `true`, `false` | Выбор узора панелей в ⚙ → Оформление |
Пользовательские изменения сохраняются в `localStorage`, но заблокированные `features` всегда возвращаются к `defaults`.
Пример пресета «шапка сверху»: [`settings-top-bar.example.json`](settings-top-bar.example.json) — `navLayout: "top"`, дерево по умолчанию скрыто (`showTree: false`), но его можно включить.
### Форматы иерархии
Панель поддерживает три базовых формата. Старый **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": "<slug-раздела>",
"number": "<опционально>",
"title": "<название раздела>",
"description": "<опционально>",
"items": [
{
"id": "<стабильный-ключ>",
"number": "<опционально>",
"title": "<название>",
"type": "<ключ из serviceMeta>",
"tags": ["<тег>"],
"url": "<ссылка>"
}
],
"groups": [
{
"id": "<slug-подраздела>",
"number": "<опционально>",
"title": "<название подраздела>",
"description": "<опционально>",
"items": ["<элементы — та же форма>"]
}
]
}
],
"items": ["<альтернатива sections: плоский список>"],
"serviceMeta": {
"<type-key>": {
"label": "<подпись типа>",
"icon": "assets/<иконка>.svg"
}
}
}
```
Используй **либо** `sections`, **либо** корневой `items`. Внутри раздела — **либо** `groups`, **либо** `items`, **либо** оба.
### Поля данных
| Уровень | Поля | Описание |
|---------|------|----------|
| **Корень** | `title` | Заголовок панели (sidebar + `<title>`) |
| | `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`):
`analytics`, `api`, `calendar`, `ceph`, `chat`, `cicd`, `cloudbeaver`, `config`, `drive`, `forum`, `gitea`, `grafana`, `ipmi`, `kubernetes`, `ldap`, `messages`, `opensearch`, `postgresql`, `prometheus`, `rabbitmq`, `redmine`, `rules`, `social`, `vault`, `video`, `vpn`, `wiki`, `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`).
| Раздел | Настройки |
|--------|-----------|
| **Макет** | **Сбоку** / **Сверху**; toggle «Показывать дерево»; toggle «Ручки изменения размера»; слайдер «Ширина настроек» |
| **Габариты** | Лимиты ширины контента, меню и шапки (сворачиваемый блок; при `allowLayoutLimitsChange`) |
| **Каталог** | Вид карточек, колонок в строке для каждого вида, нумерация |
| **Дерево** | Колонок 1–6, теги, тип, расположение мета, авто-раскрытие (при `showTree: true`) |
| **Оформление** | Тема, акцент, узоры по типам блоков, разделение слоёв, свой градиент |
| **(footer)** | Сброс, экспорт и импорт всех preferences в JSON |
### Режим «Сверху»
Компактная шапка: заголовок, поиск и ⚙. Дерево по умолчанию скрыто, но его можно включить (`showTree: true`) — оно появится под шапкой на всю ширину.
### Скрытие дерева
Дерево управляется отдельно от макета (`showTree`). Переключение — в ⚙ → **Макет**.
### Свой градиент
Кнопка **«Свой»** открывает настройки:
- угол (0–360°)
- цвет «от» и «до»
Значения сохраняются в `localStorage`.
### Узоры панелей
Настраиваются в ⚙ → **Оформление** (если `allowPanelPatternChange: true`).
#### Четыре цели (targets)
У каждого типа блока свой независимый набор: пресет, слайдеры и (опционально) свой URL.
| Цель | Preference (префикс) | DOM-элемент | Градиент под узором |
|------|----------------------|-------------|---------------------|
| Панель навигации | `panelPatternNav*` | `.hero-content` | да |
| Раздел | `panelPatternSection*` | `.catalog-section` | да |
| Подраздел | `panelPatternGroup*` | `.catalog-group` | да |
| Элемент | `panelPatternItem*` | `.nav-card` | нет (только узор) |
#### Встроенные пресеты (`panelPattern*`)
| id | Подпись в ⚙ | Описание |
|----|-------------|----------|
| `none` | Нет | Без повторяющейся текстуры |
| `hex` | Соты | Крупные гексагоны |
| `hex-sm` | Соты м. | Мелкие гексагоны |
| `dots` | Точки | Средняя точечная сетка |
| `dots-sm` | Точки м. | Мелкие кружки |
| `dots-lg` | Точки кр. | Крупные кружки |
| `grid` | Сетка | Линейная сетка |
| `diagonal` | Диаг. | Диагональные полосы |
| `cross` | Клетка | Перекрёстная штриховка |
| `mesh` | Mesh | Точки цвета акцента |
| `tri-sm` | △ мел. | Мелкие треугольники |
| `tri-lg` | △ кр. | Крупные треугольники |
| `diamond` | Ромбы | Ромбовидная сетка |
| `custom` | Свой | Своя текстура (см. ниже) |
По умолчанию: **навигация** и **разделы** — `hex`; **подразделы** и **элементы** — `none`.
#### Слайдеры (для каждой цели)
| Сuffix preference | UI | Диапазон | По умолчанию |
|-------------------|-----|----------|--------------|
| `Opacity` | Узор: прозрачность | 0–100 % | 58 |
| `Height` | Узор: высота | 0–100 % высоты блока | 38 |
| `Fade` | Узор: переход | 0–100 % | 18 |
| `GradientOpacity` | Градиент: прозрачность | 0–100 % | 62 |
| `GradientHeight` | Градиент: высота | 0–100 % | 34 |
| `GradientFade` | Градиент: переход | 0–100 % | 16 |
**Переход (fade):** `0%` — резкий обрыв на линии «высоты»; большее значение — плавнее затухание к низу блока.
Для цели **Элемент** в UI только три слайдера узора (без градиента).
#### Как узор попадает на экран
1. `preferences` → `applyPanelPatternPreferences()` в `script.js`
2. На `<html>`: `data-panel-pattern-nav="hex"` (и section / group / item)
3. CSS-переменные: `--panel-pattern-{target}-opacity`, `-height`, `-fade-start`, `--panel-gradient-{target}-*`
4. Встроенные пресеты: `styles.css` → `--pattern-tile-*` и селекторы `html[data-panel-pattern-*="…"]`
5. Слои на блоке: `::before` (градиент), `::after` (текстура с mask по высоте/переходу)
Кнопки пресетов в ⚙ **не** захардкожены в HTML: контейнеры `data-pattern-swatches="nav|section|group|item"`, список — `panelPatternCatalog` в `script.js`.
### Разделение слоёв узоров
Слайдер **«Разделение слоёв»** в ⚙ → Оформление (`patternIsolation`, 0–100 %, по умолчанию `46`).
Когда узоры включены на нескольких уровнях вложенности (раздел → подраздел → карточка), текстуры могут визуально накладываться. Настройка добавляет на `.catalog-section`, `.catalog-group` и `.nav-card`:
- полупрозрачную подложку (`--panel-layer-fill`);
- `backdrop-filter: blur(...)` (`--panel-layer-blur`).
Так каждый уровень «отрезает» узоры снизу и остаётся читаемой граница. Значение задаётся в `settings.json` → `defaults.patternIsolation` и сохраняется в `localStorage`.
### Свой узор (custom)
1. В панели нужной цели выберите пресет **«Свой»**
2. Появится блок с полями:
- **Изображение** — `panelPattern{Nav|Section|Group|Item}CustomImage`
- **Размер плитки** — `panelPattern*CustomSize` (например `32px 32px`)
Допустимые форматы URL:
- `data:image/svg+xml,…` или `data:image/png;base64,…`
- `https://…` / `http://…`
- относительный путь: `./assets/my-pattern.svg`, `assets/pattern.png`
- `javascript:` — **отклоняется** при нормализации
Пример минимального SVG (data URI):
```
data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Ccircle cx='8' cy='8' r='2' fill='white' fill-opacity='.2'/%3E%3C/svg%3E
```
При `custom` без URL узор не рисуется (opacity = 0), пока не задано изображение.
### Экспорт и импорт настроек
Внизу popup ⚙ — три кнопки:
| Кнопка | Действие |
|--------|----------|
| **Сбросить** | Подтверждение → `panelDefaults` из `settings.json`, очистка `ecosystem-panel-preferences` |
| **Экспортировать** | Скачивание `ecosystem-panel-preferences.json` |
| **Импортировать** | Выбор JSON; применение с `normalizePreferences` + `enforceFeatureLocks` |
Формат экспорта:
```json
{
"version": 1,
"exportedAt": "2026-06-29T12:00:00.000Z",
"preferences": { "theme": "dark", "panelPatternNav": "hex", "patternIsolation": 46 }
}
```
Импорт принимает и обёртку с `preferences`, и «голый» объект настроек.
### Раскрытие списков
В ⚙ → **Каталог** — режим **Резко** / **Плавно** и длительность (100–800 ms). Плавное раскрытие применяется к разделам и подразделам каталога, дереву навигации и схлопывающимся блокам в настройках. Кнопки сворачивания — chevron в стеклянной рамке с акцентной подсветкой при наведении.
| Preference | Значения | По умолчанию |
|------------|----------|--------------|
| `collapseAnimation` | `"instant"`, `"animated"` | `"animated"` |
| `collapseDuration` | `100`–`800` (ms) | `240` |
Старые ключи (`panelPattern`, `panelPatternOnNav`, …) при загрузке автоматически мигрируют в per-target формат, если в `localStorage` ещё нет `panelPatternNav`.
### Навигация по коду
Быстрый поиск: в `script.js` и `styles.css` — секции с разделителем `// =====` / `/* =====`; в `index.html` — комментарий-оглавление в начале файла.
#### script.js
| Секция | Содержимое |
|--------|------------|
| `Panel pattern catalog and defaults` | `panelPatternCatalog`, `availablePanelPatterns`, defaults по целям |
| `Panel pattern preferences` | normalize, apply, custom URL, UI (`renderPatternSwatches`, …) |
| `Settings import, export and reset` | `applyStoredPreferences`, export/import JSON |
| `Preferences application` | `applyPreferences`, sync контролов ⚙ |
| `Collapse state handling` | анимация раскрытия details |
Ключевые функции узоров: `applyPanelPatternPreferences`, `applyPatternIsolationPreference`, `getPanelPatternPreferenceKey`, `setupPatternControls`.
#### styles.css
| Секция | Содержимое |
|--------|------------|
| `01. Theme Tokens` | `--pattern-tile-*`, mapping `html[data-panel-pattern-*]`, `--panel-layer-*` |
| `05. Sidebar Panel` | `::before` / `::after` узоров, layer isolation на catalog/group/card |
| `06. Settings Popup` | `.pattern-swatches`, `.pattern-custom-panel`, `.settings-actions` |
#### index.html
| Блок | Атрибуты / классы |
|------|-------------------|
| Popup ⚙ | `[data-settings-panel]`, секции `[data-settings-section="…"]` |
| Узоры | `[data-pattern-targets-panel]`, `[data-pattern-swatches="{target}"]` |
| Разделение слоёв | `[data-pattern-isolation]` |
| Экспорт | `[data-settings-reset]`, `[data-settings-export]`, `[data-settings-import]` |
#### Добавить новый встроенный пресет
1. Добавить id в `availablePanelPatterns` и запись в `panelPatternCatalog` (`script.js`)
2. Определить `--pattern-tile-{id}-image` и `-size` в `:root` (`styles.css`)
3. Добавить строки `html[data-panel-pattern-{nav|section|group|item}="{id}"]` для всех четырёх целей
4. Добавить превью `.pattern-swatch[data-panel-pattern="{id}"]::before` в секции настроек
5. При необходимости — override SVG для `html[data-theme="light"]`
Значения сохраняются в `localStorage` и выставляют `data-panel-pattern-*` на `<html>` и CSS-переменные `--panel-pattern-{nav|section|group|item}-*`, `--panel-gradient-{target}-*`.
## Поведение интерфейса
### Поиск
Фильтрует каталог и подсвечивает совпадения в дереве и карточках. Ищет по:
- названию элемента
- номеру
- тегам
- типу сервиса
- названиям родительских разделов
### Дерево навигации
- Клик по **папке** — раскрытие/сворачивание
- Клик по **названию** — прокрутка к элементу в каталоге
- Состояние раскрытия сохраняется отдельно для дерева и контента (`ecosystem-panel-collapse-state`)
### Адаптив
- **Touch-устройства** (`pointer: coarse` / `hover: none`): stacked-layout, меню сверху, ручки resize скрыты
- **Мышь** (`pointer: fine` + `hover: hover`): sidebar + resizer + каталог в колонках
- На узких экранах число колонок каталога дополнительно ограничивается в CSS (чтобы сетка не ломалась)
### Ширина и габариты layout
Лимиты задаются в `settings.json` → `defaults` и `limits`, настраиваются пользователем в ⚙ → **Габариты** (если `allowLayoutLimitsChange: true`):
- **`shellMaxWidth`** — максимальная ширина всей панели (по умолчанию до 2800px)
- **`sidebarMaxWidth`** / **`sidebarMaxWidthWide`** — потолок ширины меню (1 колонка дерева / несколько)
- **`topBarMinHeight`** / **`topBarMaxHeight`** — диапазон высоты шапки в режиме «Сверху»
- Текущие **`sidebarWidth`** и **`topBarHeight`** — drag-resize; сохраняются в `localStorage`
## localStorage
| Ключ | Содержимое |
|------|------------|
| `ecosystem-panel-preferences` | Тема, акцент, узоры, вид, колонки, `patternIsolation`, custom-URL, ширина sidebar, export/import |
| `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 хранятся локально в браузере и не синхронизируются между устройствами
## Лицензия
Приватный проект. Используй и изменяй по своему усмотрению.