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"