280 lines
9.2 KiB
JavaScript
280 lines
9.2 KiB
JavaScript
export async function prepareEmbeddedRelations(page) {
|
|
// Grafo sintético de lectura para L04.3. Solo lo ejecuta el cliente MCP de pruebas.
|
|
const f = page.adoption;
|
|
const companyId = f.detail.company.id;
|
|
const inventory = f.inventory;
|
|
const resources = f.snapshots[`/resources/${companyId}`].resources;
|
|
const actorId = inventory.assets[0].history[0].actorId;
|
|
const asset = inventory.assets.find((a) => a.id === page.embedded.asset);
|
|
const domain = resources.find((r) => r.directory);
|
|
const smb = resources.find((r) => r.networkShare);
|
|
let sequence = 1000;
|
|
|
|
const uuid = () => `019a4300-0000-7000-b000-${String(++sequence).padStart(12, "0")}`;
|
|
|
|
const shares = Array.from({ length: 50 }, (_, i) => {
|
|
const result = structuredClone(smb);
|
|
const name = `L043-SHARE-${String(i + 1).padStart(2, "0")} · Administración Peña`;
|
|
const address = result.networkShare.address
|
|
.replace(result.networkShare.host, asset.values.name)
|
|
.replace(result.networkShare.shareName, `L043-area-${i + 1}`);
|
|
const old = result.networkShare.address;
|
|
|
|
result.id = uuid();
|
|
result.values.name = name;
|
|
result.values.characteristics = Object.fromEntries(
|
|
Object.entries(result.values.characteristics).map(([key, value]) => [
|
|
key,
|
|
value === old ? address : value === smb.networkShare.host ? asset.values.name : value,
|
|
])
|
|
);
|
|
result.networkShare = {
|
|
...result.networkShare,
|
|
address,
|
|
normalizedAddress: address.toLowerCase(),
|
|
host: asset.values.name,
|
|
shareName: `L043-area-${i + 1}`,
|
|
};
|
|
result.history = [{ ...result.history[0], id: uuid(), values: structuredClone(result.values) }];
|
|
|
|
return result;
|
|
});
|
|
|
|
resources.push(...shares);
|
|
|
|
const reference = (providerId, id) => ({
|
|
providerId,
|
|
entityType: providerId === "assets" ? "assets:asset" : "resources:resource",
|
|
id,
|
|
companyId,
|
|
});
|
|
|
|
const summary = (ref, name, subtitle) => ({
|
|
status: "resolved",
|
|
summary: { reference: ref, name, subtitle },
|
|
});
|
|
|
|
const publications = shares.map((r) => ({
|
|
id: uuid(),
|
|
companyId,
|
|
assetId: asset.id,
|
|
resourceId: r.id,
|
|
predecessorId: null,
|
|
startedAt: "2026-03-01T10:00:00.000Z",
|
|
startedBy: actorId,
|
|
endedAt: null,
|
|
endedBy: null,
|
|
}));
|
|
const history = shares.slice(0, 25).map((r) => ({
|
|
id: uuid(),
|
|
companyId,
|
|
assetId: asset.id,
|
|
resourceId: r.id,
|
|
predecessorId: null,
|
|
startedAt: "2026-02-01T10:00:00.000Z",
|
|
startedBy: actorId,
|
|
endedAt: "2026-02-10T10:00:00.000Z",
|
|
endedBy: actorId,
|
|
}));
|
|
const computers = inventory.assets
|
|
.filter((a) => a.values.status === "operational" && a.specification.templateName === "Portátil")
|
|
.slice(0, 50);
|
|
const memberships = computers.map((a) => ({
|
|
typeId: "assets:directory-membership",
|
|
origin: { kind: "record", id: uuid() },
|
|
source: reference("assets", a.id),
|
|
target: reference("resources", domain.id),
|
|
stateCode: "active",
|
|
roleCode: "member",
|
|
}));
|
|
|
|
const publicationView = (p) => ({
|
|
typeId: "assets:network-share-publication",
|
|
origin: { kind: "record", id: p.id },
|
|
source: reference("assets", p.assetId),
|
|
target: reference("resources", p.resourceId),
|
|
stateCode: p.endedAt ? "ended" : "active",
|
|
});
|
|
|
|
const summaries = (items, fromAsset) =>
|
|
items.map((item) => {
|
|
const ref = fromAsset ? item.target : item.source;
|
|
const entity = (fromAsset ? resources : inventory.assets).find((e) => e.id === ref.id);
|
|
|
|
return summary(
|
|
ref,
|
|
entity.values.name,
|
|
fromAsset
|
|
? (entity.networkShare?.address ?? entity.directory?.normalizedDns ?? "")
|
|
: entity.specification.templateName
|
|
);
|
|
});
|
|
|
|
const slice = (items, cursor, size = 20) => {
|
|
const offset = cursor ? items.findIndex((x) => (x.origin?.id ?? x.id) === cursor) + 1 : 0;
|
|
const values = items.slice(offset, offset + size);
|
|
|
|
return {
|
|
items: values,
|
|
nextCursor:
|
|
offset + size < items.length ? (values.at(-1).origin?.id ?? values.at(-1).id) : null,
|
|
};
|
|
};
|
|
|
|
page.embeddedData = {
|
|
shares,
|
|
domain: domain.id,
|
|
publications,
|
|
memberships,
|
|
requests: [],
|
|
fault: null,
|
|
held: null,
|
|
removed: null,
|
|
publicationResponse: null,
|
|
directoryResponse: null,
|
|
releases: [],
|
|
};
|
|
page.embedded.release = () => page.embeddedData.release?.();
|
|
const endpoint = `${page.adoptionOrigin}/api/asset-network-share-publications/${companyId}`;
|
|
|
|
async function publicationResponse(route, _url, params) {
|
|
const d = page.embeddedData;
|
|
|
|
const fromAsset = params.get("anchorType") === "assets:asset";
|
|
const anchor = params.get("anchorId");
|
|
const periods = (
|
|
params.get("includeEnded") === "true" ? [...history, ...publications] : publications
|
|
).filter((p) => (fromAsset ? p.assetId : p.resourceId) === anchor && p.id !== d.removed);
|
|
const pg = slice(periods, params.get("cursor"), Number(params.get("pageSize")));
|
|
const items = pg.items.map(publicationView);
|
|
const response = {
|
|
page: { items, nextCursor: pg.nextCursor },
|
|
summaries: summaries(items, fromAsset),
|
|
publications: pg.items,
|
|
};
|
|
|
|
d.publicationResponse = response;
|
|
if (d.fault === "publications") {
|
|
return route.fulfill({ status: 503, json: { code: "INTERNAL_ERROR" } });
|
|
}
|
|
if (d.fault === "denied") {
|
|
return route.fulfill({ status: 403, json: { code: "FORBIDDEN" } });
|
|
}
|
|
if (d.fault === "hold-publications") {
|
|
await new Promise((resolve) => {
|
|
d.release = resolve;
|
|
d.releases.push(resolve);
|
|
});
|
|
}
|
|
|
|
return route.fulfill({ json: response });
|
|
}
|
|
|
|
async function optionResponse(route, url, params) {
|
|
const d = page.embeddedData;
|
|
const kind = url.pathname.endsWith("/asset-options") ? "asset" : "share";
|
|
const search = params.get("search") ?? "";
|
|
const cursor = params.get("cursor");
|
|
const items = (
|
|
kind === "asset"
|
|
? inventory.assets
|
|
.filter((a) => a.values.status === "operational")
|
|
.map((a) => ({
|
|
id: a.id,
|
|
name: a.values.name,
|
|
templateName: a.specification.templateName,
|
|
status: a.values.status,
|
|
}))
|
|
: shares.map((r) => ({ id: r.id, name: r.values.name, address: r.networkShare.address }))
|
|
).filter((x) =>
|
|
`${x.name} ${x.address ?? ""}`.toLowerCase().includes(search.trim().toLowerCase())
|
|
);
|
|
const response = slice(items, cursor);
|
|
|
|
if (d.fault === "options") {
|
|
return route.fulfill({ status: 503, json: { code: "INTERNAL_ERROR" } });
|
|
}
|
|
if (search === "ANTIGUA") {
|
|
d.held = true;
|
|
await new Promise((resolve) => {
|
|
d.release = resolve;
|
|
d.releases.push(resolve);
|
|
});
|
|
// Una respuesta antigua contiene una opción distinguible, aunque su petición haya sido cancelada.
|
|
response.items = [
|
|
{ id: shares[0].id, name: "RESPUESTA ANTIGUA", address: shares[0].networkShare.address },
|
|
];
|
|
}
|
|
try {
|
|
await route.fulfill({ json: response });
|
|
} catch (error) {
|
|
if (!(String(error).includes("intercepted") || String(error).includes("Target page"))) {
|
|
throw error;
|
|
}
|
|
d.requests.push({ aborted: search });
|
|
}
|
|
}
|
|
|
|
await page.route(`${endpoint}/**`, async (route) => {
|
|
if (route.request().method() !== "GET") {
|
|
return route.fallback();
|
|
}
|
|
const url = new URL(route.request().url());
|
|
|
|
page.embeddedData.requests.push({
|
|
path: url.pathname,
|
|
query: Object.fromEntries(url.searchParams),
|
|
});
|
|
|
|
return url.pathname.endsWith("/relationships")
|
|
? publicationResponse(route, url, url.searchParams)
|
|
: optionResponse(route, url, url.searchParams);
|
|
});
|
|
await page.route(
|
|
`${page.adoptionOrigin}/api/asset-directory-memberships/${companyId}/**`,
|
|
async (route) => {
|
|
if (route.request().method() !== "GET") {
|
|
return route.fallback();
|
|
}
|
|
const url = new URL(route.request().url());
|
|
const p = url.searchParams;
|
|
|
|
page.embeddedData.requests.push({ path: url.pathname, query: Object.fromEntries(p) });
|
|
if (url.pathname.endsWith("/eligibility")) {
|
|
const id = url.pathname.split("/").at(-2);
|
|
const candidate = inventory.assets.find((asset) => asset.id === id);
|
|
const template = candidate?.specification.templateName;
|
|
|
|
return route.fulfill({
|
|
json: {
|
|
operational: candidate?.values.status === "operational",
|
|
computer: ["Portátil", "PC de sobremesa"].includes(template),
|
|
server: template === "Servidor",
|
|
},
|
|
});
|
|
}
|
|
if (!url.pathname.endsWith("/relationships")) {
|
|
return route.fallback();
|
|
}
|
|
const fromAsset = p.get("anchorType") === "assets:asset";
|
|
const anchor = p.get("anchorId");
|
|
const pg = slice(
|
|
memberships.filter((m) => (fromAsset ? m.source.id : m.target.id) === anchor),
|
|
p.get("cursor"),
|
|
Number(p.get("pageSize"))
|
|
);
|
|
const response = { page: pg, summaries: summaries(pg.items, fromAsset) };
|
|
|
|
page.embeddedData.directoryResponse = response;
|
|
|
|
return route.fulfill({ json: response });
|
|
}
|
|
);
|
|
|
|
return {
|
|
shares: shares.length,
|
|
publications: publications.length,
|
|
memberships: memberships.length,
|
|
};
|
|
}
|