Add BIM viewer service launch integration
This commit is contained in:
parent
ec021d1a95
commit
4584c227ff
|
|
@ -62,6 +62,48 @@ const publicPoolClient = {
|
||||||
createdAt: "2026-05-09T00:00:00.000Z",
|
createdAt: "2026-05-09T00:00:00.000Z",
|
||||||
updatedAt: "2026-05-09T00:00:00.000Z",
|
updatedAt: "2026-05-09T00:00:00.000Z",
|
||||||
};
|
};
|
||||||
|
const bimViewerService = {
|
||||||
|
id: "service_bim_viewer",
|
||||||
|
slug: "bim-viewer",
|
||||||
|
title: "BIM VIEWER",
|
||||||
|
subtitle: "BIM и CAD просмотр",
|
||||||
|
description: "Персональный просмотр BIM/CAD моделей, версий и публичных ссылок.",
|
||||||
|
fullDescription:
|
||||||
|
"Standalone BIM/CAD viewer NODE.DC для рабочих моделей, вложений Operational Core, персонального модельного хранилища и безопасного внешнего просмотра по share-ссылкам.",
|
||||||
|
url: "https://bim.nodedc.ru",
|
||||||
|
launchUrl: "https://bim.nodedc.ru",
|
||||||
|
accentColor: "#8FE3C8",
|
||||||
|
fallbackGradient: "linear-gradient(132deg, rgba(143, 227, 200, 0.78), rgba(24, 70, 64, 0.9) 46%, #080B0F 84%)",
|
||||||
|
status: "active",
|
||||||
|
order: 25,
|
||||||
|
authentikApplicationSlug: "bim-viewer",
|
||||||
|
authentikGroupName: "nodedc:bim:access",
|
||||||
|
isAvailableForAllNewClients: true,
|
||||||
|
createdAt: "2026-06-18T00:00:00.000Z",
|
||||||
|
updatedAt: "2026-06-18T00:00:00.000Z",
|
||||||
|
};
|
||||||
|
const bimViewerDefaultGrants = [
|
||||||
|
{
|
||||||
|
id: "grant_bim_viewer_client_romashka",
|
||||||
|
serviceId: "service_bim_viewer",
|
||||||
|
targetType: "client",
|
||||||
|
targetId: "client_romashka",
|
||||||
|
appRole: "member",
|
||||||
|
status: "active",
|
||||||
|
createdAt: "2026-06-18T00:00:00.000Z",
|
||||||
|
updatedAt: "2026-06-18T00:00:00.000Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "grant_bim_viewer_client_public_pool",
|
||||||
|
serviceId: "service_bim_viewer",
|
||||||
|
targetType: "client",
|
||||||
|
targetId: publicPoolClientId,
|
||||||
|
appRole: "member",
|
||||||
|
status: "active",
|
||||||
|
createdAt: "2026-06-18T00:00:00.000Z",
|
||||||
|
updatedAt: "2026-06-18T00:00:00.000Z",
|
||||||
|
},
|
||||||
|
];
|
||||||
const defaultSettings = {
|
const defaultSettings = {
|
||||||
brand: {
|
brand: {
|
||||||
logoLinkUrl: "/",
|
logoLinkUrl: "/",
|
||||||
|
|
@ -2281,9 +2323,89 @@ function normalizeData(payload) {
|
||||||
data.taskerInviteRequests = data.taskerInviteRequests.map(normalizeTaskerInviteRequest).filter(Boolean);
|
data.taskerInviteRequests = data.taskerInviteRequests.map(normalizeTaskerInviteRequest).filter(Boolean);
|
||||||
data.engineWorkflowAccessRequests = data.engineWorkflowAccessRequests.map(normalizeEngineWorkflowAccessRequest).filter(Boolean);
|
data.engineWorkflowAccessRequests = data.engineWorkflowAccessRequests.map(normalizeEngineWorkflowAccessRequest).filter(Boolean);
|
||||||
applyProtectedLauncherUserInvariants(data);
|
applyProtectedLauncherUserInvariants(data);
|
||||||
|
applyBimViewerServiceInvariants(data);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyBimViewerServiceInvariants(data) {
|
||||||
|
let service = data.services.find(
|
||||||
|
(candidate) => candidate.id === bimViewerService.id || candidate.slug === bimViewerService.slug
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!service) {
|
||||||
|
service = normalizeService({ ...bimViewerService });
|
||||||
|
data.services.push(service);
|
||||||
|
} else {
|
||||||
|
service.id = service.id || bimViewerService.id;
|
||||||
|
service.slug = service.slug || bimViewerService.slug;
|
||||||
|
service.title = service.title || bimViewerService.title;
|
||||||
|
service.subtitle = service.subtitle || bimViewerService.subtitle;
|
||||||
|
service.description = service.description || bimViewerService.description;
|
||||||
|
service.fullDescription = service.fullDescription || bimViewerService.fullDescription;
|
||||||
|
service.url = service.url || bimViewerService.url;
|
||||||
|
service.launchUrl = service.launchUrl || service.url || bimViewerService.launchUrl;
|
||||||
|
service.accentColor = service.accentColor || bimViewerService.accentColor;
|
||||||
|
service.fallbackGradient = service.fallbackGradient || bimViewerService.fallbackGradient;
|
||||||
|
service.status = service.status || bimViewerService.status;
|
||||||
|
service.order = service.order ?? bimViewerService.order;
|
||||||
|
service.authentikApplicationSlug = service.authentikApplicationSlug || bimViewerService.authentikApplicationSlug;
|
||||||
|
service.authentikGroupName = service.authentikGroupName || bimViewerService.authentikGroupName;
|
||||||
|
service.isAvailableForAllNewClients = service.isAvailableForAllNewClients ?? true;
|
||||||
|
service.createdAt = service.createdAt || bimViewerService.createdAt;
|
||||||
|
service.updatedAt = service.updatedAt || bimViewerService.updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const serviceId = service.id || bimViewerService.id;
|
||||||
|
for (const defaultGrant of bimViewerDefaultGrants) {
|
||||||
|
const grant = { ...defaultGrant, serviceId };
|
||||||
|
const exists = data.grants.some(
|
||||||
|
(candidate) =>
|
||||||
|
candidate.serviceId === grant.serviceId &&
|
||||||
|
candidate.targetType === grant.targetType &&
|
||||||
|
candidate.targetId === grant.targetId
|
||||||
|
);
|
||||||
|
if (!exists) {
|
||||||
|
data.grants.push({ ...grant });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureStaticSyncStatus(data, {
|
||||||
|
id: "sync_bim_viewer_authentik",
|
||||||
|
objectId: serviceId,
|
||||||
|
objectName: bimViewerService.title,
|
||||||
|
objectType: "service",
|
||||||
|
});
|
||||||
|
for (const defaultGrant of bimViewerDefaultGrants) {
|
||||||
|
const grant = { ...defaultGrant, serviceId };
|
||||||
|
ensureStaticSyncStatus(data, {
|
||||||
|
id: `sync_${grant.id}`,
|
||||||
|
objectId: grant.id,
|
||||||
|
objectName: `${bimViewerService.slug}:${grant.targetType}:${grant.targetId}`,
|
||||||
|
objectType: "grant",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureStaticSyncStatus(data, status) {
|
||||||
|
const exists = data.syncStatuses.some(
|
||||||
|
(candidate) => candidate.target === "authentik" && candidate.objectType === status.objectType && candidate.objectId === status.objectId
|
||||||
|
);
|
||||||
|
if (exists) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.syncStatuses.push({
|
||||||
|
id: status.id,
|
||||||
|
objectId: status.objectId,
|
||||||
|
objectName: status.objectName,
|
||||||
|
objectType: status.objectType,
|
||||||
|
target: "authentik",
|
||||||
|
state: "pending",
|
||||||
|
lastSyncAt: null,
|
||||||
|
error: null,
|
||||||
|
updatedAt: "2026-06-18T00:00:00.000Z",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function applyProtectedLauncherUserInvariants(data) {
|
function applyProtectedLauncherUserInvariants(data) {
|
||||||
const rootUser = data.users.find((user) => isProtectedLauncherUserId(user.id));
|
const rootUser = data.users.find((user) => isProtectedLauncherUserId(user.id));
|
||||||
if (!rootUser) return;
|
if (!rootUser) return;
|
||||||
|
|
|
||||||
|
|
@ -422,6 +422,21 @@ app.get("/api/services/:serviceSlug/launch", requireSession, (req, res) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isBimViewerServiceSlug(serviceSlug)) {
|
||||||
|
const handoffToken = createServiceHandoff(serviceSlug, runtimeContext.user);
|
||||||
|
const targetUrl = new URL("/auth/nodedc/handoff", getBimViewerBaseUrl(app));
|
||||||
|
const nextPath = sanitizeReturnTo(req.query.next_path || req.query.returnTo || "/");
|
||||||
|
|
||||||
|
targetUrl.searchParams.set("token", handoffToken);
|
||||||
|
|
||||||
|
if (nextPath && nextPath !== "/") {
|
||||||
|
targetUrl.searchParams.set("next_path", nextPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.redirect(targetUrl.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (serviceSlug !== "task-manager") {
|
if (serviceSlug !== "task-manager") {
|
||||||
res.redirect(app.openUrl || app.url || "/");
|
res.redirect(app.openUrl || app.url || "/");
|
||||||
return;
|
return;
|
||||||
|
|
@ -3031,6 +3046,11 @@ function isEngineServiceSlug(serviceSlug) {
|
||||||
return slug === "nodedc" || slug === "engine" || slug === "nodedc-engine";
|
return slug === "nodedc" || slug === "engine" || slug === "nodedc-engine";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isBimViewerServiceSlug(serviceSlug) {
|
||||||
|
const slug = sanitizeServiceSlug(serviceSlug);
|
||||||
|
return slug === "bim-viewer" || slug === "beam-viewer" || slug === "bim";
|
||||||
|
}
|
||||||
|
|
||||||
function specialRequiredGroups(serviceOrSlug) {
|
function specialRequiredGroups(serviceOrSlug) {
|
||||||
const slug = typeof serviceOrSlug === "string" ? serviceOrSlug : serviceOrSlug?.slug;
|
const slug = typeof serviceOrSlug === "string" ? serviceOrSlug : serviceOrSlug?.slug;
|
||||||
const applicationSlug = typeof serviceOrSlug === "string" ? null : serviceOrSlug?.authentikApplicationSlug;
|
const applicationSlug = typeof serviceOrSlug === "string" ? null : serviceOrSlug?.authentikApplicationSlug;
|
||||||
|
|
@ -3046,7 +3066,7 @@ function specialRequiredGroups(serviceOrSlug) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getServiceUrl(service) {
|
function getServiceUrl(service) {
|
||||||
if (service.slug === "task-manager" || isEngineServiceSlug(service.slug)) {
|
if (service.slug === "task-manager" || isEngineServiceSlug(service.slug) || isBimViewerServiceSlug(service.slug)) {
|
||||||
return `/api/services/${encodeURIComponent(service.slug)}/launch`;
|
return `/api/services/${encodeURIComponent(service.slug)}/launch`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3066,6 +3086,17 @@ function getTaskBaseUrl() {
|
||||||
return taskBaseUrl.replace(/\/$/, "");
|
return taskBaseUrl.replace(/\/$/, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getBimViewerBaseUrl(app = null) {
|
||||||
|
return String(
|
||||||
|
process.env.NODEDC_BIM_VIEWER_PUBLIC_URL ||
|
||||||
|
process.env.BIM_VIEWER_BASE_URL ||
|
||||||
|
app?.launchTargetUrl ||
|
||||||
|
app?.openUrl ||
|
||||||
|
app?.url ||
|
||||||
|
"http://localhost:8080"
|
||||||
|
).replace(/\/$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
function getEnginePublicBaseUrl() {
|
function getEnginePublicBaseUrl() {
|
||||||
return String(
|
return String(
|
||||||
process.env.NODEDC_ENGINE_PUBLIC_URL ||
|
process.env.NODEDC_ENGINE_PUBLIC_URL ||
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,26 @@ export const mockServices: Service[] = [
|
||||||
createdAt: "2026-04-01T10:00:00Z",
|
createdAt: "2026-04-01T10:00:00Z",
|
||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "service_bim_viewer",
|
||||||
|
slug: "bim-viewer",
|
||||||
|
title: "BIM VIEWER",
|
||||||
|
subtitle: "BIM и CAD просмотр",
|
||||||
|
description: "Персональный просмотр BIM/CAD моделей, версий и публичных ссылок.",
|
||||||
|
fullDescription:
|
||||||
|
"Standalone BIM/CAD viewer NODE.DC для рабочих моделей, вложений Operational Core, персонального модельного хранилища и безопасного внешнего просмотра по share-ссылкам.",
|
||||||
|
url: "https://bim.nodedc.ru",
|
||||||
|
launchUrl: "https://bim.nodedc.ru",
|
||||||
|
accentColor: "#8FE3C8",
|
||||||
|
fallbackGradient: "linear-gradient(132deg, rgba(143, 227, 200, 0.78), rgba(24, 70, 64, 0.9) 46%, #080B0F 84%)",
|
||||||
|
status: "active",
|
||||||
|
order: 25,
|
||||||
|
authentikApplicationSlug: "bim-viewer",
|
||||||
|
authentikGroupName: "nodedc:bim:access",
|
||||||
|
isAvailableForAllNewClients: true,
|
||||||
|
createdAt: "2026-06-18T00:00:00.000Z",
|
||||||
|
updatedAt: now,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "service_1c",
|
id: "service_1c",
|
||||||
slug: "1c-assistant",
|
slug: "1c-assistant",
|
||||||
|
|
@ -211,6 +231,8 @@ export const mockGrants: ServiceGrant[] = [
|
||||||
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_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"),
|
grant("grant_engine_user_constr_dc_yahoo_com", "service_nodedc", "user", "user_constr_dc_yahoo_com", "viewer"),
|
||||||
|
grant("grant_bim_viewer_client_romashka", "service_bim_viewer", "client", "client_romashka", "member"),
|
||||||
|
grant("grant_bim_viewer_client_public_pool", "service_bim_viewer", "client", "client_public_pool", "member"),
|
||||||
];
|
];
|
||||||
|
|
||||||
export const mockExceptions: ServiceAccessException[] = [];
|
export const mockExceptions: ServiceAccessException[] = [];
|
||||||
|
|
@ -235,6 +257,23 @@ export const mockSyncStatuses: SyncStatus[] = [
|
||||||
),
|
),
|
||||||
sync("sync_dctouch_groups_authentik", "client_romashka:groups", "DCTOUCH groups", "group", "authentik", "pending"),
|
sync("sync_dctouch_groups_authentik", "client_romashka:groups", "DCTOUCH groups", "group", "authentik", "pending"),
|
||||||
sync("sync_task_manager_authentik", "service_task_manager", "OPERATIONAL CORE", "service", "authentik", "synced"),
|
sync("sync_task_manager_authentik", "service_task_manager", "OPERATIONAL CORE", "service", "authentik", "synced"),
|
||||||
|
sync("sync_bim_viewer_authentik", "service_bim_viewer", "BIM VIEWER", "service", "authentik", "pending"),
|
||||||
|
sync(
|
||||||
|
"sync_grant_bim_viewer_client_romashka",
|
||||||
|
"grant_bim_viewer_client_romashka",
|
||||||
|
"bim-viewer:client:client_romashka",
|
||||||
|
"grant",
|
||||||
|
"authentik",
|
||||||
|
"pending"
|
||||||
|
),
|
||||||
|
sync(
|
||||||
|
"sync_grant_bim_viewer_client_public_pool",
|
||||||
|
"grant_bim_viewer_client_public_pool",
|
||||||
|
"bim-viewer:client:client_public_pool",
|
||||||
|
"grant",
|
||||||
|
"authentik",
|
||||||
|
"pending"
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
export const mockAuditEvents: AuditEvent[] = [
|
export const mockAuditEvents: AuditEvent[] = [
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue