Files
test-x6/ARCHITECTURE.md
Alina ef816cdcf4 feat: frontend MVP — детальная схема связей устройств (AntV X6)
- React 18 + TypeScript strict + AntV X6 2.x + AntD 5 + Zustand
- Custom nodes: SiteNode, CrossDeviceNode, SpliceNode, DeviceNode, CardNode
- 8-слойный автолейаут, порты (left/right), линии с цветами по статусу
- Toolbar, дерево навигации, карточка объекта, таблица соединений
- Контекстные меню, легенда, drag линий/нод, создание линий из портов
- Моковые данные: 3 сайта, 10 устройств, 15 линий

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 22:02:25 +03:00

24 KiB
Raw Permalink Blame History

Test-X6: Детальная схема связей устройства — Архитектура

Обзор проекта

Веб-приложение для визуализации и редактирования детальных схем связей сетевых устройств. Основано на библиотеке AntV X6 для рендеринга интерактивных графов. Позволяет отображать сайты, устройства, карты, порты и линии связи с полной поддержкой вложенности, автоматической раскладки и многопользовательского редактирования.


Технологический стек

Слой Технологии
Frontend React 18, TypeScript, AntD 5, AntV X6 2.x, Vite
Backend NestJS, TypeORM, PostgreSQL
Инфраструктура Docker, k3s, Drone CI, Harbor
Линтинг ESLint, Prettier, tsc strict mode

Структура монорепозитория

test-x6/
├── frontend/                    # React-приложение
│   ├── src/
│   │   ├── api/                 # API-клиент, типы запросов/ответов
│   │   ├── components/          # Общие UI-компоненты
│   │   │   ├── Layout/          # Общий layout приложения
│   │   │   ├── Toolbar/         # Панель инструментов
│   │   │   ├── SidePanel/       # Левая и правая боковые панели
│   │   │   └── ConnectionsPanel/# Нижняя панель соединений
│   │   ├── features/
│   │   │   └── schema/          # Основной модуль схемы
│   │   │       ├── graph/       # Инициализация и конфигурация X6 Graph
│   │   │       ├── nodes/       # Custom Nodes (Site, Cross, Splice, Device, Card)
│   │   │       ├── ports/       # Логика портов (контейнеры, сортировка, drag)
│   │   │       ├── edges/       # Линии (стили, группировка, перемычки)
│   │   │       ├── layout/      # Автолейаут (8 слоёв)
│   │   │       ├── context-menu/# Контекстные меню
│   │   │       ├── selection/   # Выделение (rectangle, lasso)
│   │   │       ├── locking/     # Блокировка / concurrency
│   │   │       └── export/      # Экспорт PNG, Excel
│   │   ├── hooks/               # React-хуки
│   │   ├── store/               # Zustand store
│   │   ├── types/               # TypeScript типы и интерфейсы
│   │   ├── utils/               # Утилиты
│   │   ├── constants/           # Константы (цвета, размеры, слои)
│   │   ├── App.tsx
│   │   └── main.tsx
│   ├── index.html
│   ├── vite.config.ts
│   ├── tsconfig.json
│   ├── .eslintrc.cjs
│   └── package.json
├── backend/                     # NestJS API
│   ├── src/
│   │   ├── modules/
│   │   │   ├── sites/           # CRUD сайтов
│   │   │   ├── devices/         # CRUD устройств (активных и пассивных)
│   │   │   ├── cards/           # CRUD карт
│   │   │   ├── ports/           # CRUD портов
│   │   │   ├── lines/           # CRUD линий связи
│   │   │   ├── schema/          # Сборка и отдача схемы, сохранение layout
│   │   │   ├── locks/           # Блокировки редактирования
│   │   │   └── export/          # Экспорт (PNG-метаданные, Excel)
│   │   ├── common/              # Guards, interceptors, filters, pipes
│   │   ├── database/            # TypeORM конфигурация, миграции
│   │   ├── app.module.ts
│   │   └── main.ts
│   ├── tsconfig.json
│   ├── .eslintrc.cjs
│   └── package.json
├── CLAUDE.md
├── ARCHITECTURE.md
└── docker-compose.yml

Архитектура Frontend

Граф (X6 Graph)

Центральный компонент — SchemaCanvas, обёртка над @antv/x6 Graph. Инициализация:

SchemaCanvas
├── useGraphInit()          — создание экземпляра Graph, плагины (Selection, Snapline, MiniMap, Keyboard)
├── useGraphData()          — загрузка данных, маппинг в ноды/эджи X6
├── useGraphInteraction()   — обработчики событий (click, dblclick, contextmenu, drag)
└── useGraphLayout()        — автоматическая раскладка (8 слоёв)

Custom Nodes

Каждый тип ноды — отдельный класс, наследующий Shape.Rect (или Shape.HTML для сложных случаев):

Нода Класс Особенности
SiteNode Shape.Rect + custom markup Контейнер с шапкой (чёрный фон, 4 строки), вложенность (embedding), динамический размер
CrossDeviceNode Shape.Rect + custom attrs Асимметричные скругления (rx/ry по углам через <path>), пропорция 1:3, L/S порты
SpliceClosureNode Shape.Rect Квадрат 98×98, border-radius 6px
OtherDeviceNode Shape.Rect Адаптивный размер под количество портов
CardNode Shape.Rect (embedded) Вложенный в устройство, асимметричный контур (верх/низ 1.5px, бока 5px), условное отображение

Система портов

// Двухконтейнерная архитектура портов
interface PortContainerConfig {
  left: PortGroup;   // Левый контейнер (ЛК)
  right: PortGroup;  // Правый контейнер (ПК)
}

Логика распределения:

  1. При загрузке — определение стороны на основе относительного положения связанных устройств
  2. При перемещении устройства — node:move событие, пересчёт пересечения границ, автопереброс портов
  3. Ручной drag — пользователь перетаскивает порт между ЛК и ПК

Линии (Edges)

// Конфигурация линии
interface EdgeConfig {
  style: EdgeLineStyle;        // solid, dashed, dotted (Appendix B)
  color: string;               // по статусу (Appendix A)
  medium: 'optical' | 'copper' | 'wireless' | 'unknown';
  router: 'orth' | 'normal';  // ломаная / прямая
  grouped: boolean;            // входит ли в группу
}

Группировка: при >1 линии между парой устройств — рендеринг одной линии с badge-счётчиком. Тултип по hover — разбивка по статусам.

Автолейаут (8 слоёв)

// Маппинг типов устройств на слои
const LAYER_MAPPING: Record<number, DeviceType[]> = {
  1: ['cross_optical', 'rrl', 'wireless', 'satellite'],
  2: ['tspu', 'dwdm'],
  3: ['men', 'sdh', 'multiservice_platform'],
  4: ['ip', 'optical_modem', 'optical_mux', 'lan_wlan'],
  5: ['ran_controller', 'mgn', 'mgx', 'server', 'sorm', 'mob', 'fix'],
  6: ['voip', 'xdsl', 'pdh'],
  7: ['ran_base_station', 'unknown', 'video_surveillance'],
  8: [], // overflow from layer 1 (>6 cross) + copper cross
};

Алгоритм:

  1. Определить тип каждого устройства → маппинг в слой
  2. Проверить overflow (слой 1, >6 кроссов → перенос в слой 8)
  3. Рассчитать Y-координаты слоёв (пустые — схлопнуть)
  4. Внутри слоя — расположить устройства в ряд с отступами
  5. Применить позиции

State Management (Zustand)

SchemaStore
├── graph: Graph | null                  — экземпляр X6 Graph
├── data: SchemaData                     — текущие данные схемы
├── mode: 'view' | 'edit'               — режим работы
├── selectedElements: string[]           — выделенные элементы
├── clipboard: ClipboardData | null      — буфер обмена
├── displaySettings: DisplaySettings     — настройки отображения
├── locks: LockInfo[]                    — информация о блокировках
├── portSettings: Record<string, PortSettings>  — пользовательские настройки портов
└── layoutPositions: Record<string, Position>   — сохранённые позиции

Архитектура Backend

API Endpoints

# Схема
GET    /api/schema/:deviceId          — получить детальную схему устройства
PUT    /api/schema/:deviceId/layout   — сохранить layout (позиции, порты, карты)

# Сайты
GET    /api/sites                     — список сайтов (с фильтрацией)
GET    /api/sites/:id                 — карточка сайта
POST   /api/sites                     — создать сайт
PUT    /api/sites/:id                 — обновить сайт
DELETE /api/sites/:id                 — удалить сайт

# Устройства
GET    /api/devices                   — список устройств (фильтры: тип, сайт)
GET    /api/devices/:id               — карточка устройства
POST   /api/devices                   — создать устройство
PUT    /api/devices/:id               — обновить устройство
DELETE /api/devices/:id               — удалить устройство
POST   /api/devices/:id/copy          — копирование (body: { withConnections: boolean })
PUT    /api/devices/:id/move-site     — перенос на другой сайт

# Карты
GET    /api/devices/:deviceId/cards   — карты устройства
PUT    /api/cards/:id/visibility      — показать/скрыть карту

# Порты
GET    /api/devices/:deviceId/ports   — порты устройства
PUT    /api/ports/order               — сохранить порядок портов
PUT    /api/ports/:id/settings        — настройки порта (цвет, сторона)

# Линии
GET    /api/lines                     — линии (фильтры: device, site)
GET    /api/lines/:id                 — карточка линии
POST   /api/lines                     — создать линию
PUT    /api/lines/:id                 — обновить линию
DELETE /api/lines/:id                 — удалить линию
POST   /api/lines/:id/break          — разрыв линии
POST   /api/lines/:id/copy           — копирование линии

# Блокировки
POST   /api/locks                     — заблокировать элементы
DELETE /api/locks/:id                 — разблокировать
GET    /api/locks/schema/:deviceId    — текущие блокировки на схеме

# Экспорт
GET    /api/export/excel/:deviceId    — экспорт в Excel

Модель данных (TypeORM Entities)

┌──────────────────────┐
│        Site           │
├──────────────────────┤
│ id: UUID              │
│ name: string          │
│ address: string       │
│ erpCode: string       │
│ code1C: string        │
│ status: SiteStatus    │
│ parentSiteId: UUID?   │
│ ──────────────────── │
│ devices: Device[]     │
│ childSites: Site[]    │
└──────────────────────┘
         │ 1:N
         ▼
┌──────────────────────┐
│       Device          │
├──────────────────────┤
│ id: UUID              │
│ name: string          │
│ networkName: string   │
│ ipAddress: string     │
│ marking: string       │
│ id1: string           │
│ id2: string           │
│ type: DeviceType      │
│ group: DeviceGroup    │  // 'active' | 'passive' | 'cross' | 'splice'
│ category: string      │  // для маппинга в слой
│ status: DeviceStatus  │
│ siteId: UUID          │
│ ──────────────────── │
│ cards: Card[]         │
│ ports: Port[]         │
│ site: Site            │
└──────────────────────┘
         │ 1:N
         ▼
┌──────────────────────┐
│        Card           │
├──────────────────────┤
│ id: UUID              │
│ slotName: string      │
│ networkName: string   │
│ status: CardStatus    │
│ visible: boolean      │
│ deviceId: UUID        │
│ ──────────────────── │
│ ports: Port[]         │
│ device: Device        │
└──────────────────────┘
         │ 1:N
         ▼
┌──────────────────────┐
│        Port           │
├──────────────────────┤
│ id: UUID              │
│ name: string          │
│ slotName: string      │
│ side: 'left' | 'right'│
│ sortOrder: number     │
│ labelColor: string    │
│ deviceId: UUID        │
│ cardId: UUID?         │
│ ──────────────────── │
│ device: Device        │
│ card: Card?           │
│ linesA: Line[]        │
│ linesZ: Line[]        │
└──────────────────────┘

┌──────────────────────┐
│        Line           │
├──────────────────────┤
│ id: UUID              │
│ name: string          │
│ templateName: string  │
│ status: LineStatus    │
│ type: LineType        │  // simple | complex
│ medium: Medium        │  // optical | copper | wireless | unknown
│ lineStyle: LineStyle  │  // solid | dashed | dotted
│ portAId: UUID         │
│ portZId: UUID         │
│ ──────────────────── │
│ portA: Port           │
│ portZ: Port           │
│ fibers: Fiber[]       │  // для сложных линий
└──────────────────────┘

┌──────────────────────┐
│       Fiber           │
├──────────────────────┤
│ id: UUID              │
│ name: string          │
│ status: FiberStatus   │
│ lineId: UUID          │
│ portAId: UUID         │
│ portZId: UUID         │
└──────────────────────┘

┌──────────────────────┐
│   SchemaLayout        │
├──────────────────────┤
│ id: UUID              │
│ userId: UUID          │
│ deviceId: UUID        │  // центральное устройство схемы
│ positions: jsonb      │  // { nodeId: {x, y, w, h} }
│ portOrders: jsonb     │  // { deviceId: { portId: sortOrder } }
│ cardVisibility: jsonb │  // { cardId: boolean }
│ settings: jsonb       │  // настройки отображения
│ updatedAt: Date       │
└──────────────────────┘

┌──────────────────────┐
│     EditLock          │
├──────────────────────┤
│ id: UUID              │
│ schemaDeviceId: UUID  │  // схема которую заблокировали
│ userId: UUID          │
│ userName: string      │
│ lockedAt: Date        │
│ expiresAt: Date       │  // автоистечение через N минут
└──────────────────────┘

Enums и цветовая карта

// Статусы (для цветовой карты — Appendix A)
enum EntityStatus {
  Active = 'active',
  Planned = 'planned',
  UnderConstruction = 'under_construction',
  Reserved = 'reserved',
  Faulty = 'faulty',
  Decommissioned = 'decommissioned',
  Unknown = 'unknown',
}

// Типы устройств (для маппинга в слои)
enum DeviceCategory {
  CrossOptical = 'cross_optical',
  CrossCopper = 'cross_copper',
  RRL = 'rrl',
  Wireless = 'wireless',
  Satellite = 'satellite',
  TSPU = 'tspu',
  DWDM = 'dwdm',
  MEN = 'men',
  SDH = 'sdh',
  MultiservicePlatform = 'multiservice_platform',
  IP = 'ip',
  OpticalModem = 'optical_modem',
  OpticalMux = 'optical_mux',
  LanWlan = 'lan_wlan',
  RanController = 'ran_controller',
  MGN = 'mgn',
  MGX = 'mgx',
  Server = 'server',
  SORM = 'sorm',
  MOB = 'mob',
  FIX = 'fix',
  VOIP = 'voip',
  xDSL = 'xdsl',
  PDH = 'pdh',
  RanBaseStation = 'ran_base_station',
  Unknown = 'unknown',
  VideoSurveillance = 'video_surveillance',
}

// Группы устройств
enum DeviceGroup {
  Active = 'active',
  Passive = 'passive',
}

// Среда передачи
enum Medium {
  Optical = 'optical',
  Copper = 'copper',
  Wireless = 'wireless',
  Unknown = 'unknown',
}

Ключевые архитектурные решения

1. Рендеринг нод: SVG vs HTML

  • Сайт (SiteNode): SVG Shape.Rect с кастомным markup. Шапка — <foreignObject> для текстового переноса.
  • Кросс: SVG <path> для асимметричного скругления.
  • Карты и порты: SVG элементы, вложенные через X6 embedding (parent-child).

2. Вложенность (Embedding)

X6 нативно поддерживает embedding:

graph.options.embedding = {
  enabled: true,
  findParent: 'bbox', // или кастомная функция
};
  • Сайт → устройства: device.setParent(site)
  • Устройство → карты: card.setParent(device)
  • Карта → порты: port.setParent(card) (через X6 ports API)

3. Группировка линий

Реализуется на уровне рендеринга:

  1. При загрузке данных — группировка линий по парам (deviceA, deviceZ)
  2. Если count > 1 → рендеринг одной «групповой» линии с label-счётчиком
  3. По клику «Разгруппировать» → замена на N отдельных линий с разбивкой по статусам

4. Автолейаут

Кастомный алгоритм (не dagre/elk):

  1. Парсинг устройств сайта → распределение по 8 слоям
  2. Расчёт геометрии слоёв (Y offset, высота по максимальному элементу)
  3. Размещение устройств внутри слоя (X offset, горизонтальный ряд)
  4. Схлопывание пустых слоёв
  5. Позиционирование дочерних сайтов по нижней границе родителя

5. Блокировка / Concurrency

  • При переходе в режим редактирования → POST /api/locks (pessimistic lock)
  • Блокировка с TTL (auto-expire через 30 минут неактивности)
  • Heartbeat каждые 60 секунд для продления блокировки
  • Другие пользователи видят значок блокировки (overlay на нодах)
  • При сохранении → DELETE /api/locks/:id

6. Сохранение состояния

  • Позиции, порядок портов, видимость карт — JSON в таблице schema_layout
  • Привязка к паре (userId, deviceId)у каждого пользователя свой layout
  • При отсутствии сохранённого layout — применяется автолейаут

Зависимости Frontend

Пакет Назначение
@antv/x6 Основная библиотека графов
@antv/x6-plugin-selection Выделение (rectangle, rubberband)
@antv/x6-plugin-snapline Привязка к сетке
@antv/x6-plugin-minimap Мини-карта
@antv/x6-plugin-keyboard Горячие клавиши
@antv/x6-plugin-clipboard Буфер обмена
@antv/x6-plugin-history Undo/Redo
@antv/x6-plugin-export Экспорт PNG
antd UI-компоненты (меню, модалки, таблицы, формы)
zustand State management
axios HTTP-клиент
xlsx Экспорт Excel

Фазы реализации

Фаза 1: Каркас и базовые ноды

  • Инициализация проекта (frontend + backend)
  • Настройка X6 Graph с базовыми плагинами
  • Custom Nodes: SiteNode, OtherDeviceNode (простые)
  • Базовые Entity и CRUD (sites, devices)
  • Загрузка и отображение статической схемы

Фаза 2: Порты и линии

  • Система портов (двухконтейнерная, сортировка)
  • CardNode с вложенными портами
  • Линии с привязкой к портам
  • Стили линий по статусу и среде передачи
  • CrossDeviceNode, SpliceClosureNode

Фаза 3: Интерактивность

  • Контекстные меню (6 типов)
  • Drag-and-drop устройств
  • Создание линий (4 способа)
  • Выделение (rectangle, lasso)
  • Автолейаут (8 слоёв)

Фаза 4: Панели и UI

  • Toolbar с настройками
  • Левая панель (дерево навигации)
  • Правая панель (карточки объектов)
  • Нижняя панель соединений

Фаза 5: Режимы работы и persistence

  • Режим просмотра / редактирования
  • Блокировки (concurrency)
  • Сохранение layout (позиции, порты, карты)
  • Копирование/вставка со связями
  • Разрыв линии

Фаза 6: Группировка, экспорт, полировка

  • Группировка линий (счётчик, тултип, разгруппировка)
  • Экспорт PNG и Excel
  • Перемычки (self-loops)
  • Snap-to-grid, MiniMap
  • Тестирование, оптимизация производительности