# Панель экосистемы Статическая навигационная панель для быстрого доступа к сервисам инфраструктуры. Поддерживает гибкую иерархию данных, поиск, дерево навигации, несколько режимов отображения и настройку внешнего вида. Стек: 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, можно скрыть ручки) - **Габариты** — лимиты ширины контента, меню и шапки (⚙, для администратора) - **Колонки каталога** — число элементов в строке для каждого из 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": 70, "panelPatternNavHeight": 45, "panelPatternNavGradientOpacity": 80, "panelPatternNavGradientHeight": 40, "panelPatternSection": "hex", "panelPatternSectionOpacity": 70, "panelPatternSectionHeight": 45, "panelPatternSectionGradientOpacity": 80, "panelPatternSectionGradientHeight": 40, "panelPatternGroup": "none", "panelPatternGroupOpacity": 70, "panelPatternGroupHeight": 45, "panelPatternGroupGradientOpacity": 80, "panelPatternGroupGradientHeight": 40, "panelPatternItem": "none", "panelPatternItemOpacity": 70, "panelPatternItemHeight": 45, "panelPatternItemGradientOpacity": 80, "panelPatternItemGradientHeight": 40, "showNumbers": false, "showTreeTags": true, "showTreeType": false, "treeMetaLayout": "inline", "treeColumns": 2, "autoCollapseSearch": true, "sidebarWidth": 390, "topBarHeight": 140, "shellMaxWidth": 2800, "sidebarMinWidth": 260, "sidebarMaxWidth": 800, "sidebarMaxWidthWide": 1400, "treeSidebarBaseWidth": 420, "treeSidebarColumnWidth": 120, "topBarMinHeight": 80, "topBarMaxHeight": 560, "catalogCardsColumns": 3, "catalogListColumns": 1, "catalogCompactCardsColumns": 5, "catalogCompactListColumns": 3, "searchPlaceholder": "Grafana, S3, журнал, сервер...", "settingsPanelWidth": 460 }, "limits": { "shellMaxWidth": { "min": 1200, "max": 3840 }, "sidebarMinWidth": { "min": 200, "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` | Прозрачность узора в % (по умолчанию `70`) | | `panelPattern{Nav\|Section\|Group\|Item}Height` | `0`–`100` | Высота узора в % (по умолчанию `45`; `0` — скрыть) | | `panelPattern{Nav\|Section\|Group\|Item}Fade` | `0`–`100` | Ширина зоны затухания узора в % высоты блока: `0` — резкий обрыв на линии высоты, `100` — плавное затухание от верха (по умолчанию `10`) | | `panelPattern{Nav\|Section\|Group\|Item}GradientOpacity` | `0`–`100` | Прозрачность акцентного градиента в % (по умолчанию `80`) | | `panelPattern{Nav\|Section\|Group\|Item}GradientHeight` | `0`–`100` | Высота градиента в % (по умолчанию `40`) | | `panelPattern{Nav\|Section\|Group\|Item}GradientFade` | `0`–`100` | Зона затухания градиента (по умолчанию `8`) | | `showNumbers` | `true`, `false` | Показывать номера разделов, подразделов и элементов | | `showTreeTags` | `true`, `false` | Теги элементов в дереве (оформление как на карточках) | | `showTreeType` | `true`, `false` | Подпись типа сервиса в дереве | | `treeMetaLayout` | `"inline"`, `"stacked"` | Расположение тегов и типа: сбоку от названия или снизу | | `treeColumns` | `1`–`6` (по умолчанию `2`) | Число колонок элементов внутри папок дерева | | `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` | Колонок в строке в режиме «Компакт список» | | `searchPlaceholder` | произвольная строка | Текст-подсказка в поле поиска | | `settingsPanelWidth` | `limits.settingsPanelWidth` (по умолчанию `320`–`720`) | Ширина popup настроек в px (по умолчанию `460`) | | `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": "", "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`): `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`) | | **Оформление** | Тема, акцент, узор панелей, свой градиент | ### Режим «Сверху» Компактная шапка: заголовок, поиск и ⚙. Дерево по умолчанию скрыто, но его можно включить (`showTree: true`) — оно появится под шапкой на всю ширину. ### Скрытие дерева Дерево управляется отдельно от макета (`showTree`). Переключение — в ⚙ → **Макет**. ### Свой градиент Кнопка **«Свой»** открывает настройки: - угол (0–360°) - цвет «от» и «до» Значения сохраняются в `localStorage`. ### Узоры панелей Настраиваются в ⚙ → **Оформление** (если `allowPanelPatternChange: true`): четыре схлопывающиеся панели — **Панель навигации**, **Раздел**, **Подраздел**, **Элемент**. В каждой — свой тип узора и слайдеры. | Значение `panelPattern*` | Описание | |--------------------------|----------| | `none` | Без узора — только акцентный градиент (если применим) | | `hex` | Соты | | `dots` | Точечная сетка | | `grid` | Линейная сетка | | `diagonal` | Диагональные полосы | | `cross` | Перекрёстная штриховка | | `mesh` | Точки цвета акцента | По умолчанию для **навигации** и **разделов** — `hex`; для **подразделов** и **элементов** — `none`. | Панель | Preference (префикс) | DOM | |--------|----------------------|-----| | Панель навигации | `panelPatternNav*` | `.hero-content` | | Раздел | `panelPatternSection*` | `.catalog-section` | | Подраздел | `panelPatternGroup*` | `.catalog-group` | | Элемент | `panelPatternItem*` | `.nav-card` — только узор, без акцентного градиента | В каждой панели (кроме «Элемент») — шесть слайдеров: прозрачность, высота и **переход** узора; то же для градиента. Для «Элемент» — три слайдера узора (без градиента). **Переход** задаёт ширину зоны затухания: `0%` — резкий обрыв на линии «высоты», большее значение — более плавное исчезновение (при значении ≥ высоты узор растворяется от самого верха блока). Старые ключи (`panelPattern`, `panelPatternOnNav`, …) при загрузке автоматически мигрируют в per-target формат, если в `localStorage` ещё нет `panelPatternNav`. Значения сохраняются в `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` | Тема, акцент, узор, вид, колонки каталога, ширина 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 хранятся локально в браузере и не синхронизируются между устройствами ## Лицензия Приватный проект. Используй и изменяй по своему усмотрению.