diff --git a/DEVOPS.md b/DEVOPS.md new file mode 100644 index 0000000..0a35470 --- /dev/null +++ b/DEVOPS.md @@ -0,0 +1,278 @@ +# РАЗРАБОТКА И ВНЕДРЕНИЕ ОТКАЗОУСТОЙЧИВОЙ МУЛЬТИТЕНАНТНОЙ ИНФРАСТРУКТУРЫ ДЛЯ СИСТЕМЫ УПРАВЛЕНИЯ УНИВЕРСИТЕТСКИМ РАСПИСАНИЕМ «МАГИСТР» + +**Диссертационное исследование и отчет о проделанной инженерной работе в качестве DevOps-архитектора проекта** + +--- + +## ВВЕДЕНИЕ + +Современные информационные системы для образовательных учреждений требуют строгого соблюдения требований к безопасности, изоляции данных различных организаций (университетов), гибкости масштабирования и высокой наблюдаемости (observability). В рамках разработки проекта **«Магистр»** — системы управления расписанием учебных занятий — передо мной встала задача проектирования и построения инфраструктуры, способной обслуживать десятки университетов в рамках единого прикладного контура при условии жесткой изоляции их баз данных. + +В качестве DevOps-инженера проекта я спроектировал и реализовал архитектурное решение, объединяющее технологии аппаратной виртуализации, оркестрации контейнеров, автоматизации конфигурации, распределенного мониторинга и непрерывной интеграции. В данном документе подробно описаны теоретические предпосылки, практическая реализация и архитектурные решения, внедренные мной в продакшн-окружение проекта. + +--- + +## РАЗДЕЛ 1. СИСТЕМНАЯ АРХИТЕКТУРА И КОНЦЕПЦИЯ МУЛЬТИТЕНАНТНОСТИ + +### 1.1 Выбор паттерна изоляции данных +При проектировании мультитенантных (multi-tenant) систем классически выделяют три подхода к организации баз данных: +1. **Shared Database & Shared Schema**: Все клиенты используют общие таблицы, записи разделяются по полю `tenant_id`. Подход дешев в обслуживании, но несет колоссальные риски утечки данных из-за ошибок в SQL-запросах приложения и не позволяет разграничивать физический доступ к базам. +2. **Shared Database & Separate Schemas**: Одна СУБД, но разные логические схемы для каждого клиента. Обеспечивает умеренную изоляцию, но сохраняет единую точку отказа и общие аппаратные ресурсы СУБД. +3. **Database-per-Tenant (Выбранный подход)**: Каждый клиент (университет) владеет собственной физически изолированной базой данных, расположенной на выделенном сервере или виртуальной машине. + +Для проекта «Магистр» мной был выбран и реализован паттерн **Database-per-Tenant**. Это обусловлено спецификой клиентов (высшие учебные заведения), которые согласно законодательству (например, ФЗ-152 «О персональных данных») обязаны хранить свои данные локально или требовать полной изоляции конфиденциальной информации. Кроме того, данный подход позволяет: +* Исключить влияние высокой нагрузки одного университета (например, в период составления сессий) на доступность системы для других вузов («синдром шумного соседа»). +* Выполнять индивидуальное резервное копирование и обслуживание БД каждого университета без остановки всей платформы. +* Переносить базы данных вузов на их собственные физические серверы в локальных сетях (on-premise), сохраняя при этом работу приложения в центральном облаке. + +### 1.2 Архитектурная схема движения трафика +Ниже представлена разработанная мной схема прохождения сетевых запросов и агрегации телеметрии в системе: + +```mermaid +graph TD + Client[Пользовательский браузер] -->|HTTPS: tenant.zuev.company| Caddy[Caddy Reverse Proxy] + Caddy -->|HTTP Round-Robin| Ingress[Traefik Ingress Controller] + + subgraph K3s Cluster + Ingress -->|Route /*| Frontend[Frontend Pods x2 Apache] + Ingress -->|Route /api/*| Backend[Backend Pods x1-x2 Spring Boot] + Backend -->|K8s API: Get/Patch CM| K8sAPI[Kubernetes API Server] + BackendConfig[ConfigMap: tenants-config] -.->|Mounted JSON| Backend + end + + subgraph Proxmox VE Infrastructure + Backend -->|JDBC Connection| VM1[(VM 1: DBMS MSU
PostgreSQL 16)] + Backend -->|JDBC Connection| VM2[(VM 2: DBMS SWSU
PostgreSQL 16)] + + VM1 -.->|Metrics OTLP/gRPC| Collector1[OTel Collector VM1] + VM2 -.->|Metrics OTLP/gRPC| Collector2[OTel Collector VM2] + end + + subgraph Observability Hub + Backend -->|Logs & Traces OTLP| SigNoz[SigNoz APM Server] + Collector1 -->|Postgres Metrics| SigNoz + Collector2 -->|Postgres Metrics| SigNoz + end + + classDef cluster fill:#e1f5fe,stroke:#01579b,stroke-width:2px; + classDef vm fill:#efebe9,stroke:#4e342e,stroke-width:2px; + classDef monitor fill:#efe8ff,stroke:#512da8,stroke-width:2px; + class K3s Cluster cluster; + class Proxmox VE Infrastructure vm; + class Observability Hub monitor; +``` + +--- + +## РАЗДЕЛ 2. АППАРАТНАЯ ВИРТУАЛИЗАЦИЯ НА БАЗЕ PROMVOX VE + +Для размещения баз данных клиентов и вспомогательных инфраструктурных служб мной был развернут гипервизор **Proxmox Virtual Environment (PVE)** на выделенных серверных мощностях. + +### 2.1 Конфигурация виртуализации и изоляции ресурсов +Каждая база данных университета разворачивается в выделенной виртуальной машине (KVM), что гарантирует жесткую изоляцию на уровне ядра ОС и гипервизора. +* **Сетевой уровень**: В Proxmox настроена виртуальная коммутация (Linux Bridge `vmbr0`). Сетевые интерфейсы виртуальных машин баз данных вынесены в приватную подсеть, изолированную от внешней сети. Доступ к ним имеет только подсеть кластера Kubernetes и управляющая машина администратора. +* **Дисковая подсистема**: Использовано локальное хранилище на базе ZFS с зеркалированием (RAID-10 на SSD-накопителях). Это обеспечивает: + * Высокую скорость операций ввода-вывода (IOPS), критически важную для СУБД при транзакционных нагрузках. + * Механизм мгновенных снимков (snapshots) для безопасного проведения обновлений. + * Аппаратное сжатие данных (LZ4) без потери производительности, снижающее износ накопителей. +* **Резервное копирование**: Интегрирован **Proxmox Backup Server (PBS)**. Каждую ночь выполняются инкрементальные бэкапы виртуальных машин с дедупликацией на уровне блоков данных, что минимизирует нагрузку на сеть и диски, позволяя восстановить любую БД на любой момент времени за последние 30 дней. + +--- + +## РАЗДЕЛ 3. ОРКЕСТРАЦИЯ КОНТЕЙНЕРНОЙ ИНФРАСТРУКТУРЫ В KUBERNETES (K3s) + +В качестве платформы оркестрации приложений был выбран **K3s** — сертифицированный дистрибутив Kubernetes от Rancher, оптимизированный под средние и малые нагрузки, обладающий минимальными накладными расходами на RAM/CPU для Control Plane, но сохраняющий полную совместимость с upstream-спецификациями Kubernetes API. + +### 3.1 Архитектура манифестов и структура ресурсов +Мной была разработана декларативная структура ресурсов, развернутая в изолированном пространстве имен `magistr` (манифест `namespace.yaml`): + +* **Frontend (`frontend.yaml`)**: + * Реализован в виде `Deployment` с 2 репликами для обеспечения высокой доступности (HA) и возможности бесшовного обновления rolling-update. + * В качестве базового образа контейнера применен легковесный веб-сервер Apache HTTPd (`httpd:alpine`). + * Для балансировки и внутреннего доступа настроен `Service` типа ClusterIP, слушающий порт 80. + +* **Backend (`backend.yaml`)**: + * `Deployment` с 1-2 репликами (детали балансировки конфигурации описаны ниже). + * Для сборки образов используется multi-stage сборка Maven (JDK 17) и запуск под управлением `eclipse-temurin:17-jre-alpine`. + * Интегрирован Java-агент OpenTelemetry для автоматического инструментирования трассировки и логов. + * Для связи с Ingress настроен ClusterIP-сервис на порту 8080. + +* **Ingress (`ingress.yaml`)**: + * В роли Ingress-контроллера выступает стандартный для K3s **Traefik**. + * Мной настроены строгие правила маршрутизации по доменным именам (например, `magistr.zuev.company` для тестового окружения, `n8n.zuev.company` и т.д.). Маршрутизация по путям разделяет трафик: запросы к API (`/api`) проксируются на бэкенд-сервис, а запросы к статическим ресурсам и страницам (`/`) — на фронтенд-сервис. + +### 3.2 Реализация динамической мультитенантности через K8s API +Одним из наиболее сложных этапов проектирования стала организация бесшовного добавления новых университетов без перезапуска бэкенда и изменения исходного кода приложения. + +1. **Хранение конфигурации**: + Список подключений к базам данных университетов хранится в JSON-формате (`tenants.json`) внутри Kubernetes `ConfigMap` с именем `tenants-config`. + +2. **Проблема Read-Only монтирования**: + По умолчанию Kubernetes монтирует ConfigMap как файловую систему в режиме Read-Only. Это исключало возможность для Java-приложения перезаписывать `tenants.json` при запросах от администратора на добавление нового тенанта. + +3. **Решение с использованием `initContainer` и `emptyDir`**: + Я спроектировал схему с временным перезаписываемым томом (`emptyDir`): + * В спецификацию пода backend добавлен `initContainer` на базе образа `busybox`. + * При старте пода `initContainer` монтирует ConfigMap `tenants-config` и копирует файл `tenants.json` во временный раздел `emptyDir` (путь `/config`). + * Основной контейнер `backend` монтирует этот же `emptyDir` в режиме Read-Write. + +4. **K8s RBAC и динамическое сохранение**: + Чтобы предоставить бэкенду возможность сохранять изменения конфигурации обратно в кластер, мной были написаны манифесты авторизации (`rbac.yaml`): + * Создан `ServiceAccount` `backend-sa` и назначен поду бэкенда. + * Описана Kubernetes `Role` `backend-configmap-role`, дающая права только на операции `get` и `patch` для ресурса `configmaps` с конкретным именем `tenants-config` (принцип наименьших привилегий). + * Создан `RoleBinding` `backend-configmap-binding`, связывающий сервис-аккаунт с этой ролью. + + В коде бэкенда класс `ConfigMapUpdater` через Kubernetes Java Client отправляет PATCH-запрос к API-серверу при добавлении нового тенанта, обновляя ConfigMap в самом кластере. + +5. **Репликация и синхронизация подов**: + Для обеспечения консистентности данных в условиях работы нескольких реплик бэкенда: + * Класс `TenantConfigWatcher` запускает фоновую задачу, которая каждые 30 секунд сверяет MD5-хеш локального файла `tenants.json` с версией из ConfigMap. + * При обнаружении расхождения файл перезаписывается, DataSource-инстансы пересоздаются в памяти приложения на лету, исключая рассинхронизацию подов. + * *Примечание*: В ходе тестирования для минимизации задержек и исключения эффекта "мигания" данных на время отладки количество реплик backend было ограничено до 1, с последующим переходом на полноценный StatefulSet/Shared Storage при росте количества нод. + +### 3.3 Повышение отказоустойчивости бэкенда (Resiliency) +При запуске бэкенда в облаке возникала проблема: если база данных одного из университетов становилась недоступной (например, из-за сетевого сбоя или регламентных работ), пул подключений HikariCP падал с ошибкой при инициализации бинов, отправляя под бэкенда в циклическую перезагрузку (`CrashLoopBackOff`), что делало недоступной систему для *всех* остальных вузов. + +Для предотвращения этого каскадного сбоя я внедрил комплекс защитных механизмов: +* **H2 Fallback**: В сборку (`pom.xml`) добавлена СУБД H2 in-memory. Если при старте ConfigMap пуст, Spring Boot инициализирует пустой JPA-контекст на базе H2, что позволяет приложению успешно пройти фазу запуска. +* **HikariCP Resiliency**: В конфигурации источников данных задано свойство `setInitializationFailTimeout(-1)`. Это заставляет HikariCP игнорировать отсутствие связи с целевой БД при старте приложения, продолжая попытки подключения в фоновом режиме. +* **Явное указание диалекта Hibernate**: Принудительно задан `org.hibernate.dialect.PostgreSQLDialect` в `TenantDataSourceConfig.java`. Это избавило Hibernate от необходимости выполнять тестовый запрос к БД для автоматического определения диалекта на ранних этапах инициализации контекста. +* **Автоматическое создание таблиц (`DataInitializer`)**: Для новых БД вузов полностью автоматизирован процесс наката структуры. При обнаружении новой базы Java-код проверяет наличие таблицы `users` и, в случае ее отсутствия, самостоятельно применяет SQL-скрипт инициализации (`init.sql`), включая создание дефолтных справочников и учетной записи суперадминистратора. + +--- + +## РАЗДЕЛ 4. АВТОМАТИЗАЦИЯ КОНФИГУРАЦИИ СУБД С ПОМОЩЬЮ ANSIBLE + +Для развертывания и администрирования баз данных университетов на удаленных виртуальных машинах Proxmox мной был спроектирован и реализован комплексный плейбук **Ansible** с модульной структурой ролей. + +### 4.1 Структура и функционал Ansible-ролей +Вся конфигурация целевого сервера разбита на 6 последовательных этапов (плейбук `site.yml`): + +1. **`common` (Базовая подготовка и Hardening ОС)**: + * Создание системного пользователя `deploy` с беспарольным доступом к `sudo` (через валидацию файла `/etc/sudoers.d/deploy` утилитой `visudo`). + * Установка системных утилит (`curl`, `git`, `htop`, `chrony` для синхронизации времени по NTP). + * **SSH Hardening**: Изменение стандартного SSH-порта на `2222`, отключение авторизации по паролям, запрет входа для пользователя `root`, ограничение таймаута сессий. + * Адаптация под различные дистрибутивы ОС: написаны отдельные сценарии для семейств Debian/Ubuntu/Astra Linux (пакетный менеджер `apt`), RedHat/РЕД ОС (`dnf`) и ALT Linux (кастомная обертка для `apt-get`). + +2. **`docker` (Среда контейнеризации)**: + * Автоматическое добавление официальных GPG-ключей и репозиториев Docker CE. + * Установка Docker Engine, CLI и Compose плагина. + * Включение системной службы `docker` и добавление пользователя `deploy` в группу `docker` для беспарольного запуска контейнеров. + +3. **`firewall` (Сетевая безопасность)**: + * Настройка межсетевого экрана UFW (для Debian-based систем) или firewalld (для RedHat и ALT Linux). + * Полная блокировка входящего трафика по умолчанию. + * Разрешение входящих пакетов только на порт SSH (`2222`) и порт PostgreSQL (`5432`). Реализована поддержка ограничения доступа к порту `5432` только для IP-адресов нод Kubernetes-кластера (`firewall_postgresql_allowed_ips`). + +4. **`postgresql` (Контейнеризированная СУБД)**: + * Генерация файлов конфигурации `docker-compose.yml` и `.env` на основе шаблонов Jinja2. + * Развертывание PostgreSQL 16 на базе легковесного Alpine-образа. + * Физическое монтирование каталога данных БД с хост-системы (`/opt/magistr/postgres/data`), предотвращающее потерю данных при пересоздании контейнера. + * Ограничение системных ресурсов контейнера (limits: memory: 512M) и тюнинг параметров PostgreSQL (`shared_buffers=256MB`, оптимизация пула соединений, логирование медленных запросов `log_min_duration_statement=1000`). + +5. **`backup` (Резервное копирование на уровне БД)**: + * Установка на хост автоматического bash-скрипта бэкапа. + * Настройка задачи `cron` (запуск ежедневно в 03:00). + * Скрипт осуществляет горячий дамп базы через `docker exec pg_dump`, архивирует его с помощью `gzip` и сохраняет в локальный каталог `/opt/magistr/backups/`. + * Реализован алгоритм автоматической ротации: удаление архивных файлов старше заданного количества дней (по умолчанию 14 дней). + +6. **`monitoring` (Сбор телеметрии хоста и БД)**: + * Развертывание контейнера OpenTelemetry Collector на целевом сервере БД. + * Конфигурирование коллектора на сбор метрик производительности PostgreSQL и ОС (процессор, память, диски) и их отправку на единый сервер SigNoz по OTLP/HTTP. + +### 4.2 Управление секретами: Ansible Vault +Для исключения попадания паролей администраторов БД в систему контроля версий (Git) все чувствительные переменные (например, `vault_postgresql_password`) вынесены в файл `group_vars/databases/vault.yml` и зашифрованы алгоритмом AES-256 с помощью **Ansible Vault**. + +Для бесшовной интеграции в CI/CD пароль расшифрования считывается из локального файла `.vault_pass` на управляющей машине, доступ к которому ограничен правами `chmod 600`. + +--- + +## РАЗДЕЛ 5. НЕПРЕРЫВНАЯ ИНТЕГРАЦИЯ И ДОСТАВКА (CI/CD) + +В рамках оптимизации внутренних процессов разработки и деплоя мной была развернута и настроена инфраструктура непрерывной интеграции на базе **Gitea** и **Gitea Actions**. + +### 5.1 Автоматизация сборки (CI) +В репозитории проекта создан workflow-манифест `.gitea/workflows/docker-build.yaml`. При каждом пуше изменений в ветку `main` запускается конвейер: +1. **Checkout**: Загрузка актуального исходного кода проекта на ранер. +2. **Setup Buildx**: Инициализация Docker Buildx для оптимизации кэширования слоев. +3. **Login to Registry**: Аутентификация во встроенном реестре контейнеров Gitea Container Registry (`git.zuev.company`) с использованием сервисного токена `ZUEV_TOKEN` (права `write:package`). +4. **Build & Push**: Параллельная сборка Docker-образов для бэкенда и фронтенда с тегом `latest` и отправка их в приватный реестр. + +### 5.2 Доставка в кластер (CD) +Для авторизации нод K3s в приватном реестре Gitea мной был создан секрет типа `docker-registry` в пространстве имен `magistr`: +```bash +kubectl create secret docker-registry gitea-registry \ + --docker-server=gitea.zuev.company \ + --docker-username=Zuev \ + --docker-password=${ZUEV_TOKEN} \ + --namespace=magistr +``` +Этот секрет ассоциирован со спецификациями деплоев в `backend.yaml` и `frontend.yaml` через директиву `imagePullSecrets`. Обновление приложений в кластере после завершения сборки образов выполняется путем контролируемого перезапуска подов: +```bash +kubectl rollout restart deployment backend -n magistr +kubectl rollout restart deployment frontend -n magistr +``` + +--- + +## РАЗДЕЛ 6. ВХОДНОЙ ПРОКСИ-СЕРВЕР НА БАЗЕ CADDY PROXY + +Для маршрутизации внешнего трафика и защиты соединений на входе в инфраструктуру развернут веб-сервер **Caddy**. + +### 6.1 Преимущества Caddy и управление SSL/TLS +В отличие от классического Nginx, Caddy был выбран благодаря: +* Встроенной интеграции с удостоверяющими центрами Let's Encrypt и ZeroSSL. При добавлении нового поддомена вуза (например, `swsu.zuev.company`) Caddy автоматически запрашивает, валидирует через DNS/HTTP-вызовы и устанавливает TLS-сертификат, а также следит за его продлением. +* Лаконичному синтаксису конфигурации (`Caddyfile`). +* Полноценной поддержке протокола HTTP/3 «из коробки». + +### 6.2 Конфигурация балансировки +Внешний Caddy-сервер принимает запросы ко всем поддоменам `*.zuev.company` и перенаправляет их на ноды кластера K3s, выполняя роль внешнего балансировщика нагрузки (L4/L7 Load Balancer): +```caddyfile +*.zuev.company { + reverse_proxy 192.168.1.104:80 192.168.1.105:80 192.168.1.106:80 { + lb_policy round_robin + lb_try_duration 5s + lb_try_interval 250ms + } +} +``` +Сетевые запросы распределяются по нодам K3s по алгоритму Round-Robin. В случае недоступности одной из нод, Caddy временно исключает ее из пула, обеспечивая отказоустойчивость инфраструктуры на сетевом уровне. + +--- + +## РАЗДЕЛ 7. СКВОЗНОЙ МОНИТОРИНГ И ОБСЕРВАБИЛИТИ (SigNoz + OpenTelemetry) + +Для контроля здоровья системы и оперативного выявления аномалий мной была спроектирована и внедрена централизованная система мониторинга на базе APM-платформы **SigNoz** и стандартов **OpenTelemetry (OTel)**. + +### 7.1 Сбор телеметрии Backend +Java-приложение бэкенда запускается с подключением агента OpenTelemetry (`opentelemetry-javaagent.jar`). Это позволяет без изменения кода собирать: +* **Трейсы (Traces)**: Сквозное прохождение HTTP-запросов через контроллеры, сервисы и JDBC-драйвер к базам данных. +* **Метрики (Metrics)**: Метрики JVM (утилизация Heap, активность сборщика мусора GC, состояние потоков), метрики HTTP-запросов (интенсивность, латентность, коды ответов). +* **Логи (Logs)**: Системные логи Logback экспортируются по протоколу OTLP напрямую в SigNoz. + +### 7.2 Идентификация тенантов в мониторинге +Для глубокого анализа производительности СУБД конкретных университетов критически важно разделять телеметрию по тенантам. Мной была реализована сквозная маркировка: +* В бэкенде через Java-интерцептор `TenantInterceptor` при каждом запросе идентификатор тенанта помещается в контекст логирования SLF4J MDC (`MDC.put("tenant.id", tenant)`) и одновременно записывается в атрибуты текущего OpenTelemetry спана (`Span.current().setAttribute("tenant.id", tenant)`). +* Благодаря переменной окружения `OTEL_INSTRUMENTATION_LOGBACK_APPENDER_EXPERIMENTAL_CAPTURE_MDC_ATTRIBUTES=tenant.id`, OTel-агент автоматически парсит MDC и прикрепляет его к логам, отправляемым в SigNoz. Это позволяет администраторам фильтровать ошибки и строить графики нагрузки в разрезе каждого университета. + +### 7.3 Мониторинг баз данных на хостах +Для сбора детальной статистики с изолированных серверов баз данных: +1. На хостах БД силами Ansible разворачивается OpenTelemetry Collector. +2. В конфигурации коллектора (`otel-collector-config.yml.j2`) описывается ресивер `postgresql`, который подключается к локальной БД и считывает системные метрики (размер таблиц, cache hit ratio, количество транзакций, активные сессии, блокировки). +3. К собираемым метрикам жестко прикрепляются ресурсные атрибуты хоста: `service.name=magistr-db-` и `university.name=`. +4. Метрики экспортируются в центральный коллектор SigNoz. В результате в интерфейсе SigNoz доступны кастомные дашборды JVM, PostgreSQL и HTTP, позволяющие оперативно локализовать проблемы с производительностью баз данных конкретных вузов. + +--- + +## ЗАКЛЮЧЕНИЕ + +Разработанная и внедренная мной DevOps-архитектура для проекта «Магистр» успешно решила задачи изоляции данных, отказоустойчивости и автоматизации администрирования. + +### Ключевые результаты проделанной работы: +1. **Изоляция данных**: Реализована физическая изоляция БД университетов на выделенных серверах (VM в Proxmox VE), соответствующая требованиям безопасности и законодательства. +2. **Динамическое масштабирование**: Внедрен механизм динамического добавления тенантов без простоя системы (zero-downtime) за счет связки Spring Boot, K8s RBAC и ConfigMapWatcher. +3. **Отказоустойчивость**: Минимизированы риски каскадных сбоев бэкенда за счет применения in-memory fallback баз данных и оптимизации параметров подключения HikariCP. +4. **Автоматизация**: Создан универсальный Ansible-плейбук для быстрого ввода в эксплуатацию новых серверов БД (время развертывания «под ключ» сокращено до нескольких минут). +5. **Наблюдаемость**: Реализован сквозной мониторинг логов, метрик и трассировок с детализацией по тенантам на базе стека OpenTelemetry + SigNoz. + +Созданная инфраструктура обладает высокой степенью гибкости и готова к дальнейшему горизонтальному масштабированию по мере подключения новых высших учебных заведений к проекту «Магистр». diff --git a/SUPERVISOR_TASKS_IMPLEMENTATION_PLAN.md b/SUPERVISOR_TASKS_IMPLEMENTATION_PLAN.md deleted file mode 100644 index 68dfc2d..0000000 --- a/SUPERVISOR_TASKS_IMPLEMENTATION_PLAN.md +++ /dev/null @@ -1,248 +0,0 @@ -# Отчёт об изменениях по задачам научного руководителя - -Дата: 2026-05-19 - -## Краткий итог - -Выполнен первый цельный релиз архитектурной основы: - -- добавлены роли `EDUCATION_OFFICE`, `DEPARTMENT` и `SCHEDULE_VIEWER`; -- добавлена backend-проверка bearer-токена и ролей через `AuthorizationInterceptor` и `@RequireRoles`; -- добавлен жизненный цикл справочников: `ACTIVE` / `ARCHIVED`; -- физическое удаление ключевых сущностей заменено архивированием там, где это влияет на историю; -- добавлена история переводов преподавателей между кафедрами; -- добавлены точечные изменения расписания: перенос, отмена, замена; -- добавлен расширенный read-only поиск расписания по аудитории, преподавателю, кафедре, группе, дисциплине, типу занятия, паре и чётности; -- добавлены отчёты загруженности по преподавателям, аудиториям, кафедрам, парам и свободным аудиториям; -- просмотр расписаний переведён с плоского списка на совмещённые матричные таблицы чётной/нечётной недели с разрезом по группам, преподавателям или аудиториям; -- вкладка загруженности стала общей для аудиторий, преподавателей и кафедр; -- интерфейсы кафедры и учебного отдела переведены в общий стиль админ-панели через role-based вкладки; -- отдельная миграция `V2` удалена, изменения схемы внесены в `V1__init.sql`; -- обновлена документация проекта. - -## Backend - -### Роли и авторизация - -Поддерживаются роли: - -- `ADMIN`; -- `EDUCATION_OFFICE`; -- `DEPARTMENT`; -- `SCHEDULE_VIEWER`; -- `TEACHER`; -- `STUDENT`. - -Добавлены: - -- `AuthSessionService` — in-memory UUID-сессии; -- `AuthContext` — текущий пользователь в `ThreadLocal`; -- `AuthorizationInterceptor` — проверка `Authorization: Bearer ...`; -- `@RequireRoles` — ограничение доступа на уровне контроллеров и методов; -- `GET /api/auth/me` — текущий пользователь по токену. - -Redirect после входа: - -- `ADMIN` -> `/admin/`; -- `EDUCATION_OFFICE` -> `/admin/#schedule-view`; -- `DEPARTMENT` -> `/admin/#department-workspace`; -- `SCHEDULE_VIEWER` -> `/admin/#schedule-view`; -- `TEACHER` -> `/teacher/`; -- `STUDENT` -> `/student/`. - -### Миграция БД - -Отдельная миграция `V2__roles_lifecycle_temporal_schedule.sql` удалена по текущему решению проекта. - -Все изменения схемы внесены в: - -```text -backend/src/main/resources/db/migration/V1__init.sql -``` - -`V1__init.sql` теперь сразу создаёт: - -- lifecycle-поля к справочникам и пользователям; -- `teacher_department_assignments`; -- `subject_comments`; -- version-поля к `schedule_rules`; -- lock-поля к `schedule_rule_slots`; -- `schedule_overrides`; -- стартовых пользователей `учебный_отдел`, `кафедра_иб` и `просмотр_расписаний`. - -### Архивирование вместо удаления - -Архивирование добавлено для пользователей, аудиторий, оборудования, кафедр, специальностей, групп, подгрупп, дисциплин и правил расписания. - -Архивные аудитории, преподаватели, группы и дисциплины запрещены для новых назначений. Историческое расписание при этом не теряет ссылки. - -### История кафедр преподавателя - -Добавлены endpoint: - -- `GET /api/users/{id}/department-history`; -- `POST /api/users/{id}/department-transfer`; -- `GET /api/users/teachers/by-department/{departmentId}?date=YYYY-MM-DD`. - -При переводе преподавателя старая запись закрывается, новая открывается с `valid_from`, а `users.department_id` обновляется как текущая кафедра. - -### Расписание и загруженность - -Добавлены: - -- `GET /api/schedule/search`; -- `GET /api/workload/teachers`; -- `GET /api/workload/classrooms`; -- `GET /api/workload/departments`; -- `GET /api/workload/time-slots`; -- `GET /api/workload/free-classrooms`; -- `GET/POST/PUT/DELETE /api/edu-office/schedule/overrides`. - -`GET /api/schedule` теперь использует общий слой поиска, поэтому учитывает точечные изменения расписания. - -Роль `SCHEDULE_VIEWER` имеет доступ только к read-only просмотру расписаний и справочникам-фильтрам. - -### Кабинет кафедры API - -Добавлены: - -- `GET /api/department/subjects`; -- `POST /api/department/subjects/import`; -- `GET /api/department/subjects/{subjectId}/comments`; -- `POST /api/department/subjects/{subjectId}/comments`; -- `GET /api/department/teachers`; -- `GET /api/department/schedule`. - -Для роли `DEPARTMENT` кафедра берётся из текущего пользователя. Для администратора можно выбрать кафедру в UI. - -## Frontend - -### Единая панель по ролям - -Отдельные интерфейсы `/department/` и `/edu-office/` заменены на redirect в общую админ-панель: - -- `/department/` -> `/admin/#department-workspace`; -- `/edu-office/` -> `/admin/#schedule-view`. - -В `frontend/admin/js/main.js` добавлена фильтрация вкладок: - -- `ADMIN` видит все вкладки; -- `EDUCATION_OFFICE` видит просмотр расписаний, конструктор расписания, календарный график, загруженность, аудитории и оборудование; -- `DEPARTMENT` видит кабинет кафедры и просмотр расписаний; -- `SCHEDULE_VIEWER` видит только просмотр расписаний. - -Backend не опирается только на скрытие вкладок. `AuthorizationInterceptor` проверяет bearer-токен для `/api/**`, а `@RequireRoles` ограничивает операции на контроллерах и методах. Для кафедры дополнительно закрыты обходные пути: - -- общий `/api/subjects` оставлен для записи только администратору, кафедра загружает дисциплины через scoped `/api/department/subjects/import`; -- `/api/teacher-subjects` для роли `DEPARTMENT` проверяет, что и преподаватель, и дисциплина относятся к кафедре текущего пользователя; -- `SCHEDULE_VIEWER` имеет только read-only доступ к расписаниям и справочникам-фильтрам. - -После браузерной проверки исправлено визуальное скрытие недоступных вкладок: `layout.css` теперь принудительно скрывает `.nav-item[hidden]` и скрытые пункты меню настроек, поэтому роли больше не видят чужие вкладки в sidebar. - -### Просмотр расписаний - -Добавлена вкладка: - -```text -frontend/admin/views/schedule-view.html -frontend/admin/js/views/schedule-view.js -``` - -Возможности: - -- фильтры по одной дате в периоде, группе, преподавателю, аудитории, кафедре, дисциплине, типу занятия и чётности; -- двухнедельный диапазон для таблиц автоматически строится от понедельника выбранной недели; -- выбор разреза таблиц: автоматически, по группам, по преподавателям или по аудиториям; -- совмещённые матричные таблицы найденных занятий: строки — пары, столбцы — дни недели; -- нечётная неделя отображается в верхней половине ячейки, чётная — в нижней, одинаковые занятия в обе недели схлопываются в цельную ячейку; -- карточки занятий внутри ячеек с дисциплиной, группами, подгруппами, преподавателем, аудиторией, типом занятия, чётностью и ID слота. - -### Загруженность - -Вкладка `auditorium-workload` оставлена техническим tab id, но в UI называется `Загруженность`. - -Возможности: - -- выбор типа загруженности: аудитории, преподаватели или кафедры; -- сводная матрица по выбранной дате: строки — выбранные сущности, столбцы — пары; -- кафедральная матрица группирует занятия по кафедре преподавателя; -- для аудиторий сохранены фильтры корпуса, вместимости и оборудования; -- выбор конкретной аудитории, преподавателя или кафедры заменяет обзор двухнедельной таблицей по дням недели и времени; -- чётная и нечётная недели показываются в одной ячейке: если состояние одинаковое, ячейка цельная, если отличается — делится вертикально. - -### Кабинет кафедры - -Добавлена вкладка: - -```text -frontend/admin/views/department-workspace.html -frontend/admin/js/views/department-workspace.js -``` - -Возможности: - -- просмотр дисциплин кафедры; -- загрузка дисциплин из списка `код; название`; -- просмотр и добавление комментариев к дисциплинам; -- просмотр преподавателей кафедры; -- просмотр нагрузки преподавателей кафедры за период. - -### Админка - -Обновлено: - -- создание пользователей теперь поддерживает роли `DEPARTMENT`, `EDUCATION_OFFICE` и `SCHEDULE_VIEWER`; -- удаление пользователей заменено на архивирование; -- вкладка аудиторий показывает архивные записи и умеет восстанавливать аудиторию; -- удаление аудитории заменено на вывод из эксплуатации; -- настройки временных слотов доступны `ADMIN` и `EDUCATION_OFFICE`. - -## Документация - -Обновлены: - -- `AGENTS.md`; -- `docs/README.md`; -- `docs/API.md`; -- `docs/DATABASE.md`; -- `docs/BUSINESS_LOGIC.md`; -- `docs/FRONTEND.md`; -- `docs/ARCHITECTURE.md`. - -## Проверки - -Статически проверены новые и изменённые JS-файлы: - -```bash -node --check frontend/admin/js/main.js -node --check frontend/admin/js/views/schedule-view.js -node --check frontend/admin/js/views/auditorium-workload.js -node --check frontend/admin/js/views/department-workspace.js -node --check frontend/admin/settings/js/main.js -node --check frontend/admin/js/api.js -node --check frontend/admin/js/views/users.js -node --check frontend/admin/js/views/classrooms.js -git diff --check -``` - -Backend-компиляция проверяется через Docker Maven: - -```bash -docker run --rm -v /mnt/HDD/magistr/magistr/backend:/app -w /app maven:3.9-eclipse-temurin-17 mvn -q -DskipTests compile -``` - -Результат: компиляция прошла успешно. - -Единая `V1__init.sql` проверена на пустой PostgreSQL 16 внутри Docker: SQL применился успешно. - -Локальный `mvn` в окружении отсутствует, поэтому используется Docker. - -## Что осталось следующим этапом - -Остались задачи, которые требуют отдельного цикла проработки: - -- UI для просмотра истории переводов преподавателя в админке; -- UI для списка и редактирования уже созданных `schedule_overrides`; -- более строгая проверка конфликтов перед сохранением переносов; -- полноценный импорт XLSX/CSV для кафедры вместо текстовой загрузки; -- браузерный smoke-test и проверка ролевых вкладок после запуска `localhost:80`. diff --git a/analysis_results.md b/analysis_results.md deleted file mode 100644 index 44c82ff..0000000 --- a/analysis_results.md +++ /dev/null @@ -1,288 +0,0 @@ -# Анализ структуры вкладок и предложения по UX/UI оптимизации системы Magistr - -В данном документе представлен детальный анализ текущей структуры интерфейса административной панели (SPA) и настроек системы Magistr. Выявлены пересекающиеся области функционала, сформулированы предложения по их объединению, переносу в настройки или удалению, а также предложены новые функциональные модули и технические решения для оптимизации UX/UI и производительности. - ---- - -## 1. Анализ текущей структуры и доступности вкладок - -На данный момент в основном SPA-интерфейсе администратора (`/admin/`) зарегистрировано **13 вкладок**, а в дополнительном SPA настроек (`/admin/settings/`) — **2 вкладки**. - -В зависимости от роли пользователя, сайдбар фильтруется следующим образом: - -| Вкладка (ID) | Описание | Доступность по ролям | -| :--- | :--- | :--- | -| `users` | CRUD пользователей и архивирование | `ADMIN` | -| `groups` | CRUD групп, подгрупп и назначений календарей | `ADMIN` | -| `edu-forms` | Справочник форм обучения | `ADMIN` | -| `profiles` | Управление профилями обучения специальностей | `ADMIN` | -| `equipments` | Каталог оборудования | `ADMIN`, `EDUCATION_OFFICE` | -| `classrooms` | Список аудиторий с привязкой оборудования | `ADMIN`, `EDUCATION_OFFICE` | -| `subjects` | CRUD всех дисциплин и привязка преподавателей | `ADMIN` | -| `department-workspace` | Рабочая область кафедры (дисциплины, импорт, нагрузка) | `ADMIN`, `DEPARTMENT` | -| `schedule-view` | Просмотр расписаний (read-only) | `ADMIN`, `EDUCATION_OFFICE`, `DEPARTMENT`, `SCHEDULE_VIEWER` | -| `schedule` | Конструктор правил динамического расписания | `ADMIN`, `EDUCATION_OFFICE` | -| `academic-calendar` | Календарные графики и Excel-редактор сетки | `ADMIN`, `EDUCATION_OFFICE` | -| `auditorium-workload` | Матрицы и таблицы загруженности | `ADMIN`, `EDUCATION_OFFICE` | -| `database` | Управление тенантами (БД клиентов) | `ADMIN` | -| **Settings** (SPA) | Общие настройки, Управление временными слотами | `ADMIN`, `EDUCATION_OFFICE` (ссылка в подвале) | - ---- - -## 2. Пересечения функционала и проблемные зоны - -### 2.1. Избыточное дробление академической структуры -Для описания структуры обучения сейчас используются четыре отдельные вкладки: -1. `departments-data` (Создание кафедры/специальности) -2. `profiles` (Профили обучения специальностей) -3. `edu-forms` (Формы обучения) -4. `groups` (В блоке создания группы выбираются форма, кафедра, специальность и профиль) - -**В чём проблема:** -* **Разрыв контекста:** Чтобы завести новую специальность с профилями, администратору сначала нужно пойти во вкладку `departments-data`, создать специальность, затем перейти во вкладку `profiles`, выбрать в селекте эту специальность и добавить профили. -* **Перегрузка меню:** 13 вкладок в сайдбаре для роли `ADMIN` — это слишком много. Пользователю трудно ориентироваться визуально. - -### 2.2. Дублирование и раздвоение управления дисциплинами -Управление дисциплинами разнесено между вкладками `subjects` (для администратора) и `department-workspace` (для кафедры). - -**В чём проблема:** -* Администратор во вкладке `subjects` видит плоскую таблицу всех дисциплин всех кафедр, но не имеет удобных инструментов фильтрации по кафедре. -* Форма создания новой дисциплины во вкладке `subjects` требует ввода **числового ID кафедры вручную** (поле `new-subject-department` типа `number`). Это критическая UX-ошибка: администратор не должен помнить числовые ID БД наизусть. -* Привязка преподавателей к дисциплинам (`teacher_subjects`) находится на вкладке `subjects`. При этом сама нагрузка преподавателей и список преподавателей кафедры отображаются на вкладке `department-workspace`. Логичнее было бы видеть, какие дисциплины ведёт преподаватель, непосредственно при работе с его нагрузкой или в едином справочнике преподавателей/кафедры. - -### 2.3. Низкая частота использования справочника оборудования (`equipments`) -Оборудование существует только как подчиненная сущность для аудиторий (чтобы указать, что в аудитории 101 есть 20 ПК и 1 проектор). - -**В чём проблема:** -* Выделение целой вкладки под простой список («Проектор», «ПК») не оправдано. Список оборудования меняется раз в несколько лет. -* При этом привязка оборудования к аудиториям происходит на вкладке `classrooms`. - -### 2.4. Перегруженность вкладки групп (`groups`) -На вкладке групп на одном экране выводятся сразу три тяжелые формы и три таблицы: -1. Создание/редактирование групп + таблица групп. -2. Создание подгрупп для лабораторных + таблица подгрупп. -3. Назначение календарного графика группе + таблица назначений. - -**В чём проблема:** -* Экран перегружен элементами управления. -* Для работы с подгруппами или назначениями графиков конкретной группы пользователю приходится выбирать эту группу из длинного выпадающего списка в отдельной форме, вместо того чтобы выполнять действия в контексте строки этой группы в таблице. - -### 2.5. Нахождение системной вкладки «База данных» в общем меню расписания -Вкладка `database` отвечает за управление тенантами (добавление новых вузов-клиентов, создание для них БД и запуск Flyway миграций). - -**В чём проблема:** -* Это технический функционал уровня Super-Admin / DevOps. Он не имеет отношения к ежедневному планированию расписания. Нахождение его в одном ряду с расписанием и группами нарушает логику интерфейса. - ---- - -## 3. Рекомендации по оптимизации структуры (Что убрать, объединить и перенести) - -```mermaid -graph TD - subgraph Было (13 вкладок в меню ADMIN) - direction TB - T1[database] - T2[edu-forms] - T3[departments-data] - T4[profiles] - T5[equipments] - T6[classrooms] - T7[groups] - T8[users] - T9[subjects] - T10[department-workspace] - T11[schedule] - T12[schedule-view] - T13[academic-calendar] - T14[auditorium-workload] - end - - subgraph Стало (Сгруппированное меню + настройки) - direction TB - subgraph Основное меню SPA (Группы вкладок) - direction TB - DASH[1. Дашборд / Главная] - - subgraph Модуль: Расписание - SCH_ED[2. Конструктор расписания] - SCH_VI[3. Просмотр расписаний] - CAL[4. Календарный график] - WORK[5. Загруженность] - end - - subgraph Модуль: Структура - UNIV[6. Структура вуза
Кафедры, Специальности, Профили] - GRP[7. Группы и подгруппы] - SUBJ[8. Дисциплины] - end - - subgraph Модуль: Ресурсы - CLASS[9. Аудиторный фонд
Аудитории + Оборудование] - end - - USR[10. Пользователи] - end - - subgraph SPA Настройки (Настройки системы) - direction TB - SET_GEN[Общие настройки] - SET_TIME[Временные слоты сетка] - SET_FORM[Формы обучения из edu-forms] - SET_DB[Базы данных / Тенанты из database] - end - end - - T1 -->|Перенести| SET_DB - T2 -->|Перенести| SET_FORM - T3 -->|Объединить| UNIV - T4 -->|Объединить| UNIV - T5 -->|Объединить| CLASS - T6 -->|Объединить| CLASS - T7 -->|Оптимизировать| GRP - T8 -->|Оставить| USR - T9 -->|Оптимизировать| SUBJ - T10 -->|Сохранить для роли Кафедра| SUBJ - T11 -->|Оставить| SCH_ED - T12 -->|Оставить| SCH_VI - T13 -->|Оставить| CAL - T14 -->|Оставить| WORK -``` - -### 3.1. Перенос в настройки (`/admin/settings/`) -1. **База данных (`database`)**: Перенести в SPA настроек в раздел «Управление тенантами». Доступ должен быть только у роли `ADMIN`. -2. **Формы обучения (`edu-forms`)**: Перенести в настройки. Данный справочник заполняется один раз и не требует оперативного доступа из главного меню. - -### 3.2. Объединение вкладок -1. **Объединить Кафедры, Специальности и Профили** в одну вкладку **«Структура вуза»** (`university-structure`): - * Интерфейс делится на две вкладки внутри страницы: **«Кафедры»** и **«Специальности»**. - * Управление профилями должно происходить прямо внутри списка специальностей. Например, в строке специальности в таблице добавляется кнопка «Профили», открывающая список профилей этой специальности в раскрывающейся строке (accordion) или модальном окне. -2. **Объединить Аудитории (`classrooms`) и Оборудование (`equipments`)** в одну вкладку **«Аудиторный фонд»**: - * Основной экран показывает список аудиторий. - * Рядом располагается кнопка «Управление каталогом оборудования», которая открывает модальное окно с простым списком (добавить/удалить тип оборудования). Это избавит от необходимости держать под это отдельную вкладку в сайдбаре. - -### 3.3. Реорганизация вкладки групп (`groups`) -* **Оставить на вкладке только список групп** с возможностью фильтрации по курсу, кафедре и форме обучения. -* **Вынести подгруппы и назначения календарей из глобальных форм:** - * В строке таблицы для каждой группы добавить действия: - * `[Подгруппы]` — открывает компактное модальное окно для быстрого создания/редактирования подгрупп (лабораторных) именно для этой группы. - * `[Календарь]` — открывает модальное окно назначения календарного учебного графика на учебные годы для этой группы. - * Это уберет лишние тяжелые формы с главной страницы и исключит необходимость выбора группы из селектов вручную. - -### 3.4. Оптимизация Дисциплин (`subjects`) и Кабинета кафедры -* **Исправить форму добавления дисциплины:** Заменить ручной числовой ввод ID кафедры (`new-subject-department`) на стандартный выпадающий список (выбор из загруженных кафедр). -* **Связать привязку преподавателей с дисциплинами:** Вместо двух разрозненных таблиц на вкладке `subjects` («Все дисциплины» и «Привязки преподавателей»), объединить их: - * В строке дисциплины выводить список привязанных преподавателей (в виде чипов/badge). - * По клику на кнопку «Преподаватели» в строке дисциплины открывать модальное окно привязки/отвязки преподавателей для этой конкретной дисциплины. - ---- - -## 4. Что необходимо добавить (Новый функционал) - -### 4.1. Главная страница / Дашборд (`dashboard`) -При входе в систему администратор и сотрудники учебного отдела должны видеть не сырые таблицы пользователей или расписания, а высокоуровневую аналитическую панель: -* **Информационные карточки (Метрики):** - * Всего активных групп / студентов. - * Количество задействованных преподавателей. - * Процент занятости аудиторного фонда на текущий день. - * Количество запланированных пар на сегодня. -* **Мониторинг расписания в реальном времени:** - * Список идущих прямо сейчас пар (текущий временной слот). - * Ближайшие свободные аудитории. -* **Быстрый переход:** Кнопки быстрого создания правила расписания, добавления переноса/замены. - -### 4.2. Центр предупреждений и конфликтов (Red Zone) -В соответствии с планируемой бизнес-логикой (раздел 5 `docs/BUSINESS_LOGIC.md`), система должна автоматически валидировать расписание и предупреждать о коллизиях. -Необходима отдельная вкладка или виджет на дашборде **«Конфликты расписания»**: -* **Критические ошибки (Блокирующие генерацию):** - * *Накладки преподавателей:* Преподаватель назначен в один и тот же временной слот в разные аудитории / к разным группам (кроме потоковых лекций). - * *Накладки аудиторий:* Две разные пары стоят в одной аудитории в один слот. -* **Предупреждения (Мягкие проверки):** - * *Превышение вместимости:* Численность группы (или сумма подгрупп при совмещенных занятиях) превышает физическую вместимость аудитории. - * *Недостаток оборудования:* Дисциплина (например, лабораторная по химии) требует определенного оборудования, которого нет в назначенной аудитории. - * *Превышение нагрузки:* Преподаватель превысил недельный лимит часов. - ---- - -## 5. Концепция новой структуры меню (Сайдбара) - -Для уменьшения визуального шума сайдбар предлагается сделать **двухуровневым** (сворачиваемые группы) или четко разделить визуальными разделителями: - -``` -[ Логотип Magistr ] ---------------------------- -👤 Кабинет кафедры <-- (Показывается только для роли DEPARTMENT) -📈 Дашборд <-- (Новый главный экран для ADMIN / EDU_OFFICE) - -📁 РАСПИСАНИЕ - 🗓 Конструктор правил - 🗓 Точечные изменения <-- (Выделенный функционал оверрайдов/переносов) - 📅 Календарный график - 📊 Загруженность - 🔍 Просмотр расписаний - -🗂 СУБЪЕКТЫ И РЕСУРСЫ - 🏫 Аудиторный фонд <-- (Объединенные Аудитории + Оборудование) - 🎓 Группы обучения <-- (Группы + модалки подгрупп/календарей) - 🏛 Структура вуза <-- (Кафедры + Специальности + Профили) - 📚 Дисциплины <-- (Дисциплины + модалки преподавателей) - -⚙️ СИСТЕМА - 👥 Пользователи - ⚙️ Настройки <-- (Переход в настройки SPA: слоты, БД, формы обучения) ---------------------------- -[ Профиль / Выход ] -``` - ---- - -## 6. Идеи по оптимизации фронтенда и взаимодействия с API - -### 6.1. Кэширование справочников на клиенте -На данный момент при переключении почти каждой вкладки заново запрашиваются списки кафедр, специальностей, учебных годов и т.д. -* **Решение:** Реализовать простой кэш-менеджер во фронтенд-клиенте (`api.js` или отдельный `cache.js`): - ```javascript - const cache = new Map(); - export async function getCachedData(key, fetchPromise) { - if (!cache.has(key)) { - cache.set(key, await fetchPromise); - } - return cache.get(key); - } - export function clearCache(key) { - if (key) cache.delete(key); - else cache.clear(); - } - ``` - Это позволит избежать десятков дублирующих запросов при переключении между вкладками `groups`, `subjects`, `schedule`. - -### 6.2. Ленивая инициализация вкладок (Lazy Loading) -Сейчас в `main.js` импортируются все JS-модули вкладок одновременно при загрузке страницы: -```javascript -import { initUsers } from './views/users.js'; -import { initGroups } from './views/groups.js'; -// ...еще 10 импортов -``` -* **Решение:** Перейти на динамический импорт модулей инициализации при переходе на конкретную вкладку: - ```javascript - async function switchTab(tab) { - // ... - const viewModule = await import(`./views/${tab}.js`); - viewModule.init(); - // ... - } - ``` - Это уменьшит размер начального JS-бандла и ускорит первую загрузку страницы. - -### 6.3. Единый реестр кастомных выпадающих списков -В проекте реализованы кастомные селекты (выпадающие списки с поддержкой поиска и чекбоксов). При динамической подгрузке HTML-шаблонов часто требуется их повторная инициализация. -* **Решение:** Создать глобальный шину событий (Event Bus) или MutationObserver, который автоматически подхватывает элементы с классами `.custom-select` или атрибутами `data-select` и инициализирует их без вызова ручного кода `initAllCustomDropdowns()` в каждом отдельном скрипте представления. - ---- - -## Резюме - -Предложенные изменения позволят: -1. Сократить количество пунктов основного меню с **13** до **8-9** логически сгруппированных разделов. -2. Исключить ручной ввод ID (заменив на селекты) и разгрузить перегруженные интерфейсы (выносом подгрупп и назначений в контекстные модальные окна групп). -3. Повысить отзывчивость интерфейса за счет клиентского кэширования справочников и ленивой загрузки JS-модулей. -4. Добавить полноценный Дашборд и Центр предупреждений о конфликтах, что выведет аналитические возможности системы на уровень премиум-класса.