NODEDC_LAUNCHER/doc/base/BASE_THINK.md

38 KiB
Raw Blame History

BASE_THINK.md

Да, тут лучше сразу зафиксировать не как “страничку с плитками”, а как отдельный Control Plane / Launcher NODE.DC: единая точка входа, витрина приложений, управление клиентами, выдача доступов, связка с Authentik и дальше — прокидывание пользователей в Plane/Task Manager, NodeDC и будущие сервисы.

Я бы раскладывал так.


1. Главная мысль по продукту

Launcher — это не часть NodeDC и не часть Task Manager. Это отдельное React-приложение, которое управляет всей экосистемой.

Функционально он отвечает за:

  1. вход пользователя через Authentik / OIDC;
  2. определение, к какому клиенту / компании относится пользователь;
  3. показ только доступных сервисов;
  4. управление клиентами, пользователями, группами и сервисами;
  5. выдачу доступов на уровне клиента, группы или конкретного участника;
  6. передачу пользователя в нужный сервис по SSO;
  7. хранение каталога приложений: название, описание, URL, slug, статус, превью, порядок отображения;
  8. в будущем — синхронизацию доступов с Authentik, Plane, NodeDC и другими сервисами.

Это хорошо совпадает с базовым документом бэкендеров: там уже выделены сущности Клиент, Участник, Группа, Сервис и Доступ, а доступ может выдаваться всему клиенту, группе или отдельному участнику.


2. Важное архитектурное решение: Authentik не должен быть всей бизнес-логикой

Authentik лучше использовать как Identity Provider и SSO-слой, а не как единственное место хранения бизнес-иерархии.

То есть:

Слой За что отвечает
Authentik логин, пароль, OIDC, группы, claims, SSO, политики входа
Launcher Backend клиенты, компании, участники, сервисы, доступы, исключения, демо-периоды, статусы, каталог приложений
Plane / Task Manager workspace, проекты, роли внутри workspace, доски, задачи
NodeDC workflows, sharing workflow, агентные сценарии, runtime-доступы
Launcher Frontend витрина, админка, доступы, запуск сервисов

Почему так: в Authentik действительно есть пользователи, группы, роли, приложения, политики, application bindings и entitlements. Группы — это коллекции пользователей, группы могут иметь parent groups, роли могут назначаться группам и наследоваться вниз по иерархии. (authentik) Но бизнес-сущности типа “ООО Ромашка купила Task Manager до 01.09”, “у Васи исключение из доступа”, “этому клиенту доступен демо-модуль”, “у сервиса вот такое превью” — лучше держать в собственной модели Launcher.

Authentik должен получать из Launcher только то, что нужно для SSO и доступа: группы, application bindings, claims, entitlements или синхронизированные атрибуты. Scope mappings в Authentik как раз позволяют передавать данные пользователя и групп в OAuth/OIDC claims. (authentik)


3. Доменная модель Launcher

Я бы зафиксировал такие сущности.

3.1. Client / Клиент

Клиент — владелец доступа. Это может быть компания или частное лицо.

Поля:

Client {
  id: string;
  type: "company" | "person";
  name: string;
  legalName?: string;
  status: "active" | "suspended" | "demo" | "expired";
  demoEndsAt?: string;
  contactName?: string;
  contactEmail?: string;
  notes?: string;
  createdAt: string;
  updatedAt: string;
}

Пример:

{
  id: "client_romashka",
  type: "company",
  name: "ООО Ромашка",
  status: "active",
  demoEndsAt: "2026-06-01"
}

3.2. User / Участник

Пользователь берётся из Authentik, но в Launcher хранится его бизнес-привязка.

LauncherUser {
  id: string;
  authentikUserId: string;
  email: string;
  name: string;
  avatarUrl?: string;
  globalStatus: "invited" | "active" | "blocked";
  createdAt: string;
}

3.3. ClientMembership / Членство в клиенте

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

ClientMembership {
  id: string;
  clientId: string;
  userId: string;
  role: "client_owner" | "client_admin" | "member";
  status: "active" | "disabled";
}

Роли:

Роль Что может
root_admin ваш главный NODE.DC-админ, видит всё
support_admin ваш внутренний оператор, можно ограничить
client_owner главный админ клиента
client_admin админ клиента
member обычный пользователь

Важно: root_admin Launcher не обязательно должен быть тем же самым, что super-user Authentik. В Authentik super-user даётся через группу с super-user правами, и эти права наследуются descendant-группами, поэтому с этим нужно аккуратно. (authentik)


3.4. ClientGroup / Группа внутри клиента

Это не обязательно один-в-один Authentik group. Это бизнес-группа внутри клиента.

ClientGroup {
  id: string;
  clientId: string;
  name: string;
  description?: string;
  memberIds: string[];
}

Примеры:

  • Руководство
  • Менеджеры
  • Бухгалтерия
  • Демо-команда
  • IT-администраторы

Эта модель уже есть в документе бэкендеров: группы создаются внутри клиента, туда добавляются участники, и сервис может подключаться группе.


3.5. Service / Сервис

Сервис — это приложение в экосистеме.

Service {
  id: string;
  slug: string;
  title: string;
  subtitle?: string;
  description: string;
  url: string;
  launchUrl?: string;
  iconUrl?: string;
  coverImageUrl?: string;
  previewVideoUrl?: string;
  ambientVideoUrl?: string;
  accentColor?: string;
  status: "active" | "maintenance" | "hidden" | "disabled";
  order: number;
  authentikApplicationSlug?: string;
  authentikGroupName?: string;
}

Примеры сервисов:

  • NodeDC Agent Platform
  • NODE.DC Task Manager
  • Tender Agent
  • 1C Assistant
  • Digital Twin Viewer
  • Demo Gallery
  • Internal Tools

В базовом документе уже есть поля сервиса: название, описание, иконка, URL, технический slug, статус, связанная группа Authentik, порядок отображения.


3.6. ServiceAccess / Доступ к сервису

Доступ должен быть трёхуровневым:

ServiceGrant {
  id: string;
  serviceId: string;
  targetType: "client" | "group" | "user";
  targetId: string;
  appRole: "viewer" | "member" | "admin" | "owner";
  status: "active" | "disabled";
}

Отдельно нужны исключения:

ServiceAccessException {
  id: string;
  serviceId: string;
  userId: string;
  type: "deny" | "allow";
  reason?: string;
}

Правило вычисления:

effectiveAccess =
  client is active
  AND service is active
  AND user is active
  AND user has grant via client/group/user
  AND user does not have deny exception

Это прямо нужно, потому что в документе бэкендеров есть сценарий: сервис подключен всему клиенту, но конкретному участнику его надо убрать — это должно отображаться как исключение.


4. Иерархия прав в интерфейсе

4.1. Root Admin NODE.DC

Видит всё:

  • все клиенты;
  • всех пользователей из Authentik / Launcher;
  • все сервисы;
  • все доступы;
  • все исключения;
  • статусы синхронизации;
  • ошибки provisioning;
  • аудит действий;
  • может банить, блокировать, приостанавливать клиента;
  • может создавать сервисы и редактировать каталог;
  • может назначать клиента, админов клиента и доступы к приложениям.

4.2. Client Owner / Client Admin

Видит только свою компанию.

Может:

  • приглашать пользователей;
  • удалять / деактивировать пользователей внутри своей компании;
  • создавать группы внутри клиента;
  • добавлять участников в группы;
  • выдавать доступы только к тем сервисам, которые уже разрешены клиенту;
  • назначать других админов внутри клиента;
  • смотреть, кто к чему имеет доступ.

Не может:

  • видеть других клиентов;
  • создавать глобальные сервисы;
  • менять URL сервисов;
  • выдавать доступ к сервису, который клиенту не куплен / не разрешён;
  • менять Authentik-группы напрямую;
  • видеть системные ошибки других клиентов.

4.3. Member

Видит только пользовательский Launcher:

  • доступные сервисы;
  • карточку профиля;
  • выход из аккаунта;
  • возможно — выбор компании, если он состоит в нескольких клиентах.

Никаких глобальных настроек.


5. UX-структура приложения

Ты правильно смотришь в сторону референса. Его можно взять не буквально, а как композиционный принцип:

полноэкранный медиа-фон + верхний минимальный бар + нижняя карусель сервисов + glass-detail выбранного сервиса + отдельный админский слой.

5.1. Пользовательский экран

Композиция:

┌─────────────────────────────────────────────┐
│ Logo                      Search/Profile/⚙  │
│                                             │
│                                             │
│        Ambient preview выбранного сервиса   │
│        / абстрактная анимация / видео       │
│                                             │
│                  Glass Detail Card          │
│                  Название                   │
│                  Описание                   │
│                  Статус                     │
│                  [Открыть]                  │
│                                             │
│                                             │
│      [NodeDC] [Task Manager] [1C] [Demo]    │
└─────────────────────────────────────────────┘

Внизу — не “проекты” в смысле Plane Projects, а лучше назвать:

  • Контуры
  • Сервисы
  • Приложения
  • Рабочие среды

Я бы выбрал “Сервисы” или “Контуры”, потому что “Проекты” уже есть внутри Plane.


5.2. Поведение выбора сервиса

При клике на карточку в нижней панели:

  1. центральный фон меняется на ambientVideoUrl / previewVideoUrl / coverImageUrl;
  2. поверх появляется большая glass-карточка;
  3. справа или по центру показывается описание;
  4. кнопка “Открыть” ведёт на SSO launch URL;
  5. если сервис на техработах — кнопка disabled, статус “Техработы”;
  6. если сервис скрыт — обычный пользователь его не видит;
  7. root admin может видеть скрытые сервисы с бейджем.

5.3. Верхняя панель

Минимальная:

  • логотип NODE.DC / H&H DC;
  • выбранный клиент, если у пользователя несколько компаний;
  • кнопка “Администрирование”, если есть права;
  • профиль пользователя;
  • выход.

По дизайн-коду верхние панели должны держать единую горизонтальную ось, одинаковую высоту кнопок и нормальные радиусы.


6. Админка: не отдельная страница, а слой поверх Launcher

Так как приложение одностраничное, админку лучше делать не как /admin, а как режим / overlay:

[Launcher View]
    ↓
[Admin Command Center overlay / drawer]

Варианты:

  1. правая большая glass-панель;
  2. полноэкранный matte glass overlay;
  3. split view: слева список, справа детали;
  4. command-center в стиле системной панели.

Для MVP я бы делал полноэкранный админский overlay с внутренними вкладками.


7. Разделы админки

Для root admin

Администрирование
├── Клиенты
├── Участники
├── Группы
├── Каталог сервисов
├── Доступы
├── Инвайты
├── Синхронизация
└── Аудит

Это расширяет базовый документ, где уже есть разделы “Клиенты”, “Участники”, “Группы внутри клиента”, “Каталог сервисов”, “Доступы” и “Статус Authentik”.


Для client admin

Администрирование компании
├── Участники
├── Группы
├── Доступы к сервисам
├── Инвайты
└── Профиль компании

8. Экран “Клиенты”

Список клиентов:

Клиент Тип Статус Участники Сервисы Демо до Контакт
ООО Ромашка Компания Активен 18 2 Иван
Иван Петров Частное лицо Демо 2 1 01.06 Иван

Детальная карточка клиента:

  • название;
  • тип;
  • статус;
  • демо-доступ;
  • контактное лицо;
  • заметки;
  • участники;
  • группы;
  • подключенные сервисы;
  • история действий;
  • синхронизация.

9. Экран “Каталог сервисов”

Сервис — это настраиваемая витрина.

Поля в форме:

Название
Slug
Краткое описание
Полное описание
URL
Launch URL
Статус: активен / скрыт / техработы / отключен
Иконка
Обложка
Превью-видео
Ambient-видео
Цветовой акцент
Порядок отображения
Связанная группа Authentik
Связанное приложение Authentik
Доступен всем новым клиентам: да/нет

Очень важно: медиа нужно сразу предусмотреть мультиформатно:

ServiceMedia {
  icon?: AssetRef;
  thumbnail?: AssetRef;
  coverImage?: AssetRef;
  previewVideo?: AssetRef;
  ambientVideo?: AssetRef;
  fallbackGradient?: string;
}

Поддержать:

  • .png
  • .jpg
  • .webp
  • .gif
  • .mp4
  • .webm

На фронте:

type MediaKind = "image" | "video" | "gif" | "gradient";

10. Экран “Доступы”

Это ключевой экран.

Я бы делал не просто таблицу, а матрицу доступа:

Клиент: ООО Ромашка

Участник        Task Manager    NodeDC      1C Assistant
---------------------------------------------------------
Иван            Admin           Admin       —
Вася            Member          —           —
Лена            Member          Member      Deny
Бухгалтерия     Member          —           Member

И рядом объяснение effective access:

Лена / NodeDC:
Нет доступа, потому что есть индивидуальное исключение deny.

Вася / Task Manager:
Есть доступ, потому что сервис подключен всему клиенту.

Пётр / 1C Assistant:
Есть доступ, потому что состоит в группе “Бухгалтерия”.

Это прямо соответствует требованию из PDF: надо отображать итоговый доступ пользователя и объяснять, почему он есть или отсутствует.


11. Как это ложится на Plane / Task Manager

Здесь лучше зафиксировать жёсткое правило:

Один клиент в Launcher = один workspace в Task Manager.

Например:

Client: ООО Ромашка
Plane Workspace: romashka

Дальше:

  1. root admin создаёт клиента;
  2. клиенту подключается сервис task-manager;
  3. backend создаёт / связывает Plane workspace;
  4. все пользователи клиента, которым выдан доступ к Task Manager, автоматически появляются в этом workspace;
  5. внутри Plane админ клиента уже создаёт проекты;
  6. распределение людей по проектам остаётся штатной логикой Plane.

То есть Launcher не должен управлять каждым проектом Plane. Он управляет только:

  • доступом к приложению;
  • принадлежностью пользователя к клиенту;
  • provisioning в workspace.

А уже Plane управляет:

  • проектами;
  • досками;
  • задачами;
  • ролями внутри workspace;
  • project-level access.

Это правильная граница, потому что вы не ломаете внутреннюю модель Plane.


12. Как это ложится на NodeDC

Для NodeDC логика похожая, но глубже:

Client: ООО Ромашка
NodeDC Tenant / Workspace: romashka
NodeDC Workflows: выдаются через NodeDC sharing

Launcher отвечает за:

  • доступ к приложению NodeDC;
  • роль пользователя в NodeDC: member/admin;
  • привязку пользователя к клиентскому пространству.

NodeDC отвечает за:

  • workflow;
  • sharing workflow;
  • runtime-права;
  • агенты;
  • запуск / мониторинг;
  • доступ к конкретным сценариям.

То есть в MVP Launcher не должен пытаться управлять каждым workflow. Он должен только открыть пользователю вход в NodeDC и передать идентичность / клиент / роль.


13. Authentik: как лучше состыковать

В Authentik есть Applications, Providers, Groups, Roles, Policies, Application bindings и Entitlements. Для приложений важно помнить: если на приложение не задано bindings, то по умолчанию все пользователи могут получить к нему доступ, поэтому для ваших сервисов лучше всегда задавать bindings явно. (authentik)

Рекомендуемая схема:

Authentik
├── Application: launcher
├── Application: task-manager
├── Application: nodedc
├── Group: nodedc-root-admins
├── Group: client-romashka-users
├── Group: client-romashka-admins
├── Group: service-task-manager-romashka
└── Group: service-nodedc-romashka

Но бизнес-правило всё равно должно жить в Launcher.

Пример flow:

Launcher Backend:
user Вася имеет доступ к Task Manager
↓
создать / обновить группу в Authentik
↓
добавить Васю в group service-task-manager-romashka
↓
Authentik application binding разрешает вход в Task Manager
↓
Task Manager получает пользователя через OIDC
↓
Task Manager backend проверяет / создаёт пользователя в workspace romashka

OIDC scopes в Authentik уже включают openid, profile, email, entitlements, offline_access; profile включает базовую информацию пользователя и group membership, а entitlements можно использовать для application-level прав. (authentik)


14. Что фронту делать сейчас без настоящего Authentik

Фронт сейчас надо делать так, будто Authentik уже есть, но через mock contract.

Mock endpoint GET /api/me

type MeResponse = {
  user: {
    id: string;
    name: string;
    email: string;
    avatarUrl?: string;
  };
  launcherRole: "root_admin" | "support_admin" | "client_owner" | "client_admin" | "member";
  memberships: {
    clientId: string;
    clientName: string;
    role: "client_owner" | "client_admin" | "member";
  }[];
  activeClientId: string;
  permissions: {
    canOpenAdmin: boolean;
    canManageClients: boolean;
    canManageOwnClient: boolean;
    canManageServiceCatalog: boolean;
    canInviteUsers: boolean;
  };
};

Mock endpoint GET /api/launcher/services

type LauncherServiceView = {
  id: string;
  slug: string;
  title: string;
  description: string;
  status: "active" | "maintenance" | "hidden";
  userAccess: "allowed" | "denied";
  appRole?: "viewer" | "member" | "admin" | "owner";
  openUrl?: string;
  media: {
    icon?: string;
    thumbnail?: string;
    coverImage?: string;
    previewVideo?: string;
    ambientVideo?: string;
  };
};

Mock endpoint GET /api/admin/clients

Для root admin.

Mock endpoint GET /api/admin/client/:id/access-matrix

Для матрицы доступов.


15. Frontend state-модель

Я бы делал так:

LauncherState {
  mode: "user" | "admin";
  activeClientId: string;
  selectedServiceId?: string;
  serviceRail: LauncherServiceView[];
  me: MeResponse;
}

Для админки:

AdminState {
  activeSection:
    | "clients"
    | "users"
    | "groups"
    | "services"
    | "access"
    | "invites"
    | "sync"
    | "audit";

  selectedClientId?: string;
  selectedUserId?: string;
  selectedServiceId?: string;
}

Технически:

  • React;
  • TypeScript;
  • Tailwind;
  • Zustand или Redux Toolkit для UI state;
  • React Query / TanStack Query для API;
  • mock API adapter, который потом меняется на реальный backend;
  • без реального Authentik на первом этапе.

16. UI-компоненты

Базовые

AppShell
TopBar
ProfileMenu
ClientSwitcher
ServiceRail
ServiceTile
ServiceStage
ServiceDetailGlassCard
LaunchButton
StatusBadge
AdminOverlay
AdminSidebar
AdminSectionHeader
GlassTable
AccessMatrix
UserPicker
GroupPicker
ServiceEditor
InviteModal
ConfirmModal
ActionDropdown
MediaPreviewField

Shared design components

GlassSurface
GlassCard
RoundIconButton
AccentButton
SecondaryButton
DangerButton
Checker
Chip
PortalDropdown
SearchInput
SegmentedControl

Это важно, потому что в вашем дизайн-коде прямо написано: новый экран или popup не стилизуется локально “на глаз”; сначала используется shared-class или shared-component, а если shared-слоя нет — создаётся reusable-компонент.


17. Дизайн-канон для Launcher

Из текущего HDESIGN-CODE надо взять:

  1. matte black glass для popup, dropdown, modal, sidebar overlays и settings-карточек;
  2. blur / backdrop-filter;
  3. мягкую стеклянную границу;
  4. большие радиусы;
  5. отсутствие жёстких outline;
  6. круглые action-кнопки;
  7. акцентные CTA с правильным контрастом текста;
  8. dropdown только через portal, если он внутри scroll/detail/card/sticky header.

Радиусы можно взять напрямую:

--launcher-radius-xl: 1.75rem;
--launcher-radius-card: 1.35rem;
--launcher-radius-control: 1.25rem;
--launcher-radius-circle: 999px;

Из дизайн-кода:

  • большие surface-контейнеры: 1.75rem;
  • glass-карточки: 1.35rem;
  • поля, селекты, кнопки, chip-кнопки: 1.25rem;
  • малые круглые action-кнопки: 999px.

18. Как адаптировать референс

Референс даёт правильную композицию:

  • большой экран;
  • затемнённый / заблюренный фон;
  • центральная медиа-зона;
  • мягкая glass-панель;
  • нижняя timeline/rail-панель;
  • плавающий правый assistant-like модуль.

Для Launcher это можно перевести так:

Reference Video Editor → Launcher

Storyboard              → список сервисов / контуров
AI Assistant card       → detail выбранного сервиса / админ-панель
Moodboard               → медиа-превью / related apps
Timeline bottom         → service rail
Main preview            → ambient preview выбранного сервиса

То есть визуально это будет не “админка”, а входная операционная панель экосистемы.


19. MVP-границы

На первом этапе не надо делать всё.

MVP 1 — пользовательский Launcher

  • mock login state;
  • верхний бар;
  • профиль;
  • список доступных сервисов;
  • нижняя карусель;
  • выбранный сервис;
  • glass-detail;
  • кнопка “Открыть”;
  • статусы active / maintenance / hidden;
  • mock media assets.

MVP 2 — root admin shell

  • кнопка “Администрирование”;

  • overlay;

  • разделы:

    • Клиенты;
    • Участники;
    • Каталог сервисов;
    • Доступы;
  • CRUD на mock data;

  • access matrix;

  • service editor.

MVP 3 — client admin shell

  • ограниченная админка;
  • участники своей компании;
  • группы своей компании;
  • инвайты;
  • выдача доступов только к разрешённым сервисам.

MVP 4 — backend contract ready

  • заменить mock adapter на API;
  • оставить те же UI-компоненты;
  • добавить loading / error / empty states;
  • добавить sync-status;
  • добавить audit log.

20. Инвайты и регистрация

Лучше сейчас зафиксировать:

MVP работает только по invite-only модели.

Сценарий:

1. Root admin создаёт клиента.
2. Root admin создаёт client_owner.
3. Client owner заходит в Launcher.
4. Client owner создаёт invite.
5. Пользователь переходит по одноразовой ссылке.
6. Authentik создаёт / активирует пользователя.
7. Launcher связывает пользователя с clientId.
8. Пользователь видит только сервисы, доступные ему.

Если позже появится свободная регистрация, то новый пользователь должен попадать в состояние:

unassigned / pending / no services

То есть он вошёл, но ничего не видит, пока root admin не привяжет его к клиенту.


21. Синхронизация

В админке надо сразу предусмотреть статус:

SyncStatus {
  target: "authentik" | "task-manager" | "nodedc";
  status: "synced" | "pending" | "error";
  lastSyncAt?: string;
  errorMessage?: string;
}

В PDF уже есть статусы Authentik: “синхронизировано”, “ожидает синхронизации”, “ошибка синхронизации”, а также действия “повторить” и “открыть детали ошибки”.

В UI это нужно показывать не техническим логом, а понятной плашкой:

Authentik: синхронизировано
Task Manager: ожидает создания workspace
NodeDC: ошибка синхронизации — открыть детали

22. Главные риски

Риск 1. Смешать роли Launcher и роли приложений

Нельзя делать одну роль “admin” на всё.

Нужно разделить:

launcherRole: root_admin / client_admin / member
taskManagerRole: owner / admin / member
nodeDcRole: admin / editor / viewer

Риск 2. Назвать сервисы “проектами”

В Plane уже есть Projects. В NodeDC есть Workflows. В Launcher лучше использовать “Сервисы” или “Контуры”.


Риск 3. Полагаться только на Authentik groups

Authentik groups хороши для SSO и грубой авторизации, но ваша коммерческая модель доступа должна жить в Launcher.


Риск 4. Не сделать deny exceptions

Без исключений будет больно: “всему клиенту доступ дали, но Васе нельзя”.


Риск 5. Сделать админку как обычный скучный CRUD

У вас визуальная система сильная. Админка должна быть такой же glass/control-plane, но с нормальной читаемостью таблиц.


23. ТЗ для Codex — первый проход

Можно дать Кодексу так:

# ТЗ: NODE.DC Launcher Frontend MVP

## Цель
Создать отдельное одностраничное React-приложение Launcher для входа в экосистему NODE.DC. Приложение должно работать без реального Authentik на первом этапе, но иметь mock API-контракт, совместимый с будущей OIDC/Authentik-интеграцией.

## Основные режимы
1. User Launcher View — витрина доступных сервисов.
2. Root Admin View — глобальная админка NODE.DC.
3. Client Admin View — админка конкретного клиента.

## Дизайн
Использовать NODE.DC glass-canon:
- matte black glass;
- backdrop blur;
- крупные радиусы;
- без browser outline;
- круглые action-кнопки;
- portal dropdown;
- русскоязычный UI;
- reusable shared components, без локальной стилизации “на глаз”.

## User Launcher View
Сделать:
- fullscreen shell;
- top bar с логотипом, выбранным клиентом, профилем;
- центральную media-stage зону;
- нижнюю service rail панель;
- service tile карточки;
- detail glass-card выбранного сервиса;
- кнопку “Открыть”;
- статусы сервиса: доступен / техработы / скрыт;
- поддержку image / gif / mp4 / webm preview.

## Admin View
Сделать overlay-панель администрирования:
- Клиенты;
- Участники;
- Группы;
- Каталог сервисов;
- Доступы;
- Инвайты;
- Синхронизация.

## Mock Data
Добавить mock:
- ООО Ромашка;
- ООО Рога и Копыта;
- NodeDC Agent Platform;
- NODE.DC Task Manager;
- 1C Assistant;
- несколько пользователей с разными ролями.

## Access Engine
Реализовать функцию computeEffectiveAccess:
- учитывать client status;
- service status;
- grants на client/group/user;
- deny exceptions;
- user status.

## Acceptance Criteria
- member видит только доступные сервисы;
- client_admin видит админку только своей компании;
- root_admin видит все разделы;
- сервис на техработах виден, но кнопка “Открыть” disabled;
- hidden-сервис не виден member-у;
- deny exception перекрывает client-level grant;
- dropdown/menu рендерятся через portal;
- UI не содержит смешанных английских подписей.

24. Мой вердикт по структуре

Делать надо так:

Launcher
├── витрина сервисов
├── клиентская админка
├── root-админка
├── каталог сервисов
├── access matrix
├── invite flow
├── sync status
└── SSO launch layer

А не так:

просто React-страница с кнопками на разные сайты

Потому что у вас уже не набор сайтов, а экосистема:

Информационный сайт
↓
Launcher / Auth
↓
NodeDC Agent Platform
↓
Task Manager / Plane
↓
дальше 1C, тендеры, digital twins, демки, внутренние сервисы

И Launcher становится тем самым верхним слоем, который делает из отдельных приложений единый веб-суперапп.