diff --git a/public/storage/launcher-data.json b/public/storage/launcher-data.json index 190673c..1096ce6 100644 --- a/public/storage/launcher-data.json +++ b/public/storage/launcher-data.json @@ -231,7 +231,7 @@ "subtitle": "Агентная платформа", "description": "Сборка, запуск и мониторинг агентных workflow.", "fullDescription": "NodeDC используется для настройки агентных процессов, визуальной оркестрации, интеграций и runtime-мониторинга.", - "url": "https://dev.handhdc.ru", + "url": "https://dev.handhdc.ru/sso/launch", "launchUrl": "https://dev.handhdc.ru/sso/launch", "accentColor": "#B5FF5A", "fallbackGradient": "linear-gradient(128deg, rgba(181, 255, 90, 0.84), rgba(37, 58, 36, 0.86) 42%, #0A0D10 82%)", @@ -257,7 +257,7 @@ "subtitle": "Операционный слой", "description": "Задачи, контуры предприятия, процессы и AI-функции поверх задачника.", "fullDescription": "Task Manager основан на архитектуре Plane и расширен AI-функциями NODE.DC.", - "url": "https://tasks.handhdc.ru", + "url": "https://tasks.handhdc.ru/sso/launch", "launchUrl": "https://tasks.handhdc.ru/sso/launch", "accentColor": "#D7C8FF", "fallbackGradient": "linear-gradient(132deg, rgba(215, 200, 255, 0.82), rgba(51, 41, 79, 0.9) 46%, #0B0D10 84%)", @@ -279,7 +279,7 @@ "subtitle": "Бухгалтерский ассистент", "description": "Вопросы к 1С, точные выборки и доказательная навигация по данным.", "fullDescription": "Ассистент для бухгалтерских запросов, анализа операций, остатков и документов.", - "url": "https://1c.handhdc.ru", + "url": "https://1c.handhdc.ru/sso/launch", "launchUrl": "https://1c.handhdc.ru/sso/launch", "accentColor": "#8FD7FF", "fallbackGradient": "linear-gradient(126deg, rgba(143, 215, 255, 0.8), rgba(32, 61, 80, 0.9) 44%, #080B0F 84%)", @@ -301,7 +301,7 @@ "subtitle": "Госзакупки и тендеры", "description": "Поиск, анализ и подготовка тендерных решений.", "fullDescription": "Сервис собирает тендерные данные, строит выжимку рисков и помогает подготовить пакет участия.", - "url": "https://tender.handhdc.ru", + "url": "https://tender.handhdc.ru/sso/launch", "launchUrl": "https://tender.handhdc.ru/sso/launch", "accentColor": "#FFD166", "fallbackGradient": "linear-gradient(135deg, rgba(255, 209, 102, 0.84), rgba(74, 53, 19, 0.92) 42%, #0B0D10 86%)", @@ -327,7 +327,7 @@ "subtitle": "3D и пространственные данные", "description": "Просмотр цифровых двойников, карт и объектных сцен.", "fullDescription": "Витрина геометрии, объектов, слоёв и статусов инфраструктуры.", - "url": "https://twin.handhdc.ru", + "url": "https://launch.dcserve.ru/", "launchUrl": "https://launch.dcserve.ru/", "accentColor": "#76E4F7", "fallbackGradient": "linear-gradient(140deg, rgba(118, 228, 247, 0.82), rgba(23, 69, 87, 0.92) 47%, #080B0F 86%)", @@ -349,7 +349,7 @@ "subtitle": "Будущие модули", "description": "Скрытый каталог модулей для root-admin preview.", "fullDescription": "Площадка для будущих цифровых модулей NODE.DC.", - "url": "https://dm.handhdc.ru", + "url": "https://dm.handhdc.ru/sso/launch", "launchUrl": "https://dm.handhdc.ru/sso/launch", "accentColor": "#FF9AC2", "fallbackGradient": "linear-gradient(135deg, rgba(255, 154, 194, 0.78), rgba(76, 41, 64, 0.9) 44%, #090B0F 86%)", diff --git a/src/app/LauncherApp.tsx b/src/app/LauncherApp.tsx index 515eeba..3938eeb 100644 --- a/src/app/LauncherApp.tsx +++ b/src/app/LauncherApp.tsx @@ -1,6 +1,7 @@ import { useEffect, useMemo, useState } from "react"; import type { Client } from "../entities/client/types"; import type { Invite } from "../entities/invite/types"; +import { syncServiceLaunchLink } from "../entities/service/links"; import type { LauncherServiceView, Service } from "../entities/service/types"; import type { SyncStatus } from "../entities/sync/types"; import type { ClientGroup, ClientMembership, LauncherUser } from "../entities/user/types"; @@ -18,7 +19,7 @@ import { ServiceStage } from "../widgets/service-stage/ServiceStage"; import { TopBar } from "../widgets/top-bar/TopBar"; export function LauncherApp() { - const [data, setData] = useState(initialLauncherData); + const [data, setData] = useState(() => syncLauncherServiceLinks(initialLauncherData)); const [activeProfileId, setActiveProfileId] = useState(profileOptions[0].userId); const [activeClientId, setActiveClientId] = useState(profileOptions[0].defaultClientId); const [selectedServiceId, setSelectedServiceId] = useState(); @@ -51,7 +52,7 @@ export function LauncherApp() { loadPersistedLauncherData() .then((persistedData) => { if (isMounted && persistedData) { - setData(persistedData); + setData(syncLauncherServiceLinks(persistedData)); } }) .finally(() => { @@ -232,11 +233,11 @@ export function LauncherApp() { ...current, services: current.services.map((service) => service.id === serviceId - ? { + ? syncServiceLaunchLink({ ...service, ...patch, updatedAt: new Date().toISOString(), - } + }) : service ), })); @@ -444,7 +445,7 @@ export function LauncherApp() { subtitle: "Новый сервис", description: "Описание сервиса для витрины.", fullDescription: "Заполните описание, медиа и ссылку запуска в редакторе контента.", - url: "https://service.handhdc.ru", + url: "https://service.handhdc.ru/sso/launch", launchUrl: "https://service.handhdc.ru/sso/launch", accentColor: "#F7F8F4", fallbackGradient: "linear-gradient(135deg, rgba(247, 248, 244, 0.72), rgba(36, 37, 42, 0.9) 52%, #090B0F 88%)", @@ -529,3 +530,10 @@ export function LauncherApp() { ); } + +function syncLauncherServiceLinks(data: LauncherData): LauncherData { + return { + ...data, + services: data.services.map(syncServiceLaunchLink), + }; +} diff --git a/src/entities/service/links.ts b/src/entities/service/links.ts new file mode 100644 index 0000000..2d479da --- /dev/null +++ b/src/entities/service/links.ts @@ -0,0 +1,21 @@ +import type { Service } from "./types"; + +export function getServiceLaunchLink(service: Pick): string { + return service.launchUrl?.trim() || service.url.trim(); +} + +export function createServiceLaunchLinkPatch(value: string): Pick { + const launchLink = value.trim(); + + return { + url: launchLink, + launchUrl: launchLink || null, + }; +} + +export function syncServiceLaunchLink(service: Service): Service { + return { + ...service, + ...createServiceLaunchLinkPatch(getServiceLaunchLink(service)), + }; +} diff --git a/src/shared/api/mockApi.ts b/src/shared/api/mockApi.ts index 759eb49..e3199b2 100644 --- a/src/shared/api/mockApi.ts +++ b/src/shared/api/mockApi.ts @@ -2,6 +2,7 @@ import { computeEffectiveAccess } from "../../entities/access/computeEffectiveAc import type { EffectiveAccessResult, ServiceAccessException, ServiceGrant } from "../../entities/access/types"; import type { Client } from "../../entities/client/types"; import type { Invite } from "../../entities/invite/types"; +import { getServiceLaunchLink } from "../../entities/service/links"; import type { LauncherServiceView, Service } from "../../entities/service/types"; import type { SyncStatus } from "../../entities/sync/types"; import type { @@ -217,7 +218,7 @@ export function buildLauncherServices(data: LauncherData, userId: string, active status: service.status, userAccess: effectiveAccess.allowed ? ("allowed" as const) : ("denied" as const), appRole: effectiveAccess.appRole, - openUrl: effectiveAccess.openEnabled ? service.launchUrl ?? service.url : null, + openUrl: effectiveAccess.openEnabled ? getServiceLaunchLink(service) || null : null, accentColor: service.accentColor, media: { icon: service.iconUrl, diff --git a/src/shared/api/mockData.ts b/src/shared/api/mockData.ts index 028cf67..3f63c80 100644 --- a/src/shared/api/mockData.ts +++ b/src/shared/api/mockData.ts @@ -150,7 +150,7 @@ export const mockServices: Service[] = [ description: "Сборка, запуск и мониторинг агентных workflow.", fullDescription: "NodeDC используется для настройки агентных процессов, визуальной оркестрации, интеграций и runtime-мониторинга.", - url: "https://dev.handhdc.ru", + url: "https://dev.handhdc.ru/sso/launch", launchUrl: "https://dev.handhdc.ru/sso/launch", accentColor: "#B5FF5A", fallbackGradient: "linear-gradient(128deg, rgba(181, 255, 90, 0.84), rgba(37, 58, 36, 0.86) 42%, #0A0D10 82%)", @@ -168,7 +168,7 @@ export const mockServices: Service[] = [ subtitle: "Операционный слой", description: "Задачи, контуры предприятия, процессы и AI-функции поверх задачника.", fullDescription: "Task Manager основан на архитектуре Plane и расширен AI-функциями NODE.DC.", - url: "https://tasks.handhdc.ru", + url: "https://tasks.handhdc.ru/sso/launch", launchUrl: "https://tasks.handhdc.ru/sso/launch", accentColor: "#D7C8FF", fallbackGradient: "linear-gradient(132deg, rgba(215, 200, 255, 0.82), rgba(51, 41, 79, 0.9) 46%, #0B0D10 84%)", @@ -186,7 +186,7 @@ export const mockServices: Service[] = [ subtitle: "Бухгалтерский ассистент", description: "Вопросы к 1С, точные выборки и доказательная навигация по данным.", fullDescription: "Ассистент для бухгалтерских запросов, анализа операций, остатков и документов.", - url: "https://1c.handhdc.ru", + url: "https://1c.handhdc.ru/sso/launch", launchUrl: "https://1c.handhdc.ru/sso/launch", accentColor: "#8FD7FF", fallbackGradient: "linear-gradient(126deg, rgba(143, 215, 255, 0.8), rgba(32, 61, 80, 0.9) 44%, #080B0F 84%)", @@ -204,7 +204,7 @@ export const mockServices: Service[] = [ subtitle: "Госзакупки и тендеры", description: "Поиск, анализ и подготовка тендерных решений.", fullDescription: "Сервис собирает тендерные данные, строит выжимку рисков и помогает подготовить пакет участия.", - url: "https://tender.handhdc.ru", + url: "https://tender.handhdc.ru/sso/launch", launchUrl: "https://tender.handhdc.ru/sso/launch", accentColor: "#FFD166", fallbackGradient: "linear-gradient(135deg, rgba(255, 209, 102, 0.84), rgba(74, 53, 19, 0.92) 42%, #0B0D10 86%)", @@ -222,7 +222,7 @@ export const mockServices: Service[] = [ subtitle: "3D и пространственные данные", description: "Просмотр цифровых двойников, карт и объектных сцен.", fullDescription: "Витрина геометрии, объектов, слоёв и статусов инфраструктуры.", - url: "https://twin.handhdc.ru", + url: "https://twin.handhdc.ru/sso/launch", launchUrl: "https://twin.handhdc.ru/sso/launch", accentColor: "#76E4F7", fallbackGradient: "linear-gradient(140deg, rgba(118, 228, 247, 0.82), rgba(23, 69, 87, 0.92) 47%, #080B0F 86%)", @@ -240,7 +240,7 @@ export const mockServices: Service[] = [ subtitle: "Будущие модули", description: "Скрытый каталог модулей для root-admin preview.", fullDescription: "Площадка для будущих цифровых модулей NODE.DC.", - url: "https://dm.handhdc.ru", + url: "https://dm.handhdc.ru/sso/launch", launchUrl: "https://dm.handhdc.ru/sso/launch", accentColor: "#FF9AC2", fallbackGradient: "linear-gradient(135deg, rgba(255, 154, 194, 0.78), rgba(76, 41, 64, 0.9) 44%, #090B0F 86%)", @@ -259,7 +259,7 @@ export const mockServices: Service[] = [ description: "Отключённый сервис для проверки диагностики root-admin.", fullDescription: "Не показывается обычным пользователям, виден root-admin в каталоге.", url: "https://internal.handhdc.ru", - launchUrl: null, + launchUrl: "https://internal.handhdc.ru", accentColor: "#F97373", fallbackGradient: "linear-gradient(135deg, rgba(249, 115, 115, 0.78), rgba(73, 32, 32, 0.92) 43%, #090B0F 86%)", status: "disabled", diff --git a/src/widgets/admin-overlay/AdminOverlay.tsx b/src/widgets/admin-overlay/AdminOverlay.tsx index 90acc67..35cdd22 100644 --- a/src/widgets/admin-overlay/AdminOverlay.tsx +++ b/src/widgets/admin-overlay/AdminOverlay.tsx @@ -40,6 +40,7 @@ import { import type { ServiceAppRole } from "../../entities/access/types"; import type { Client, ClientStatus, ClientType } from "../../entities/client/types"; import type { Invite, InviteStatus } from "../../entities/invite/types"; +import { createServiceLaunchLinkPatch, getServiceLaunchLink } from "../../entities/service/links"; import type { MediaKind, Service, ServiceMediaSource, ServiceStatus } from "../../entities/service/types"; import type { SyncState, SyncStatus } from "../../entities/sync/types"; import type { @@ -875,7 +876,7 @@ function ServicesSection({ Сервис Slug Статус - URL + Ссылка запуска Authentik @@ -1007,9 +1008,9 @@ function ServiceTableCells({ onUpdateService(service.id, { url: event.target.value })} - aria-label={`URL сервиса ${service.title}`} + value={getServiceLaunchLink(service)} + onChange={(event) => onUpdateService(service.id, createServiceLaunchLinkPatch(event.target.value))} + aria-label={`Ссылка запуска сервиса ${service.title}`} /> @@ -1262,7 +1263,10 @@ function ServiceContentModal({ Ссылка запуска - update("launchUrl", event.target.value || null)} /> + setDraft((current) => ({ ...current, ...createServiceLaunchLinkPatch(event.target.value) }))} + /> {uploadingSlot ? "Сохраняем файл" : "Сохранить"}