FEAT - LAUNCHER: wire Engine access roles

This commit is contained in:
DCCONSTRUCTIONS 2026-05-17 21:49:45 +03:00
parent ec91821fdc
commit 55ab952ae8
4 changed files with 90 additions and 8 deletions

View File

@ -6,7 +6,14 @@ const platformGroups = {
launcherUser: "nodedc:launcher:user", launcherUser: "nodedc:launcher:user",
taskManagerAdmin: "nodedc:taskmanager:admin", taskManagerAdmin: "nodedc:taskmanager:admin",
taskManagerUser: "nodedc:taskmanager:user", taskManagerUser: "nodedc:taskmanager:user",
engineAdmin: "nodedc:engine:admin",
engineEditor: "nodedc:engine:editor",
engineViewer: "nodedc:engine:viewer",
engineLegacyAdmin: "nodedc_admin",
engineLegacyEditor: "nodedc_editor",
engineLegacyViewer: "nodedc_viewer",
}; };
const engineServiceSlugs = new Set(["nodedc", "engine", "nodedc-engine"]);
const publicPoolClientId = "client_public_pool"; const publicPoolClientId = "client_public_pool";
const publicPoolClient = { const publicPoolClient = {
id: publicPoolClientId, id: publicPoolClientId,
@ -200,6 +207,7 @@ export function resolveRequiredGroups(data, user) {
groupNames.add(platformGroups.launcherAdmin); groupNames.add(platformGroups.launcherAdmin);
groupNames.add(platformGroups.taskManagerAdmin); groupNames.add(platformGroups.taskManagerAdmin);
groupNames.add(platformGroups.taskManagerUser); groupNames.add(platformGroups.taskManagerUser);
addGroups(groupNames, resolveEngineRoleGroups("admin"));
return [...groupNames]; return [...groupNames];
} }
@ -219,7 +227,9 @@ export function resolveRequiredGroups(data, user) {
continue; continue;
} }
if (service.slug === "task-manager") { if (isEngineService(service)) {
addGroups(groupNames, resolveEngineRoleGroups(access.appRole));
} else if (service.slug === "task-manager") {
groupNames.add(platformGroups.taskManagerUser); groupNames.add(platformGroups.taskManagerUser);
if (access.appRole === "admin" || access.appRole === "owner") { if (access.appRole === "admin" || access.appRole === "owner") {
@ -234,6 +244,32 @@ export function resolveRequiredGroups(data, user) {
return [...groupNames]; return [...groupNames];
} }
function isEngineService(service) {
return (
engineServiceSlugs.has(service.slug) ||
engineServiceSlugs.has(service.authentikApplicationSlug) ||
service.id === "service_nodedc"
);
}
function resolveEngineRoleGroups(appRole) {
if (appRole === "admin" || appRole === "owner") {
return [platformGroups.engineAdmin, platformGroups.engineLegacyAdmin];
}
if (appRole === "viewer") {
return [platformGroups.engineViewer, platformGroups.engineLegacyViewer];
}
return [platformGroups.engineEditor, platformGroups.engineLegacyEditor];
}
function addGroups(target, groups) {
for (const group of groups) {
target.add(group);
}
}
function getUserRuntimeClients(data, userId) { function getUserRuntimeClients(data, userId) {
const clients = [...data.clients]; const clients = [...data.clients];
const hasPublicPoolMembership = data.memberships.some( const hasPublicPoolMembership = data.memberships.some(

View File

@ -37,6 +37,14 @@ const accessRequestStatuses = new Set(["new", "approved", "rejected"]);
const taskerInviteRequestStatuses = new Set(["new", "approved", "rejected", "cancelled"]); const taskerInviteRequestStatuses = new Set(["new", "approved", "rejected", "cancelled"]);
const taskManagerInviteRoles = new Set(["guest", "member", "admin"]); const taskManagerInviteRoles = new Set(["guest", "member", "admin"]);
const publicPoolClientId = "client_public_pool"; const publicPoolClientId = "client_public_pool";
const engineAuthentikGroups = [
"nodedc:engine:admin",
"nodedc:engine:editor",
"nodedc:engine:viewer",
"nodedc_admin",
"nodedc_editor",
"nodedc_viewer",
];
const publicPoolClient = { const publicPoolClient = {
id: publicPoolClientId, id: publicPoolClientId,
type: "person", type: "person",
@ -1811,6 +1819,7 @@ export function createControlPlaneStore({ projectRoot }) {
"nodedc:superadmin", "nodedc:superadmin",
"nodedc:launcher:admin", "nodedc:launcher:admin",
"nodedc:launcher:user", "nodedc:launcher:user",
...engineAuthentikGroups,
...data.services.flatMap((service) => (service.authentikGroupName ? [service.authentikGroupName] : [])), ...data.services.flatMap((service) => (service.authentikGroupName ? [service.authentikGroupName] : [])),
...data.groups.map((group) => `client:${group.clientId}:group:${slugify(group.name)}`), ...data.groups.map((group) => `client:${group.clientId}:group:${slugify(group.name)}`),
], ],
@ -1931,6 +1940,7 @@ function normalizeData(payload) {
...client, ...client,
integrations: normalizeClientIntegrations(client.integrations), integrations: normalizeClientIntegrations(client.integrations),
})); }));
data.services = data.services.map(normalizeService);
data.accessRequests = data.accessRequests.map(normalizeAccessRequest).filter(Boolean); data.accessRequests = data.accessRequests.map(normalizeAccessRequest).filter(Boolean);
data.revokedAccounts = data.revokedAccounts.map(normalizeRevokedAccount).filter(Boolean); data.revokedAccounts = data.revokedAccounts.map(normalizeRevokedAccount).filter(Boolean);
data.serviceModuleEntitlements = data.serviceModuleEntitlements.map(normalizeServiceModuleEntitlement).filter(Boolean); data.serviceModuleEntitlements = data.serviceModuleEntitlements.map(normalizeServiceModuleEntitlement).filter(Boolean);
@ -1938,6 +1948,23 @@ function normalizeData(payload) {
return data; return data;
} }
function normalizeService(service) {
if (typeof service !== "object" || service === null) return service;
if (service.id !== "service_nodedc" && service.slug !== "nodedc" && service.authentikApplicationSlug !== "nodedc") return service;
return {
...service,
title: service.title === "AGENT CORE" || service.title === "NodeDC" ? "ENGINE" : service.title,
url: service.url === "https://nodedc.ru/" || service.url === "https://dev.handhdc.ru/sso/launch" ? "https://engine.nodedc.ru/" : service.url,
launchUrl:
service.launchUrl === "https://nodedc.ru/" || service.launchUrl === "https://dev.handhdc.ru/sso/launch"
? "https://engine.nodedc.ru/"
: service.launchUrl,
authentikApplicationSlug: service.authentikApplicationSlug === "nodedc" ? "nodedc-engine" : service.authentikApplicationSlug,
authentikGroupName: service.authentikGroupName === "service-nodedc" ? "nodedc:engine:viewer" : service.authentikGroupName,
};
}
function normalizeServiceModuleEntitlement(payload) { function normalizeServiceModuleEntitlement(payload) {
if (typeof payload !== "object" || payload === null) return null; if (typeof payload !== "object" || payload === null) return null;
const clientId = nullableString(payload.clientId); const clientId = nullableString(payload.clientId);

View File

@ -2134,7 +2134,7 @@ function getAppCatalog() {
const launcherData = readLauncherData(); const launcherData = readLauncherData();
const services = Array.isArray(launcherData?.services) ? launcherData.services : []; const services = Array.isArray(launcherData?.services) ? launcherData.services : [];
const serviceCatalog = services.map((service) => { const serviceCatalog = services.map((service) => {
const specialGroups = specialRequiredGroups(service.slug); const specialGroups = specialRequiredGroups(service);
const requiredGroups = specialGroups.length const requiredGroups = specialGroups.length
? specialGroups ? specialGroups
: service.authentikGroupName : service.authentikGroupName
@ -2175,9 +2175,26 @@ function getAppCatalog() {
]; ];
} }
function specialRequiredGroups(slug) { const engineRequiredGroups = [
"nodedc:engine:admin",
"nodedc:engine:editor",
"nodedc:engine:viewer",
"nodedc_admin",
"nodedc_editor",
"nodedc_viewer",
];
function specialRequiredGroups(serviceOrSlug) {
const slug = typeof serviceOrSlug === "string" ? serviceOrSlug : serviceOrSlug?.slug;
const applicationSlug = typeof serviceOrSlug === "string" ? null : serviceOrSlug?.authentikApplicationSlug;
const serviceId = typeof serviceOrSlug === "string" ? null : serviceOrSlug?.id;
if (slug === "launcher") return ["nodedc:launcher:admin", "nodedc:launcher:user"]; if (slug === "launcher") return ["nodedc:launcher:admin", "nodedc:launcher:user"];
if (slug === "task-manager") return ["nodedc:taskmanager:admin", "nodedc:taskmanager:user"]; if (slug === "task-manager") return ["nodedc:taskmanager:admin", "nodedc:taskmanager:user"];
if (slug === "nodedc" || slug === "engine" || slug === "nodedc-engine" || applicationSlug === "nodedc-engine" || serviceId === "service_nodedc") {
return engineRequiredGroups;
}
return []; return [];
} }

View File

@ -77,19 +77,19 @@ export const mockServices: Service[] = [
{ {
id: "service_nodedc", id: "service_nodedc",
slug: "nodedc", slug: "nodedc",
title: "NodeDC", title: "ENGINE",
subtitle: "Агентная платформа", subtitle: "Агентная платформа",
description: "Сборка, запуск и мониторинг агентных workflow.", description: "Сборка, запуск и мониторинг агентных workflow.",
fullDescription: fullDescription:
"NodeDC используется для настройки агентных процессов, визуальной оркестрации, интеграций и runtime-мониторинга.", "NodeDC используется для настройки агентных процессов, визуальной оркестрации, интеграций и runtime-мониторинга.",
url: "https://dev.handhdc.ru/sso/launch", url: "https://engine.nodedc.ru/",
launchUrl: "https://dev.handhdc.ru/sso/launch", launchUrl: "https://engine.nodedc.ru/",
accentColor: "#B5FF5A", accentColor: "#B5FF5A",
fallbackGradient: "linear-gradient(128deg, rgba(181, 255, 90, 0.84), rgba(37, 58, 36, 0.86) 42%, #0A0D10 82%)", fallbackGradient: "linear-gradient(128deg, rgba(181, 255, 90, 0.84), rgba(37, 58, 36, 0.86) 42%, #0A0D10 82%)",
status: "active", status: "active",
order: 10, order: 10,
authentikApplicationSlug: "nodedc", authentikApplicationSlug: "nodedc-engine",
authentikGroupName: "service-nodedc", authentikGroupName: "nodedc:engine:viewer",
createdAt: "2026-04-01T10:00:00Z", createdAt: "2026-04-01T10:00:00Z",
updatedAt: now, updatedAt: now,
}, },
@ -207,6 +207,8 @@ export const mockGrants: ServiceGrant[] = [
grant("grant_dctouch_task_admins", "service_task_manager", "group", "group_dctouch_admins", "admin"), grant("grant_dctouch_task_admins", "service_task_manager", "group", "group_dctouch_admins", "admin"),
grant("grant_dctouch_task_managers", "service_task_manager", "group", "group_dctouch_managers", "member"), grant("grant_dctouch_task_managers", "service_task_manager", "group", "group_dctouch_managers", "member"),
grant("grant_dctouch_nodedc_admins", "service_nodedc", "group", "group_dctouch_admins", "admin"), grant("grant_dctouch_nodedc_admins", "service_nodedc", "group", "group_dctouch_admins", "admin"),
grant("grant_engine_user_silver_psih_yahoo_com", "service_nodedc", "user", "user_silver_psih", "member"),
grant("grant_engine_user_constr_dc_yahoo_com", "service_nodedc", "user", "user_constr_dc_yahoo_com", "viewer"),
]; ];
export const mockExceptions: ServiceAccessException[] = []; export const mockExceptions: ServiceAccessException[] = [];