universal-dashboard/README.md

419 lines
21 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)
- [Рекомендуемые типы сервисов](#рекомендуемые-типы-сервисов)
- [Настройки интерфейса (⚙)](#настройки-интерфейса-)
- [Поведение интерфейса](#поведение-интерфейса)
- [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": "<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`):
`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 хранятся локально в браузере и не синхронизируются между устройствами
## Лицензия
Приватный проект. Используй и изменяй по своему усмотрению.