diff --git a/doc/.DS_Store b/doc/.DS_Store new file mode 100644 index 0000000..c0bb2e2 Binary files /dev/null and b/doc/.DS_Store differ diff --git a/doc/.gitkeep b/doc/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/doc/.gitkeep @@ -0,0 +1 @@ + diff --git a/doc/base/BASE_THINK.md b/doc/base/BASE_THINK.md new file mode 100644 index 0000000..226fdb6 --- /dev/null +++ b/doc/base/BASE_THINK.md @@ -0,0 +1,1123 @@ +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][1]) Но бизнес-сущности типа “ООО Ромашка купила Task Manager до 01.09”, “у Васи исключение из доступа”, “этому клиенту доступен демо-модуль”, “у сервиса вот такое превью” — лучше держать в собственной модели Launcher. + +Authentik должен получать из Launcher только то, что нужно для SSO и доступа: группы, application bindings, claims, entitlements или синхронизированные атрибуты. Scope mappings в Authentik как раз позволяют передавать данные пользователя и групп в OAuth/OIDC claims. ([authentik][2]) + +--- + +## 3. Доменная модель Launcher + +Я бы зафиксировал такие сущности. + +### 3.1. Client / Клиент + +Клиент — владелец доступа. Это может быть компания или частное лицо. + +Поля: + +```ts +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; +} +``` + +Пример: + +```ts +{ + id: "client_romashka", + type: "company", + name: "ООО Ромашка", + status: "active", + demoEndsAt: "2026-06-01" +} +``` + +--- + +### 3.2. User / Участник + +Пользователь берётся из Authentik, но в Launcher хранится его бизнес-привязка. + +```ts +LauncherUser { + id: string; + authentikUserId: string; + email: string; + name: string; + avatarUrl?: string; + globalStatus: "invited" | "active" | "blocked"; + createdAt: string; +} +``` + +--- + +### 3.3. ClientMembership / Членство в клиенте + +Один пользователь потенциально может быть в нескольких клиентах. Например, интегратор, внешний подрядчик или ваш внутренний супер-админ. + +```ts +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][1]) + +--- + +### 3.4. ClientGroup / Группа внутри клиента + +Это не обязательно один-в-один Authentik group. Это бизнес-группа внутри клиента. + +```ts +ClientGroup { + id: string; + clientId: string; + name: string; + description?: string; + memberIds: string[]; +} +``` + +Примеры: + +* Руководство +* Менеджеры +* Бухгалтерия +* Демо-команда +* IT-администраторы + +Эта модель уже есть в документе бэкендеров: группы создаются внутри клиента, туда добавляются участники, и сервис может подключаться группе. + +--- + +### 3.5. Service / Сервис + +Сервис — это приложение в экосистеме. + +```ts +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 / Доступ к сервису + +Доступ должен быть трёхуровневым: + +```ts +ServiceGrant { + id: string; + serviceId: string; + targetType: "client" | "group" | "user"; + targetId: string; + appRole: "viewer" | "member" | "admin" | "owner"; + status: "active" | "disabled"; +} +``` + +Отдельно нужны исключения: + +```ts +ServiceAccessException { + id: string; + serviceId: string; + userId: string; + type: "deny" | "allow"; + reason?: string; +} +``` + +Правило вычисления: + +```txt +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. Пользовательский экран + +Композиция: + +```txt +┌─────────────────────────────────────────────┐ +│ 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**: + +```txt +[Launcher View] + ↓ +[Admin Command Center overlay / drawer] +``` + +Варианты: + +1. правая большая glass-панель; +2. полноэкранный matte glass overlay; +3. split view: слева список, справа детали; +4. command-center в стиле системной панели. + +Для MVP я бы делал **полноэкранный админский overlay** с внутренними вкладками. + +--- + +## 7. Разделы админки + +### Для root admin + +```txt +Администрирование +├── Клиенты +├── Участники +├── Группы +├── Каталог сервисов +├── Доступы +├── Инвайты +├── Синхронизация +└── Аудит +``` + +Это расширяет базовый документ, где уже есть разделы “Клиенты”, “Участники”, “Группы внутри клиента”, “Каталог сервисов”, “Доступы” и “Статус Authentik”. + +--- + +### Для client admin + +```txt +Администрирование компании +├── Участники +├── Группы +├── Доступы к сервисам +├── Инвайты +└── Профиль компании +``` + +--- + +## 8. Экран “Клиенты” + +Список клиентов: + +| Клиент | Тип | Статус | Участники | Сервисы | Демо до | Контакт | +| ----------- | ------------ | ------- | --------- | ------- | ------- | ------- | +| ООО Ромашка | Компания | Активен | 18 | 2 | — | Иван | +| Иван Петров | Частное лицо | Демо | 2 | 1 | 01.06 | Иван | + +Детальная карточка клиента: + +* название; +* тип; +* статус; +* демо-доступ; +* контактное лицо; +* заметки; +* участники; +* группы; +* подключенные сервисы; +* история действий; +* синхронизация. + +--- + +## 9. Экран “Каталог сервисов” + +Сервис — это настраиваемая витрина. + +Поля в форме: + +```txt +Название +Slug +Краткое описание +Полное описание +URL +Launch URL +Статус: активен / скрыт / техработы / отключен +Иконка +Обложка +Превью-видео +Ambient-видео +Цветовой акцент +Порядок отображения +Связанная группа Authentik +Связанное приложение Authentik +Доступен всем новым клиентам: да/нет +``` + +Очень важно: медиа нужно сразу предусмотреть мультиформатно: + +```ts +ServiceMedia { + icon?: AssetRef; + thumbnail?: AssetRef; + coverImage?: AssetRef; + previewVideo?: AssetRef; + ambientVideo?: AssetRef; + fallbackGradient?: string; +} +``` + +Поддержать: + +* `.png` +* `.jpg` +* `.webp` +* `.gif` +* `.mp4` +* `.webm` + +На фронте: + +```ts +type MediaKind = "image" | "video" | "gif" | "gradient"; +``` + +--- + +## 10. Экран “Доступы” + +Это ключевой экран. + +Я бы делал не просто таблицу, а **матрицу доступа**: + +```txt +Клиент: ООО Ромашка + +Участник Task Manager NodeDC 1C Assistant +--------------------------------------------------------- +Иван Admin Admin — +Вася Member — — +Лена Member Member Deny +Бухгалтерия Member — Member +``` + +И рядом объяснение effective access: + +```txt +Лена / NodeDC: +Нет доступа, потому что есть индивидуальное исключение deny. + +Вася / Task Manager: +Есть доступ, потому что сервис подключен всему клиенту. + +Пётр / 1C Assistant: +Есть доступ, потому что состоит в группе “Бухгалтерия”. +``` + +Это прямо соответствует требованию из PDF: надо отображать итоговый доступ пользователя и объяснять, почему он есть или отсутствует. + +--- + +## 11. Как это ложится на Plane / Task Manager + +Здесь лучше зафиксировать жёсткое правило: + +> Один клиент в Launcher = один workspace в Task Manager. + +Например: + +```txt +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 логика похожая, но глубже: + +```txt +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][3]) + +Рекомендуемая схема: + +```txt +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: + +```txt +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][4]) + +--- + +## 14. Что фронту делать сейчас без настоящего Authentik + +Фронт сейчас надо делать так, будто Authentik уже есть, но через mock contract. + +### Mock endpoint `GET /api/me` + +```ts +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` + +```ts +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-модель + +Я бы делал так: + +```ts +LauncherState { + mode: "user" | "admin"; + activeClientId: string; + selectedServiceId?: string; + serviceRail: LauncherServiceView[]; + me: MeResponse; +} +``` + +Для админки: + +```ts +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-компоненты + +### Базовые + +```txt +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 + +```txt +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. + +Радиусы можно взять напрямую: + +```css +--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 это можно перевести так: + +```txt +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 модели. + +Сценарий: + +```txt +1. Root admin создаёт клиента. +2. Root admin создаёт client_owner. +3. Client owner заходит в Launcher. +4. Client owner создаёт invite. +5. Пользователь переходит по одноразовой ссылке. +6. Authentik создаёт / активирует пользователя. +7. Launcher связывает пользователя с clientId. +8. Пользователь видит только сервисы, доступные ему. +``` + +Если позже появится свободная регистрация, то новый пользователь должен попадать в состояние: + +```txt +unassigned / pending / no services +``` + +То есть он вошёл, но ничего не видит, пока root admin не привяжет его к клиенту. + +--- + +## 21. Синхронизация + +В админке надо сразу предусмотреть статус: + +```ts +SyncStatus { + target: "authentik" | "task-manager" | "nodedc"; + status: "synced" | "pending" | "error"; + lastSyncAt?: string; + errorMessage?: string; +} +``` + +В PDF уже есть статусы Authentik: “синхронизировано”, “ожидает синхронизации”, “ошибка синхронизации”, а также действия “повторить” и “открыть детали ошибки”. + +В UI это нужно показывать не техническим логом, а понятной плашкой: + +```txt +Authentik: синхронизировано +Task Manager: ожидает создания workspace +NodeDC: ошибка синхронизации — открыть детали +``` + +--- + +## 22. Главные риски + +### Риск 1. Смешать роли Launcher и роли приложений + +Нельзя делать одну роль “admin” на всё. + +Нужно разделить: + +```txt +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 — первый проход + +Можно дать Кодексу так: + +```md +# ТЗ: 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. Мой вердикт по структуре + +Делать надо так: + +```txt +Launcher +├── витрина сервисов +├── клиентская админка +├── root-админка +├── каталог сервисов +├── access matrix +├── invite flow +├── sync status +└── SSO launch layer +``` + +А не так: + +```txt +просто React-страница с кнопками на разные сайты +``` + +Потому что у вас уже не набор сайтов, а экосистема: + +```txt +Информационный сайт +↓ +Launcher / Auth +↓ +NodeDC Agent Platform +↓ +Task Manager / Plane +↓ +дальше 1C, тендеры, digital twins, демки, внутренние сервисы +``` + +И Launcher становится тем самым верхним слоем, который делает из отдельных приложений единый веб-суперапп. + +[1]: https://docs.goauthentik.io/users-sources/groups/manage_groups/ "Manage groups | authentik" +[2]: https://docs.goauthentik.io/add-secure-apps/providers/property-mappings/ "Provider property mappings | authentik" +[3]: https://docs.goauthentik.io/add-secure-apps/applications/manage_apps/ "Manage applications | authentik" +[4]: https://docs.goauthentik.io/add-secure-apps/providers/oauth2/ "OAuth 2.0 provider | authentik" diff --git a/doc/base/VISUALREF.png b/doc/base/VISUALREF.png new file mode 100644 index 0000000..89c1550 Binary files /dev/null and b/doc/base/VISUALREF.png differ diff --git a/doc/base/nodedc_launcher_tz_frontend_mvp.md b/doc/base/nodedc_launcher_tz_frontend_mvp.md new file mode 100644 index 0000000..15fb0f8 --- /dev/null +++ b/doc/base/nodedc_launcher_tz_frontend_mvp.md @@ -0,0 +1,2352 @@ +# NODE.DC Launcher — техническое задание на фронтенд MVP и light-backend + +**Версия:** 0.1 +**Назначение документа:** согласование архитектуры Launcher с backend-командой и постановка задачи на разработку frontend/light-backend в Codex. +**Формат реализации:** отдельное одностраничное React-приложение. +**Текущий этап:** без подключения Authentik к runtime, но с заранее заложенной структурой под будущую Authentik/OIDC-интеграцию. + +--- + +## 1. Краткое описание продукта + +**NODE.DC Launcher** — отдельное веб-приложение, которое является единой точкой входа в экосистему NODE.DC. + +Launcher не является частью NodeDC Agent Platform и не является частью Task Manager. Это отдельный верхнеуровневый слой, который отвечает за: + +- вход пользователя в экосистему; +- отображение доступных пользователю сервисов; +- управление клиентами и пользователями; +- выдачу доступов к сервисам; +- подготовку структуры под Authentik/OIDC; +- запуск внешних приложений по SSO-ссылке; +- визуальную витрину сервисов NODE.DC. + +В экосистему могут входить разные приложения: + +- NodeDC Agent Platform; +- NODE.DC Task Manager; +- 1C Assistant; +- DM / Digital Modules; +- Tender Agent; +- Digital Twin Viewer; +- внутренние демо-сервисы; +- будущие сервисы, подключаемые через каталог Launcher. + +Важно: **Launcher управляет доступом к приложениям/сервисам, но не управляет внутренними сущностями этих приложений**. + +Например: + +- проекты внутри Task Manager / Plane не относятся к Launcher; +- workflow внутри NodeDC не относятся к Launcher; +- доски, задачи, проектные роли и внутренние настройки Task Manager остаются внутри Task Manager; +- sharing workflow, агентные сценарии и runtime-права остаются внутри NodeDC. + +Launcher даёт пользователю вход в приложение. Всё, что происходит внутри приложения после входа, является ответственностью самого приложения. + +--- + +## 2. Цель текущей разработки + +Создать первый полноценный frontend MVP Launcher, который можно развивать дальше как production-приложение. + +На текущем этапе нужно сделать: + +1. отдельное React-приложение; +2. одностраничную архитектуру без дополнительных публичных страниц; +3. пользовательский экран-витрину доступных сервисов; +4. root-admin режим для управления клиентами, пользователями, сервисами и доступами; +5. client-admin режим для управления пользователями и доступами внутри своей компании; +6. mock/light-backend API, который имитирует будущие данные Authentik и backend; +7. доменную модель, совместимую с будущей Authentik/OIDC-интеграцией; +8. визуальную систему в стиле NODE.DC glass-canon; +9. структуру проекта, пригодную для дальнейшей backend-интеграции. + +На текущем этапе **не требуется**: + +- подключать реальный Authentik; +- реализовывать реальный SSO; +- синхронизировать пользователей с Plane; +- синхронизировать пользователей с NodeDC; +- реализовывать реальные invite email; +- управлять проектами Plane; +- управлять workflow NodeDC; +- реализовывать billing; +- реализовывать production audit trail на backend. + +Но все эти направления должны быть предусмотрены архитектурно. + +--- + +## 3. Главная граница ответственности Launcher + +Launcher — это **Control Plane экосистемы**, а не замена внутренним админкам сервисов. + +### 3.1. Launcher отвечает за + +- кто является клиентом; +- какие пользователи относятся к клиенту; +- какие группы есть внутри клиента; +- какие сервисы подключены клиенту; +- какие сервисы видит конкретный пользователь; +- кто является админом клиента; +- какие сервисы доступны всем, группе или отдельному участнику; +- какие индивидуальные исключения применяются; +- какие приложения есть в каталоге; +- какой URL, иконка, описание, статус и медиа-превью у приложения; +- какой статус синхронизации с Authentik / внешними приложениями. + +### 3.2. Launcher не отвечает за + +- проекты внутри Task Manager / Plane; +- доски внутри Task Manager; +- задачи внутри Task Manager; +- роли пользователей внутри конкретных проектов Task Manager; +- workflow внутри NodeDC; +- sharing конкретных workflow внутри NodeDC; +- настройку конкретных агентов NodeDC; +- бизнес-логику 1C Assistant; +- внутренние настройки подключённых сервисов. + +### 3.3. Принцип разделения + +```txt +Launcher + управляет доступом к приложению + +Task Manager + управляет workspace, проектами, задачами, досками, внутренними ролями + +NodeDC + управляет workflow, агентами, sharing, runtime, execution traces + +1C Assistant + управляет своим набором данных, инструментов, сессий и прав +``` + +--- + +## 4. Общая архитектура экосистемы + +```txt +Информационный сайт + │ + ▼ +Authentik / OIDC + │ + ▼ +NODE.DC Launcher + │ + ├── NodeDC Agent Platform + ├── NODE.DC Task Manager + ├── 1C Assistant + ├── Tender Agent + ├── Digital Twin Viewer + ├── DM / Digital Modules + └── будущие сервисы +``` + +На текущем этапе Authentik не подключается физически, но приложение должно проектироваться так, чтобы позже подставить настоящий OIDC-профиль и claims без переписывания frontend-архитектуры. + +--- + +## 5. Термины + +| Термин | Описание | +|---|---| +| Клиент | Владелец доступа. Может быть компанией или частным лицом. | +| Компания | Клиент типа `company`. Например, ООО «Ромашка». | +| Частное лицо | Клиент типа `person`. Например, Иван Петров. | +| Участник | Пользователь внутри клиента. | +| Группа | Подгруппа участников внутри клиента. Например, «Бухгалтерия», «Менеджеры», «Руководство». | +| Сервис | Приложение, доступное через Launcher. Например, NodeDC, Task Manager, 1C Assistant. | +| Доступ | Правило, которое определяет, кому показывать и разрешать сервис. | +| Исключение | Индивидуальное правило, которое перекрывает общий доступ. Например, сервис включён всему клиенту, но конкретному пользователю отключён. | +| Root Admin | Главный администратор NODE.DC, который видит всех клиентов, пользователей и сервисы. | +| Client Admin | Администратор конкретного клиента. | +| Member | Обычный пользователь клиента. | +| Service Catalog | Каталог подключаемых сервисов. | +| Service Rail | Нижняя карусель/панель доступных пользователю сервисов. | +| Service Stage | Центральная визуальная зона выбранного сервиса. | + +--- + +## 6. Роли и уровни прав + +### 6.1. Глобальные роли Launcher + +```ts +export type LauncherGlobalRole = + | "root_admin" + | "support_admin" + | "client_owner" + | "client_admin" + | "member"; +``` + +### 6.2. Root Admin NODE.DC + +Root Admin — внутренний супер-администратор NODE.DC. + +Он может: + +- видеть всех клиентов; +- создавать клиентов; +- редактировать клиентов; +- блокировать клиентов; +- видеть всех пользователей; +- создавать пользователей; +- блокировать пользователей; +- создавать сервисы; +- редактировать каталог сервисов; +- выдавать доступы любому клиенту, группе или участнику; +- создавать исключения; +- видеть статусы синхронизации; +- повторять синхронизацию; +- смотреть детали ошибок; +- видеть скрытые и отключённые сервисы; +- управлять демо-доступами. + +### 6.3. Support Admin + +Опциональная внутренняя роль NODE.DC. + +На MVP можно не реализовывать отдельно, но архитектурно предусмотреть. + +Может использоваться для сотрудников поддержки, которым нужно видеть клиентов и помогать с настройкой, но не менять критические системные параметры. + +### 6.4. Client Owner + +Главный администратор клиента. + +Может: + +- видеть только свою компанию / клиента; +- приглашать пользователей; +- деактивировать пользователей внутри своей компании; +- создавать группы внутри клиента; +- добавлять пользователей в группы; +- назначать client_admin; +- выдавать пользователям доступ к тем сервисам, которые уже разрешены клиенту; +- смотреть итоговые доступы пользователей; +- управлять invite-ссылками; +- видеть статусы синхронизации по своей компании. + +Не может: + +- видеть других клиентов; +- создавать глобальные сервисы; +- менять URL сервисов; +- редактировать технические slug сервисов; +- выдавать доступ к сервису, который не подключен клиенту; +- управлять root-admin пользователями; +- напрямую менять Authentik-конфигурацию. + +### 6.5. Client Admin + +Администратор внутри клиента. Права похожи на Client Owner, но могут быть ограничены. + +На MVP можно сделать Client Owner и Client Admin одинаковыми по интерфейсу, но в типах оставить разные роли. + +### 6.6. Member + +Обычный пользователь. + +Может: + +- войти в Launcher; +- видеть только доступные ему сервисы; +- открыть доступный сервис; +- открыть профиль; +- выйти из аккаунта; +- выбрать клиента, если пользователь состоит в нескольких клиентах. + +Не может: + +- открывать админку; +- видеть пользователей; +- видеть клиентов; +- выдавать доступы; +- видеть скрытые сервисы; +- редактировать каталог сервисов. + +--- + +## 7. Матрица прав + +| Действие | Root Admin | Client Owner | Client Admin | Member | +|---|---:|---:|---:|---:| +| Видеть все сервисы каталога | Да | Нет | Нет | Нет | +| Видеть доступные себе сервисы | Да | Да | Да | Да | +| Видеть скрытые сервисы | Да | Нет | Нет | Нет | +| Создать клиента | Да | Нет | Нет | Нет | +| Редактировать клиента | Да | Только свой профиль клиента | Только свой профиль клиента | Нет | +| Заблокировать клиента | Да | Нет | Нет | Нет | +| Видеть всех пользователей | Да | Нет | Нет | Нет | +| Видеть пользователей своего клиента | Да | Да | Да | Нет | +| Приглашать пользователей | Да | Да | Да | Нет | +| Деактивировать пользователя клиента | Да | Да | Да | Нет | +| Назначить Client Admin | Да | Да | Опционально | Нет | +| Создать группу внутри клиента | Да | Да | Да | Нет | +| Подключить сервис клиенту | Да | Нет | Нет | Нет | +| Подключить доступ к разрешённому сервису группе | Да | Да | Да | Нет | +| Подключить доступ к разрешённому сервису пользователю | Да | Да | Да | Нет | +| Создать deny-исключение | Да | Да | Да | Нет | +| Создать сервис в каталоге | Да | Нет | Нет | Нет | +| Редактировать сервис | Да | Нет | Нет | Нет | +| Открыть доступный сервис | Да | Да | Да | Да | +| Смотреть статусы синхронизации | Да | По своему клиенту | По своему клиенту | Нет | + +--- + +## 8. Доменная модель + +### 8.1. Client + +```ts +export type ClientType = "company" | "person"; +export type ClientStatus = "active" | "suspended" | "demo" | "expired"; + +export interface Client { + id: string; + type: ClientType; + name: string; + legalName?: string; + status: ClientStatus; + demoEndsAt?: string | null; + contactName?: string | null; + contactEmail?: string | null; + notes?: string | null; + createdAt: string; + updatedAt: string; +} +``` + +### 8.2. LauncherUser + +```ts +export type LauncherUserStatus = "invited" | "active" | "blocked"; + +export interface LauncherUser { + id: string; + authentikUserId?: string | null; + email: string; + name: string; + avatarUrl?: string | null; + globalStatus: LauncherUserStatus; + createdAt: string; + updatedAt: string; +} +``` + +На текущем этапе `authentikUserId` может быть mock-полем. После интеграции оно должно ссылаться на пользователя Authentik. + +### 8.3. ClientMembership + +```ts +export type ClientMembershipRole = + | "client_owner" + | "client_admin" + | "member"; + +export type ClientMembershipStatus = "active" | "disabled"; + +export interface ClientMembership { + id: string; + clientId: string; + userId: string; + role: ClientMembershipRole; + status: ClientMembershipStatus; + createdAt: string; + updatedAt: string; +} +``` + +Один пользователь потенциально может быть участником нескольких клиентов. + +### 8.4. ClientGroup + +```ts +export interface ClientGroup { + id: string; + clientId: string; + name: string; + description?: string | null; + memberIds: string[]; + createdAt: string; + updatedAt: string; +} +``` + +Группа всегда существует внутри клиента. Группа не является глобальной. + +Примеры групп: + +- Руководство; +- Менеджеры; +- Бухгалтерия; +- Демо-команда; +- IT-администраторы. + +### 8.5. Service + +```ts +export type ServiceStatus = + | "active" + | "maintenance" + | "hidden" + | "disabled"; + +export interface Service { + id: string; + slug: string; + title: string; + subtitle?: string | null; + description: string; + fullDescription?: string | null; + url: string; + launchUrl?: string | null; + iconUrl?: string | null; + coverImageUrl?: string | null; + previewVideoUrl?: string | null; + ambientVideoUrl?: string | null; + accentColor?: string | null; + status: ServiceStatus; + order: number; + authentikApplicationSlug?: string | null; + authentikGroupName?: string | null; + isAvailableForAllNewClients?: boolean; + createdAt: string; + updatedAt: string; +} +``` + +### 8.6. Service media + +```ts +export type MediaKind = "image" | "video" | "gif" | "gradient"; + +export interface ServiceMedia { + kind: MediaKind; + url?: string; + posterUrl?: string; + fallbackGradient?: string; +} +``` + +Поддерживаемые форматы: + +- png; +- jpg; +- jpeg; +- webp; +- gif; +- mp4; +- webm. + +### 8.7. ServiceGrant + +```ts +export type ServiceGrantTargetType = "client" | "group" | "user"; +export type ServiceAppRole = "viewer" | "member" | "admin" | "owner"; +export type ServiceGrantStatus = "active" | "disabled"; + +export interface ServiceGrant { + id: string; + serviceId: string; + targetType: ServiceGrantTargetType; + targetId: string; + appRole: ServiceAppRole; + status: ServiceGrantStatus; + createdAt: string; + updatedAt: string; +} +``` + +### 8.8. ServiceAccessException + +```ts +export type ServiceAccessExceptionType = "deny" | "allow"; + +export interface ServiceAccessException { + id: string; + serviceId: string; + userId: string; + type: ServiceAccessExceptionType; + reason?: string | null; + createdAt: string; + updatedAt: string; +} +``` + +Приоритет: + +1. `deny` exception перекрывает любой общий доступ; +2. `allow` exception может точечно выдать доступ пользователю, если это разрешено бизнес-логикой; +3. direct user grant сильнее group/client grant по роли, но не сильнее `deny`; +4. если сервис hidden, обычный пользователь его не видит; +5. если сервис maintenance, пользователь может видеть карточку, но не может открыть сервис; +6. если клиент suspended/expired, сервисы не открываются. + +### 8.9. Invite + +```ts +export type InviteStatus = + | "created" + | "sent" + | "accepted" + | "expired" + | "revoked"; + +export interface Invite { + id: string; + clientId: string; + email: string; + role: ClientMembershipRole; + invitedByUserId: string; + token: string; + expiresAt: string; + status: InviteStatus; + createdAt: string; + updatedAt: string; +} +``` + +На MVP можно не отправлять реальные email. Достаточно генерировать mock invite-ссылку и показывать её в интерфейсе. + +### 8.10. SyncStatus + +```ts +export type SyncTarget = "authentik" | "task_manager" | "nodedc" | "service"; +export type SyncState = "synced" | "pending" | "error" | "disabled"; + +export interface SyncStatus { + id: string; + target: SyncTarget; + entityType: "user" | "client" | "service" | "grant"; + entityId: string; + status: SyncState; + lastSyncAt?: string | null; + errorMessage?: string | null; + retryAvailable: boolean; +} +``` + +--- + +## 9. Логика вычисления итогового доступа + +### 9.1. Общий принцип + +Пользователь видит сервис, если: + +```txt +клиент активен +AND пользователь активен +AND сервис активен или находится на техработах +AND у пользователя есть доступ через клиента, группу или персональное правило +AND нет deny-исключения +``` + +### 9.2. Алгоритм + +```ts +export interface EffectiveAccessResult { + serviceId: string; + userId: string; + allowed: boolean; + visible: boolean; + openEnabled: boolean; + appRole?: ServiceAppRole; + reason: string; + source?: "client" | "group" | "user" | "exception"; + sourceId?: string; +} +``` + +```ts +export function computeEffectiveAccess(input: { + client: Client; + user: LauncherUser; + membership: ClientMembership; + userGroups: ClientGroup[]; + service: Service; + grants: ServiceGrant[]; + exceptions: ServiceAccessException[]; +}): EffectiveAccessResult { + // 1. Клиент заблокирован или истёк + if (input.client.status === "suspended" || input.client.status === "expired") { + return { + serviceId: input.service.id, + userId: input.user.id, + allowed: false, + visible: false, + openEnabled: false, + reason: "Клиент приостановлен или срок доступа истёк", + }; + } + + // 2. Пользователь заблокирован + if (input.user.globalStatus === "blocked" || input.membership.status === "disabled") { + return { + serviceId: input.service.id, + userId: input.user.id, + allowed: false, + visible: false, + openEnabled: false, + reason: "Пользователь заблокирован или отключён внутри клиента", + }; + } + + // 3. Сервис отключён + if (input.service.status === "disabled") { + return { + serviceId: input.service.id, + userId: input.user.id, + allowed: false, + visible: false, + openEnabled: false, + reason: "Сервис отключён", + }; + } + + // 4. Сервис скрыт + if (input.service.status === "hidden") { + return { + serviceId: input.service.id, + userId: input.user.id, + allowed: false, + visible: false, + openEnabled: false, + reason: "Сервис скрыт", + }; + } + + // 5. Deny exception + const deny = input.exceptions.find( + (item) => item.serviceId === input.service.id && item.userId === input.user.id && item.type === "deny" + ); + + if (deny) { + return { + serviceId: input.service.id, + userId: input.user.id, + allowed: false, + visible: false, + openEnabled: false, + source: "exception", + sourceId: deny.id, + reason: "Доступ отключён индивидуальным исключением", + }; + } + + // 6. Direct user grant + const userGrant = input.grants.find( + (grant) => + grant.serviceId === input.service.id && + grant.targetType === "user" && + grant.targetId === input.user.id && + grant.status === "active" + ); + + if (userGrant) { + return { + serviceId: input.service.id, + userId: input.user.id, + allowed: true, + visible: true, + openEnabled: input.service.status === "active", + appRole: userGrant.appRole, + source: "user", + sourceId: userGrant.id, + reason: "Доступ выдан пользователю напрямую", + }; + } + + // 7. Group grant + const groupIds = input.userGroups.map((group) => group.id); + const groupGrant = input.grants.find( + (grant) => + grant.serviceId === input.service.id && + grant.targetType === "group" && + groupIds.includes(grant.targetId) && + grant.status === "active" + ); + + if (groupGrant) { + return { + serviceId: input.service.id, + userId: input.user.id, + allowed: true, + visible: true, + openEnabled: input.service.status === "active", + appRole: groupGrant.appRole, + source: "group", + sourceId: groupGrant.id, + reason: "Доступ выдан группе пользователя", + }; + } + + // 8. Client grant + const clientGrant = input.grants.find( + (grant) => + grant.serviceId === input.service.id && + grant.targetType === "client" && + grant.targetId === input.client.id && + grant.status === "active" + ); + + if (clientGrant) { + return { + serviceId: input.service.id, + userId: input.user.id, + allowed: true, + visible: true, + openEnabled: input.service.status === "active", + appRole: clientGrant.appRole, + source: "client", + sourceId: clientGrant.id, + reason: "Доступ выдан всему клиенту", + }; + } + + // 9. No access + return { + serviceId: input.service.id, + userId: input.user.id, + allowed: false, + visible: false, + openEnabled: false, + reason: "Доступ к сервису не выдан", + }; +} +``` + +--- + +## 10. Связка с Authentik + +### 10.1. Принцип + +Authentik должен использоваться как identity provider и SSO-слой. + +Launcher Backend должен оставаться владельцем бизнес-логики: + +- клиенты; +- клиентские группы; +- каталог сервисов; +- доступы; +- исключения; +- демо-периоды; +- статусы клиентов; +- описание сервисов; +- порядок отображения сервисов. + +Authentik должен отвечать за: + +- пользователей; +- аутентификацию; +- OIDC; +- SSO; +- application bindings; +- группы/claims, необходимые приложениям; +- передачу identity в подключённые сервисы. + +### 10.2. Почему бизнес-логику нельзя полностью отдавать в Authentik + +В Launcher есть бизнес-сущности, которые неудобно и неправильно хранить только в Authentik: + +- клиент как юридическая/коммерческая сущность; +- демо-доступ; +- контактное лицо; +- заметки администратора; +- медиа-превью сервиса; +- порядок отображения сервисов; +- статус сервиса в витрине; +- индивидуальные исключения; +- explanation итогового доступа; +- синхронизация с Task Manager и NodeDC. + +Поэтому Authentik должен быть нижним identity-слоем, а Launcher — продуктовым control-plane. + +### 10.3. Ожидаемые Authentik-группы + +Примерная схема групп: + +```txt +authentik +├── nodedc-root-admins +├── nodedc-support-admins +├── clients +│ ├── client-romashka +│ │ ├── client-romashka-admins +│ │ ├── client-romashka-users +│ │ ├── service-task-manager-romashka +│ │ └── service-nodedc-romashka +│ └── client-roga-kopyta +│ ├── client-roga-kopyta-admins +│ ├── client-roga-kopyta-users +│ ├── service-task-manager-roga-kopyta +│ └── service-nodedc-roga-kopyta +``` + +Это не финальная обязательная структура, но frontend и backend должны быть готовы к тому, что пользователь будет иметь: + +- Authentik user id; +- email; +- имя; +- аватар; +- список групп; +- список claims; +- список доступных приложений/entitlements. + +### 10.4. Ожидаемые OIDC claims + +```ts +export interface AuthentikClaimsMock { + sub: string; + email: string; + name: string; + preferred_username?: string; + picture?: string; + groups?: string[]; + entitlements?: string[]; +} +``` + +На MVP эти claims должны имитироваться mock API. + +### 10.5. Важное правило для приложений Authentik + +Для production-интеграции важно не оставлять приложения без явных bindings, потому что приложение без bindings может быть доступно шире, чем требуется. Поэтому service access должен синхронизироваться с Authentik через явные группы/application bindings. + +### 10.6. Статусы синхронизации + +В Launcher UI должны быть предусмотрены статусы: + +- синхронизировано; +- ожидает синхронизации; +- ошибка синхронизации; +- отключено. + +Для ошибки должны быть действия: + +- повторить; +- открыть детали ошибки. + +--- + +## 11. Связка с Task Manager + +### 11.1. Главное правило + +Launcher управляет только доступом к приложению Task Manager. + +Launcher **не управляет**: + +- проектами внутри Task Manager; +- досками; +- задачами; +- проектными ролями; +- внутренним разделением на контуры; +- внутренней логикой Plane. + +### 11.2. Пользовательский сценарий + +```txt +1. Root Admin создаёт клиента ООО «Ромашка». +2. Root Admin подключает клиенту сервис Task Manager. +3. Client Owner приглашает пользователей. +4. Client Owner выдаёт нужным пользователям доступ к Task Manager. +5. Пользователь входит в Launcher. +6. Пользователь видит карточку Task Manager. +7. Пользователь нажимает «Открыть». +8. Пользователь попадает в Task Manager под тем же аккаунтом. +9. Всё дальнейшее управление проектами, досками и задачами происходит внутри Task Manager. +``` + +### 11.3. Возможная backend-интеграция в будущем + +В будущем backend может автоматически: + +- создавать workspace клиента в Task Manager; +- добавлять пользователей клиента в workspace; +- деактивировать пользователей; +- передавать роль admin/member; +- удалять доступ при отключении сервиса. + +Но это не должно быть frontend-зависимостью MVP. + +--- + +## 12. Связка с NodeDC Agent Platform + +### 12.1. Главное правило + +Launcher управляет только доступом к приложению NodeDC. + +Launcher **не управляет**: + +- workflow; +- agent nodes; +- sharing конкретных workflow; +- execution traces; +- runtime запуском; +- настройками нод; +- внутренними credential checks; +- агентной бизнес-логикой. + +### 12.2. Пользовательский сценарий + +```txt +1. Root Admin подключает клиенту сервис NodeDC. +2. Client Owner выдаёт пользователю доступ к NodeDC. +3. Пользователь видит NodeDC в Launcher. +4. Пользователь открывает NodeDC. +5. Внутри NodeDC он видит только те workflow/ресурсы, которые ему выданы уже механизмами NodeDC. +``` + +### 12.3. Возможная backend-интеграция в будущем + +Backend может передавать в NodeDC: + +- user id; +- client id; +- app role; +- группы пользователя; +- признак client admin; +- service entitlement. + +--- + +## 13. UX-концепция пользовательского Launcher + +### 13.1. Общая композиция + +Пользовательский экран — это не таблица и не обычная админка, а визуальная витрина сервисов. + +Композиция: + +```txt +┌──────────────────────────────────────────────────────────┐ +│ Logo / NODE.DC Client / Profile / Admin │ +│ │ +│ │ +│ Service Stage / Ambient Media │ +│ │ +│ ┌────────────────────────┐ │ +│ │ Glass Service Detail │ │ +│ │ Название │ │ +│ │ Описание │ │ +│ │ Статус │ │ +│ │ [Открыть] │ │ +│ └────────────────────────┘ │ +│ │ +│ │ +│ [NodeDC] [Task Manager] [1C] [DM] [Tender] │ +└──────────────────────────────────────────────────────────┘ +``` + +### 13.2. Основные зоны + +1. **Top Bar** + Верхняя панель с логотипом, клиентом, профилем, кнопкой администрирования. + +2. **Service Stage** + Центральная зона выбранного сервиса. Может показывать видео, картинку, gif или градиент. + +3. **Service Detail Glass Card** + Стеклянная карточка с описанием выбранного сервиса. + +4. **Service Rail** + Нижняя карусель доступных сервисов. + +5. **Profile Menu** + Меню пользователя. + +6. **Admin Overlay** + Панель администрирования, если у пользователя есть права. + +### 13.3. Service Rail + +Service Rail — нижняя карусель приложений. + +Важно: это не проекты Plane и не контуры внутри Task Manager. Это верхнеуровневые приложения экосистемы. + +Примеры карточек в rail: + +- NodeDC; +- Task Manager; +- 1C; +- DM; +- Tender Agent; +- Digital Twin; +- Demo Gallery. + +### 13.4. Карточка сервиса в rail + +Карточка должна содержать: + +- иконку или thumbnail; +- название; +- короткий subtitle; +- статус; +- состояние active/selected; +- disabled-состояние для техработ; +- soft glass hover. + +### 13.5. Поведение выбора сервиса + +При выборе сервиса: + +1. карточка становится активной; +2. центральный фон меняется на media выбранного сервиса; +3. detail-card обновляет название и описание; +4. кнопка «Открыть» обновляет URL; +5. если сервис на техработах, кнопка disabled; +6. если у пользователя нет доступа, сервис не показывается; +7. root-admin может видеть скрытые/disabled сервисы с бейджами. + +### 13.6. Кнопка открытия + +Текст кнопки: + +- `Открыть` — для активного сервиса; +- `На техработах` — для maintenance; +- `Нет доступа` — если карточка показывается администратору в диагностическом режиме; +- `Скрыт` — для hidden-сервиса в root-admin preview. + +--- + +## 14. Админка Launcher + +### 14.1. Формат + +Админка должна быть реализована как overlay внутри одностраничного приложения. + +Не нужно делать отдельную публичную страницу. Допустимо внутреннее состояние `mode = "admin"`, но приложение остаётся SPA. + +Вариант UX: + +```txt +Launcher View + ↓ click «Администрирование» +Full-screen Admin Overlay + ↓ close +Launcher View +``` + +### 14.2. Разделы root-admin + +```txt +Администрирование NODE.DC +├── Обзор +├── Клиенты +├── Участники +├── Группы +├── Каталог сервисов +├── Доступы +├── Инвайты +├── Синхронизация +└── Аудит +``` + +### 14.3. Разделы client-admin + +```txt +Администрирование компании +├── Обзор +├── Участники +├── Группы +├── Доступы к сервисам +├── Инвайты +└── Профиль компании +``` + +### 14.4. Раздел «Обзор» + +Для root-admin: + +- количество клиентов; +- количество активных клиентов; +- количество пользователей; +- количество сервисов; +- ошибки синхронизации; +- клиенты с истекающим demo; +- последние действия. + +Для client-admin: + +- количество пользователей в компании; +- доступные сервисы компании; +- активные приглашения; +- ошибки синхронизации по клиенту. + +--- + +## 15. Раздел «Клиенты» + +Доступен root-admin. + +### 15.1. Список клиентов + +Поля таблицы: + +- название; +- тип; +- статус; +- количество участников; +- количество подключённых сервисов; +- срок demo; +- контакт; +- последнее обновление; +- действия. + +### 15.2. Карточка клиента + +Поля: + +- название; +- юридическое название; +- тип клиента; +- статус; +- срок demo; +- контактное лицо; +- email контакта; +- заметки; +- участники; +- группы; +- подключённые сервисы; +- исключения; +- sync status. + +### 15.3. Действия + +- создать клиента; +- редактировать клиента; +- приостановить клиента; +- активировать клиента; +- продлить demo; +- открыть доступы клиента; +- открыть участников клиента; +- открыть sync details. + +--- + +## 16. Раздел «Участники» + +### 16.1. Для root-admin + +Показывает всех пользователей всех клиентов. + +Поля: + +- имя; +- email; +- клиент/клиенты; +- статус; +- роль; +- доступные сервисы; +- дата создания; +- действия. + +### 16.2. Для client-admin + +Показывает только пользователей своего клиента. + +Поля: + +- имя; +- email; +- роль внутри клиента; +- группы; +- доступные сервисы; +- статус; +- действия. + +### 16.3. Действия + +- добавить участника; +- отправить invite; +- деактивировать; +- активировать; +- назначить client_admin; +- убрать роль client_admin; +- посмотреть доступы; +- открыть профиль участника. + +--- + +## 17. Раздел «Группы» + +Группы всегда создаются внутри клиента. + +### 17.1. Поля группы + +- название; +- описание; +- количество участников; +- подключённые сервисы; +- дата создания. + +### 17.2. Действия + +- создать группу; +- переименовать группу; +- удалить группу; +- добавить участников; +- удалить участников; +- подключить группе сервис; +- отключить группе сервис. + +### 17.3. Пример + +```txt +Клиент: ООО «Ромашка» + +Группы: +- Руководство +- Менеджеры +- Бухгалтерия +- Демо-команда +``` + +--- + +## 18. Раздел «Каталог сервисов» + +Доступен root-admin. + +### 18.1. Список сервисов + +Поля: + +- название; +- slug; +- статус; +- URL; +- Authentik application slug; +- Authentik group name; +- порядок отображения; +- количество клиентов с доступом; +- действия. + +### 18.2. Форма сервиса + +Поля: + +```txt +Название +Subtitle +Slug +Краткое описание +Полное описание +URL +Launch URL +Статус +Иконка +Thumbnail +Cover image +Preview video +Ambient video +Fallback gradient +Accent color +Порядок отображения +Authentik application slug +Authentik group name +Доступен всем новым клиентам +``` + +### 18.3. Статусы сервиса + +| Статус | Поведение | +|---|---| +| active | Показывается пользователям с доступом, можно открыть. | +| maintenance | Показывается пользователям с доступом, открыть нельзя, кнопка disabled. | +| hidden | Обычным пользователям не показывается. Root-admin видит в режиме администрирования. | +| disabled | Сервис отключён полностью, не открывается. | + +### 18.4. Медиа + +Сервис может иметь несколько типов медиа: + +- иконка для карточки; +- thumbnail для rail; +- cover image для central stage; +- preview video для карточки; +- ambient video для фоновой анимации; +- fallback gradient, если медиа нет. + +Frontend должен иметь fallback-логику: + +```txt +ambientVideoUrl + ↓ если нет +previewVideoUrl + ↓ если нет +coverImageUrl + ↓ если нет +fallbackGradient + ↓ если нет +системный абстрактный background +``` + +--- + +## 19. Раздел «Доступы» + +### 19.1. Главная идея + +Доступы должны отображаться не только как CRUD-список, а как понятная матрица. + +Администратор должен видеть: + +- кому выдан сервис; +- почему пользователь имеет доступ; +- откуда пришёл доступ: клиент, группа, пользователь; +- есть ли исключение; +- какая итоговая роль в приложении. + +### 19.2. Матрица доступа + +```txt +Клиент: ООО «Ромашка» + +Участник Task Manager NodeDC 1C Assistant +----------------------------------------------------------------- +Иван Admin / client Admin / user — +Вася Member / client — — +Лена Deny exception Member / group Member / group +Бухгалтерия Member — Member +``` + +### 19.3. Explanation panel + +При выборе ячейки показывать объяснение: + +```txt +Вася / Task Manager +Есть доступ. +Причина: сервис подключён всему клиенту. +Роль: member. +Источник: client grant. +``` + +```txt +Лена / Task Manager +Нет доступа. +Причина: индивидуальное deny-исключение перекрывает доступ клиента. +``` + +### 19.4. Действия + +- выдать сервис всему клиенту; +- выдать сервис группе; +- выдать сервис пользователю; +- убрать доступ; +- создать deny-исключение; +- удалить deny-исключение; +- изменить appRole; +- открыть sync details. + +--- + +## 20. Раздел «Инвайты» + +### 20.1. MVP flow + +```txt +1. Client Admin открывает раздел «Инвайты». +2. Вводит email пользователя. +3. Выбирает роль: member / client_admin. +4. Создаёт invite. +5. Система генерирует одноразовую ссылку. +6. Админ копирует ссылку. +7. Пользователь переходит по ссылке. +8. В production Authentik создаёт/активирует пользователя. +9. Launcher связывает пользователя с клиентом. +``` + +### 20.2. На MVP + +Можно не делать реальную регистрацию. Достаточно: + +- создать invite в mock-store; +- показать ссылку; +- показать статус invite; +- дать revoke action; +- дать expire simulation. + +--- + +## 21. Раздел «Синхронизация» + +### 21.1. Цель + +Показывать, что данные Launcher должны быть синхронизированы с внешними системами. + +На MVP это mock-status, но UI должен быть production-like. + +### 21.2. Цели синхронизации + +- Authentik; +- Task Manager; +- NodeDC; +- сервисы будущих модулей. + +### 21.3. UI + +Поля: + +- объект; +- тип объекта; +- целевая система; +- статус; +- последнее обновление; +- ошибка; +- действия. + +Действия: + +- повторить; +- открыть детали; +- скопировать ошибку. + +--- + +## 22. Раздел «Аудит» + +На MVP можно сделать mock/read-only. + +Поля: + +- дата; +- пользователь; +- действие; +- объект; +- клиент; +- результат; +- details. + +Примеры событий: + +- создан клиент; +- пользователь приглашён; +- сервис подключён клиенту; +- сервис отключён пользователю; +- создано deny-исключение; +- ошибка синхронизации; +- повторная синхронизация запущена. + +--- + +## 23. Frontend API contracts + +### 23.1. GET /api/me + +```ts +export interface MeResponse { + user: { + id: string; + authentikUserId?: string | null; + name: string; + email: string; + avatarUrl?: string | null; + }; + launcherRole: LauncherGlobalRole; + memberships: Array<{ + clientId: string; + clientName: string; + role: ClientMembershipRole; + status: ClientMembershipStatus; + }>; + activeClientId: string; + permissions: { + canOpenAdmin: boolean; + canManageClients: boolean; + canManageOwnClient: boolean; + canManageServiceCatalog: boolean; + canInviteUsers: boolean; + canManageAccess: boolean; + canViewSync: boolean; + }; + mockAuthentikClaims?: AuthentikClaimsMock; +} +``` + +### 23.2. GET /api/launcher/services + +Возвращает только сервисы, доступные пользователю в рамках активного клиента. + +```ts +export interface LauncherServiceView { + id: string; + slug: string; + title: string; + subtitle?: string | null; + description: string; + status: ServiceStatus; + userAccess: "allowed" | "denied"; + appRole?: ServiceAppRole; + openUrl?: string | null; + media: { + icon?: string | null; + thumbnail?: string | null; + coverImage?: string | null; + previewVideo?: string | null; + ambientVideo?: string | null; + fallbackGradient?: string | null; + }; + effectiveAccess: EffectiveAccessResult; +} +``` + +### 23.3. GET /api/admin/clients + +Root-admin only. + +```ts +export interface AdminClientsResponse { + clients: Array; +} +``` + +### 23.4. GET /api/admin/clients/:clientId + +```ts +export interface AdminClientDetailsResponse { + client: Client; + memberships: Array; + groups: ClientGroup[]; + serviceGrants: ServiceGrant[]; + exceptions: ServiceAccessException[]; + syncStatuses: SyncStatus[]; +} +``` + +### 23.5. GET /api/admin/services + +```ts +export interface AdminServicesResponse { + services: Service[]; +} +``` + +### 23.6. GET /api/admin/access-matrix?clientId=... + +```ts +export interface AccessMatrixResponse { + client: Client; + users: LauncherUser[]; + groups: ClientGroup[]; + services: Service[]; + cells: Array<{ + userId: string; + serviceId: string; + effectiveAccess: EffectiveAccessResult; + }>; +} +``` + +### 23.7. POST /api/admin/service-grants + +```ts +export interface CreateServiceGrantRequest { + serviceId: string; + targetType: ServiceGrantTargetType; + targetId: string; + appRole: ServiceAppRole; +} +``` + +### 23.8. POST /api/admin/service-exceptions + +```ts +export interface CreateServiceExceptionRequest { + serviceId: string; + userId: string; + type: ServiceAccessExceptionType; + reason?: string; +} +``` + +### 23.9. POST /api/admin/invites + +```ts +export interface CreateInviteRequest { + clientId: string; + email: string; + role: ClientMembershipRole; +} +``` + +--- + +## 24. Frontend state model + +### 24.1. Launcher state + +```ts +export interface LauncherState { + mode: "user" | "admin"; + activeClientId: string; + selectedServiceId?: string; + services: LauncherServiceView[]; + me?: MeResponse; + isAdminOverlayOpen: boolean; +} +``` + +### 24.2. Admin state + +```ts +export type AdminSection = + | "overview" + | "clients" + | "users" + | "groups" + | "services" + | "access" + | "invites" + | "sync" + | "audit"; + +export interface AdminState { + activeSection: AdminSection; + selectedClientId?: string; + selectedUserId?: string; + selectedServiceId?: string; + selectedAccessCell?: { + userId: string; + serviceId: string; + }; +} +``` + +### 24.3. Рекомендуемые библиотеки + +- React; +- TypeScript; +- Tailwind CSS; +- Zustand для локального UI-state; +- TanStack Query для API-state; +- React Router опционально, но без обязательных страниц; +- Framer Motion для мягких переходов; +- Radix UI или Headless UI для accessibility-основы, если уже принято в проекте; +- собственные shared components для glass-canon. + +--- + +## 25. UI component map + +### 25.1. App shell + +```txt +LauncherApp +├── LauncherProvider +├── AppBackground +├── TopBar +├── ServiceStage +├── ServiceDetailCard +├── ServiceRail +├── ProfileMenu +└── AdminOverlay +``` + +### 25.2. User components + +```txt +TopBar +ClientSwitcher +ProfileAvatar +ProfileMenu +ServiceRail +ServiceTile +ServiceStatusBadge +ServiceStage +ServiceMediaRenderer +ServiceDetailGlassCard +LaunchServiceButton +EmptyServicesState +MaintenanceState +``` + +### 25.3. Admin components + +```txt +AdminOverlay +AdminSidebar +AdminSectionHeader +AdminOverview +ClientsTable +ClientDetailsPanel +UsersTable +UserDetailsPanel +GroupsTable +GroupEditor +ServicesTable +ServiceEditor +AccessMatrix +AccessCell +AccessExplanationPanel +InviteList +InviteCreateModal +SyncStatusList +SyncErrorDetailsModal +AuditLogTable +``` + +### 25.4. Shared components + +```txt +GlassSurface +GlassCard +GlassModal +GlassDrawer +AccentButton +SecondaryButton +DangerButton +RoundIconButton +Checker +Chip +StatusBadge +PortalDropdown +SearchField +SegmentedControl +MediaUploadField +MediaPreview +ConfirmDialog +``` + +--- + +## 26. Design system требования + +### 26.1. Общий стиль + +Launcher должен продолжать визуальный язык NODE.DC: + +- тёмный интерфейс; +- matte glass; +- blur/backdrop-filter; +- мягкие стеклянные границы; +- крупные радиусы; +- без жёстких outline; +- без синих browser outline; +- акцентные круглые элементы; +- мягкие hover/focus surface; +- русскоязычные подписи; +- единые shared-компоненты. + +### 26.2. Радиусы + +```css +:root { + --launcher-radius-modal: 1.75rem; + --launcher-radius-card: 1.35rem; + --launcher-radius-control: 1.25rem; + --launcher-radius-circle: 999px; +} +``` + +### 26.3. Цвета + +Использовать CSS variables, совместимые с текущим дизайн-кодом: + +```css +:root { + --nodedc-accent-rgb: 181 255 90; + --nodedc-card-passive-rgb: 24 27 31; + --nodedc-card-active-rgb: 181 255 90; + --nodedc-on-accent-rgb: 11 17 23; +} +``` + +### 26.4. Glass surface + +```css +.launcher-glass-surface { + background: rgba(10, 12, 16, 0.62); + backdrop-filter: blur(28px); + -webkit-backdrop-filter: blur(28px); + border: 1px solid rgba(255, 255, 255, 0.12); + box-shadow: 0 24px 80px rgba(0, 0, 0, 0.35); + border-radius: var(--launcher-radius-card); +} +``` + +### 26.5. Primary CTA + +```css +.launcher-primary-button { + border-radius: var(--launcher-radius-control); + background: rgb(var(--nodedc-accent-rgb)); + color: rgb(var(--nodedc-on-accent-rgb)); + border: none; + outline: none; +} +``` + +Текст на светлом акценте должен быть тёмным. + +### 26.6. Dropdown / popup + +Все dropdown/popup, которые открываются внутри карточек, таблиц, scroll-контейнеров, sticky header или detail panel, должны рендериться через portal. + +Inline popup внутри ограниченного контейнера считается дефектом. + +### 26.7. Кнопки + +- без жёсткого outline; +- без случайных синих browser рамок; +- primary CTA — акцентная заливка; +- secondary — тёмная glass-поверхность; +- danger — мягкая danger surface, без кислотно-красного свечения; +- icon actions — круглые. + +### 26.8. Тексты + +UI должен быть на русском. + +Не оставлять смешанные подписи: + +- Created at; +- Updated at; +- Label; +- State; +- Workspace; +- Project; +- Members. + +Допускаются технические поля в dev/debug блоках, но production UI должен быть русифицирован. + +--- + +## 27. Mock data для первого этапа + +### 27.1. Клиенты + +```ts +export const mockClients: Client[] = [ + { + id: "client_romashka", + type: "company", + name: "ООО Ромашка", + legalName: "ООО Ромашка", + status: "active", + demoEndsAt: null, + contactName: "Иван Петров", + contactEmail: "ivan@romashka.ru", + notes: "Основной demo-клиент для проверки Task Manager и NodeDC.", + createdAt: "2026-04-01T10:00:00Z", + updatedAt: "2026-04-30T10:00:00Z", + }, + { + id: "client_roga_kopyta", + type: "company", + name: "ООО Рога и Копыта", + legalName: "ООО Рога и Копыта", + status: "demo", + demoEndsAt: "2026-06-01T00:00:00Z", + contactName: "Мария Иванова", + contactEmail: "maria@example.ru", + notes: "Клиент на демо-доступе.", + createdAt: "2026-04-10T10:00:00Z", + updatedAt: "2026-04-30T10:00:00Z", + }, +]; +``` + +### 27.2. Сервисы + +```ts +export const mockServices: Service[] = [ + { + id: "service_nodedc", + slug: "nodedc", + title: "NodeDC", + subtitle: "Агентная платформа", + description: "Сборка, запуск и мониторинг агентных workflow.", + fullDescription: "NodeDC используется для настройки агентных процессов, визуальной оркестрации, интеграций и runtime-мониторинга.", + url: "https://dev.handhdc.ru", + launchUrl: "https://dev.handhdc.ru/sso/launch", + iconUrl: "/mock/services/nodedc/icon.svg", + coverImageUrl: "/mock/services/nodedc/cover.webp", + previewVideoUrl: "/mock/services/nodedc/preview.mp4", + ambientVideoUrl: "/mock/services/nodedc/ambient.webm", + accentColor: "#B5FF5A", + status: "active", + order: 10, + authentikApplicationSlug: "nodedc", + authentikGroupName: "service-nodedc", + isAvailableForAllNewClients: false, + createdAt: "2026-04-01T10:00:00Z", + updatedAt: "2026-04-30T10:00:00Z", + }, + { + id: "service_task_manager", + slug: "task-manager", + title: "Task Manager", + subtitle: "Операционный слой", + description: "Задачи, контуры предприятия, рабочие процессы и AI-функции поверх задачника.", + fullDescription: "Task Manager основан на архитектуре Plane и расширен AI-функциями NODE.DC.", + url: "https://tasks.handhdc.ru", + launchUrl: "https://tasks.handhdc.ru/sso/launch", + iconUrl: "/mock/services/task-manager/icon.svg", + coverImageUrl: "/mock/services/task-manager/cover.webp", + previewVideoUrl: "/mock/services/task-manager/preview.mp4", + ambientVideoUrl: "/mock/services/task-manager/ambient.webm", + accentColor: "#D7C8FF", + status: "active", + order: 20, + authentikApplicationSlug: "task-manager", + authentikGroupName: "service-task-manager", + isAvailableForAllNewClients: false, + createdAt: "2026-04-01T10:00:00Z", + updatedAt: "2026-04-30T10:00:00Z", + }, + { + id: "service_1c", + slug: "1c-assistant", + title: "1C Assistant", + subtitle: "Бухгалтерский ассистент", + description: "Вопросы к 1С, точные выборки, доказательная навигация по данным.", + fullDescription: "Ассистент для бухгалтерских запросов, анализа операций, остатков, задолженностей и документов.", + url: "https://1c.handhdc.ru", + launchUrl: "https://1c.handhdc.ru/sso/launch", + iconUrl: "/mock/services/1c/icon.svg", + coverImageUrl: "/mock/services/1c/cover.webp", + previewVideoUrl: null, + ambientVideoUrl: null, + accentColor: "#8FD7FF", + status: "maintenance", + order: 30, + authentikApplicationSlug: "1c-assistant", + authentikGroupName: "service-1c-assistant", + isAvailableForAllNewClients: false, + createdAt: "2026-04-01T10:00:00Z", + updatedAt: "2026-04-30T10:00:00Z", + }, +]; +``` + +--- + +## 28. Рекомендуемая структура проекта + +```txt +src/ +├── app/ +│ ├── LauncherApp.tsx +│ ├── providers/ +│ │ ├── QueryProvider.tsx +│ │ └── LauncherProvider.tsx +│ └── routes/ +│ └── AppRouter.tsx +│ +├── entities/ +│ ├── client/ +│ │ ├── types.ts +│ │ ├── mock.ts +│ │ └── api.ts +│ ├── user/ +│ │ ├── types.ts +│ │ ├── mock.ts +│ │ └── api.ts +│ ├── service/ +│ │ ├── types.ts +│ │ ├── mock.ts +│ │ └── api.ts +│ └── access/ +│ ├── types.ts +│ ├── computeEffectiveAccess.ts +│ ├── mock.ts +│ └── api.ts +│ +├── features/ +│ ├── service-launch/ +│ ├── client-switcher/ +│ ├── profile-menu/ +│ ├── admin-clients/ +│ ├── admin-users/ +│ ├── admin-groups/ +│ ├── admin-services/ +│ ├── admin-access-matrix/ +│ ├── admin-invites/ +│ └── admin-sync/ +│ +├── widgets/ +│ ├── top-bar/ +│ ├── service-stage/ +│ ├── service-rail/ +│ ├── service-detail-card/ +│ └── admin-overlay/ +│ +├── shared/ +│ ├── api/ +│ │ ├── client.ts +│ │ └── mockAdapter.ts +│ ├── ui/ +│ │ ├── GlassSurface.tsx +│ │ ├── GlassCard.tsx +│ │ ├── Button.tsx +│ │ ├── RoundIconButton.tsx +│ │ ├── PortalDropdown.tsx +│ │ ├── StatusBadge.tsx +│ │ └── MediaRenderer.tsx +│ ├── config/ +│ │ └── designTokens.ts +│ └── lib/ +│ ├── permissions.ts +│ └── cn.ts +│ +└── styles/ + ├── globals.css + ├── launcher-theme.css + └── glass.css +``` + +--- + +## 29. Сценарии проверки MVP + +### 29.1. Member видит только доступные сервисы + +Дано: + +- пользователь Вася; +- клиент ООО «Ромашка»; +- клиенту подключён Task Manager; +- NodeDC не подключён. + +Ожидаемо: + +- Вася видит Task Manager; +- Вася не видит NodeDC; +- кнопка «Открыть» ведёт в Task Manager. + +### 29.2. Сервис на техработах + +Дано: + +- 1C Assistant подключён пользователю; +- статус сервиса `maintenance`. + +Ожидаемо: + +- карточка сервиса видна; +- есть бейдж «Техработы»; +- кнопка открытия disabled; +- detail-card объясняет, что сервис временно недоступен. + +### 29.3. Deny exception + +Дано: + +- Task Manager подключён всему клиенту; +- пользователю Лена создано deny-исключение. + +Ожидаемо: + +- Лена не видит Task Manager; +- access matrix показывает deny exception; +- explanation panel объясняет, что индивидуальное исключение перекрыло доступ клиента. + +### 29.4. Client Admin не видит других клиентов + +Дано: + +- пользователь Иван является client_admin ООО «Ромашка». + +Ожидаемо: + +- в админке он видит только ООО «Ромашка»; +- он не видит ООО «Рога и Копыта»; +- он не может создать глобальный сервис; +- он может приглашать пользователей в ООО «Ромашка». + +### 29.5. Root Admin видит скрытый сервис + +Дано: + +- сервис DM имеет статус `hidden`. + +Ожидаемо: + +- обычный member не видит DM; +- root_admin видит DM в каталоге сервисов; +- root_admin видит бейдж `Скрыт`. + +### 29.6. Смена активного клиента + +Дано: + +- пользователь состоит в двух клиентах. + +Ожидаемо: + +- в top bar доступен client switcher; +- при смене клиента обновляется service rail; +- selectedServiceId сбрасывается на первый доступный сервис нового клиента; +- админские права пересчитываются для выбранного клиента. + +--- + +## 30. Acceptance Criteria + +### 30.1. Архитектура + +- Приложение создано как отдельный React frontend. +- Нет зависимости от реального Authentik на MVP. +- Есть mock API слой. +- Типы данных совместимы с будущим Authentik/OIDC. +- Доступы считаются через computeEffectiveAccess. +- Внутренние проекты Plane не упоминаются как сущности Launcher. +- Внутренние workflow NodeDC не управляются через Launcher. + +### 30.2. Пользовательский UI + +- Есть fullscreen user launcher view. +- Есть top bar. +- Есть service rail. +- Есть service stage. +- Есть glass detail card. +- Можно выбирать сервис. +- Можно открыть активный сервис. +- Maintenance-сервис нельзя открыть. +- Hidden-сервис не виден обычному пользователю. +- Member видит только доступные сервисы. + +### 30.3. Админка + +- Root admin может открыть admin overlay. +- Root admin видит клиентов, пользователей, сервисы и доступы. +- Client admin видит только свою компанию. +- Есть матрица доступов. +- Есть explanation итогового доступа. +- Есть CRUD mock для клиентов/сервисов/групп/доступов. +- Есть invite mock-flow. +- Есть sync-status mock-flow. + +### 30.4. Design + +- UI соответствует glass-canon. +- Нет жёстких browser outline. +- Dropdown рендерятся через portal. +- Кнопки имеют единый стиль. +- Primary CTA использует акцентную заливку и контрастный текст. +- UI русифицирован. +- Нет локальной стилизации «на глаз», используются shared-компоненты. + +### 30.5. Code quality + +- Все доменные типы вынесены в отдельные файлы. +- Mock data отделена от UI. +- API adapter отделён от components. +- Access logic покрыта unit-тестами. +- UI components не содержат бизнес-логику доступа. +- Состояния loading/error/empty предусмотрены. + +--- + +## 31. Unit tests для access engine + +Минимальный набор тестов: + +```txt +computeEffectiveAccess +├── returns false when client is suspended +├── returns false when user is blocked +├── returns false when service is disabled +├── hides hidden service from normal user +├── returns false when no grant exists +├── returns true from client grant +├── returns true from group grant +├── returns true from user grant +├── deny exception overrides client grant +├── deny exception overrides group grant +├── deny exception overrides user grant +├── maintenance service is visible but openEnabled=false +└── returns correct explanation reason +``` + +--- + +## 32. Этапы разработки + +### Этап 1. База приложения + +- создать проект; +- подключить TypeScript; +- настроить Tailwind; +- создать app shell; +- создать mock API; +- создать доменные типы; +- создать design tokens. + +### Этап 2. Пользовательский Launcher + +- top bar; +- service rail; +- service stage; +- service detail card; +- profile menu; +- client switcher; +- service open flow; +- media renderer. + +### Этап 3. Access engine + +- типы grants/exceptions; +- computeEffectiveAccess; +- unit tests; +- интеграция с `/api/launcher/services`; +- фильтрация сервисов пользователя. + +### Этап 4. Root Admin Overlay + +- layout admin overlay; +- admin sidebar; +- overview; +- clients table; +- users table; +- services table; +- basic edit forms. + +### Этап 5. Access Matrix + +- access matrix; +- explanation panel; +- create grant; +- remove grant; +- create deny exception; +- remove exception. + +### Этап 6. Client Admin + +- ограничение видимости по клиенту; +- участники клиента; +- группы клиента; +- доступы только к разрешённым сервисам; +- invite mock-flow. + +### Этап 7. Sync и audit mock + +- sync status list; +- retry mock; +- error details; +- audit log mock. + +### Этап 8. Подготовка к backend + +- заменить mock adapter на fetch adapter; +- описать API DTO; +- добавить env config; +- добавить error boundary; +- добавить loading skeletons; +- подготовить Authentik claims adapter. + +--- + +## 33. Что отправить backend-команде на согласование + +Backend-команде нужно согласовать: + +1. доменные сущности; +2. названия ролей; +3. схему клиента и членства; +4. схему service grants; +5. схему deny exceptions; +6. expected `/api/me`; +7. expected `/api/launcher/services`; +8. expected admin endpoints; +9. формат Authentik claims; +10. схему будущей синхронизации с Authentik; +11. правило, что бизнес-логика доступа живёт в Launcher Backend; +12. правило, что Authentik остаётся identity/SSO-слоем. + +--- + +## 34. Что отправить Codex на разработку + +Codex нужно передать: + +1. этот документ; +2. текущий дизайн-код NODE.DC; +3. референс изображения Launcher; +4. список сервисов для mock; +5. требование не делать реальный Authentik; +6. требование сделать mock API-compatible architecture; +7. требование не смешивать проекты Plane с сервисами Launcher; +8. требование реализовать access engine; +9. требование использовать shared glass components; +10. требование подготовить приложение к backend-интеграции. + +--- + +## 35. Короткое резюме для команды + +Launcher — отдельный control-plane экосистемы NODE.DC. + +Он показывает пользователю доступные приложения и даёт администраторам управлять клиентами, пользователями, сервисами и доступами. + +На MVP не подключается настоящий Authentik, но структура данных сразу проектируется под OIDC/Authentik. + +Главный принцип: Launcher управляет входом и доступом к сервисам, но не управляет внутренними сущностями подключённых приложений. + +Task Manager сам управляет своими проектами и задачами. NodeDC сам управляет workflow и агентами. Launcher только определяет, кто имеет право открыть соответствующий сервис. + +--- + +## 36. Открытые вопросы + +Эти вопросы не блокируют MVP, но их нужно закрыть перед production-интеграцией: + +1. Будет ли один пользователь иметь доступ к нескольким клиентам? +2. Нужно ли разделять Client Owner и Client Admin на MVP? +3. Нужно ли root-admin видеть пользовательскую витрину от лица выбранного клиента? +4. Какой финальный naming для доменов сервисов? +5. Какой exact формат Authentik group naming? +6. Кто является владельцем provisioning в Task Manager: Launcher Backend или отдельный интеграционный сервис? +7. Кто является владельцем provisioning в NodeDC? +8. Нужны ли платные тарифы/billing в Launcher или это внешний контур? +9. Нужно ли делать публичную self-registration в будущем? +10. Нужен ли approval flow для подключения сервисов клиенту? + +--- + +## 37. Приложение: минимальный Codex prompt + +```md +Нужно реализовать отдельное React-приложение NODE.DC Launcher. + +Launcher — это SPA/control-plane для экосистемы NODE.DC. Он показывает пользователю доступные сервисы и даёт администраторам управлять клиентами, пользователями, группами, сервисами и доступами. + +На текущем этапе не подключать настоящий Authentik. Сделать mock/light-backend слой, но типы и API-контракты заложить так, чтобы позже подключить Authentik/OIDC. + +Главное: Launcher управляет доступом к приложениям, но не управляет внутренними сущностями приложений. Проекты внутри Task Manager/Plane не относятся к Launcher. Workflow внутри NodeDC не относятся к Launcher. + +Реализовать: +- fullscreen user launcher view; +- top bar; +- service rail; +- service stage; +- glass service detail card; +- profile menu; +- admin overlay; +- root-admin sections: clients, users, groups, services, access, invites, sync, audit; +- client-admin ограничение по своему клиенту; +- service catalog; +- access matrix; +- computeEffectiveAccess; +- deny exceptions; +- mock data; +- mock API adapter; +- glass-canon shared components. + +UI на русском. Дизайн — matte glass, blur, большие радиусы, без browser outline, portal dropdown, shared components. +``` +