21 KiB
Панель экосистемы
Статическая навигационная панель для быстрого доступа к сервисам инфраструктуры. Поддерживает гибкую иерархию данных, поиск, дерево навигации, несколько режимов отображения и настройку внешнего вида.
Стек: HTML, CSS, vanilla JavaScript. Без сборщиков и фреймворков.
Оглавление
- Возможности
- Быстрый старт
- Структура проекта
- Конфигурация
- Настройки интерфейса (⚙)
- Поведение интерфейса
- localStorage
- Разработка
- Ограничения
- Лицензия
Возможности
- Древовидная навигация слева в стиле файлового менеджера
- Каталог карточек справа с collapsible-разделами
- Поиск по названию, тегам, типу сервиса и родительским разделам
- 4 режима отображения: плитки, список, компактные плитки, компактный список
- Темы: светлая / тёмная, 4 пресета акцента + свой градиент
- Настройки через popup (иконка ⚙)
- Изменяемая ширина бокового меню (drag)
- Сохранение состояния в
localStorage(настройки UI, раскрытие веток) - Конфигурация через JSON — локальный объект или загрузка по URL
- Гибкая иерархия: полная вложенность, раздел без подраздела, плоский список
Быстрый старт
Панель использует fetch, поэтому её нужно открывать через HTTP-сервер, а не напрямую как file://.
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:
{
"dataUrl": "./panel-data.json"
}
Можно указать внешний адрес или любой из example-файлов:
{
"dataUrl": "./panel-data-flat.example.json"
}
Вариант 2 — inline-объект в том же файле:
{
"dataUrl": "",
"data": {
"title": "Заголовок панели",
"sections": []
}
}
Если
dataUrlне пустой, он имеет приоритет над полемdata.
При ошибке загрузки или невалидном JSON на странице показывается экран с описанием проблемы.
Пресет организации (defaults + features)
В settings.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 — 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
Абстрактная структура файла данных (без привязки к конкретным сервисам):
{
"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/. Корневые panel-data-*.example.json — те же структуры в виде JSON-сниппетов.
Готовые демо-страницы (examples/)
Каждый пример — отдельная страница с index.html, settings.json и panel-data.json. Общие ресурсы в examples/shared/.
cd examples
npx serve .
| Путь | Формат |
|---|---|
/ |
оглавление примеров |
/full/ |
section → group → item |
/section-items/ |
section → item |
/flat/ |
плоский items[] |
/mixed/ |
items + groups в разделе |
Подробнее: 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:
{
"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 |
Чтобы подключить тип:
- Добавь запись в
serviceMetaвашего JSON или вdefaultServiceMetaвscript.js - При необходимости положи отдельную SVG в
assets/ - Укажи
"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 |
Раскрытые/свернутые ветки дерева и каталога |
Разработка
Добавить элемент
- Открой
panel-data.json(или свой JSON изdataUrl) - Найди нужный раздел / подраздел или добавь в
items/ корневойitems - Добавь объект с полями
title,type, при необходимостиtags,url,id,number - Обнови страницу
Добавить иконку и тип
- Положи SVG в
assets/(или переиспользуй существующую) - Зарегистрируй тип в
serviceMetaJSON или вdefaultServiceMetaвscript.js
Внешний источник данных
{
"dataUrl": "https://your-server/panel-data.json"
}
Убедись, что сервер отдаёт Content-Type: application/json и разрешает CORS при другом домене.
Ограничения
- Требуется HTTP-сервер (
fetchне работает сfile://) - Внешний
dataUrlдолжен быть доступен по CORS - Настройки UI хранятся локально в браузере и не синхронизируются между устройствами
Лицензия
Приватный проект. Используй и изменяй по своему усмотрению.